Simple meter reading example
Before use the following device parameters must be set. Parameters are manufacturer specific- Is Short or Logical name used.
- Server address
- Client address
- Interface type
You can read here to find out how Client and Server addresses are counted. Some manufacturers might use own custom Server and Client addresses. Note!
If Server address is wrong meter does not send response. If Client address is wrong you will get authentication error. Using authentication When authentication (Access security) is used server(meter) can allow different rights to the client. Without authentication (None) only read is allowed. Gurux DLMS components supports six different authentication level:
- None
- Low
- High
- HighMD5
- HighSHA1
- GMAC
GXDLMSClient client = new GXDLMSClient(); // Is used Logican Name or Short Name referencing. client.setUseLogicalNameReferencing(true); // Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) client.setInterfaceType(InterfaceType.HDLC); client.setClientAddress(16); client.setServerAddress(1);
GXDLMSClient client = new GXDLMSClient(); // Is used Logican Name or Short Name referencing. client.UseLogicalNameReferencing = true; // Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) client.InterfaceType = InterfaceType.Hdlc; client.ClientAddress = 16; client.ServerAddress = 1;
Client := TGXDLMSClient.Create( // Is used Logican Name or Short Name referencing. True, //Client ID 16, //Server ID 1, //Authentication Level. TAuthentication.None, //PassWord Nil, // Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) TInterfaceType.General);
CGXDLMSClient cl( // Is used Logican Name or Short Name referencing. true, //Client ID 16, //Server ID 1, //Authentication Level. GXDLMS_AUTHENTICATION_LOW, //PassWord "ABCDEFGH", // Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) GXDLMS_INTERFACETYPE_HDLC);
connection con; //Support trace. con_init(&con, 1); //Initialize settings using Logical Name referencing and WRAPPER. cl_init(&con.settings, // Is used Logican Name or Short Name referencing. 1, //Client ID 16, //Server ID 1, //Authentication Level. DLMS_AUTHENTICATION_NONE, //Password NULL, // Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) DLMS_INTERFACE_TYPE_HDLC);
client = GXDLMSClient() #Is used Logican Name or Short Name referencing. client.useLogicalNameReferencing = True #Is used HDLC or COSEM transport layers for IPv4 networks (IEC 62056-47) client.interfaceType = InterfaceType.HDLC client.clientAddress = 16 client.serverAddress = 1
Connection Initialization
The connection initialization depends on the connection type and the device. Some devices require IEC62056-21 protocol handshake when serial port connecion is used before starting to communicate using DLMS protocol.Serial/Modem handshake
This is done only with serial and modem connections. Direct tcp/ip doesn't need this. If IEC62056-21 handshake is used it is done before this. This handshake uses SNMR request to gather packet and window size information from the device. If the device does not reply to this message usually Server address is wrong or meter do not support DLMS. Once the full reply data is received call ParseUAResponse method to set configure GXCOSEM Component with correct settings.AARE request
This is the first command that is mandatory for all connections and device types. This command tells the device if authentication is used and whether Long Name or Short Name reference is used. The packet can be generated with AARQRequest method and it uses UseLogicalName and Authentication properties so make sure these are set to correct values. If password is defined all data do not necessary do not fit to one message. Once the full reply is received parse it with ParseAAREResponse method. This method sets the relevant settings to the GXCOSEM component and return a collection of manufacturer specific tags if there was any. Now the connection is established.GXReplyData reply = new GXReplyData(); byte[] data; data = client.SNRMRequest(); if (data != null) { readDLMSPacket(data, reply); //Has server accepted client. client.parseUAResponse(reply.getData()); } //Generate AARQ request. //Split requests to multiple packets if needed. //If password is used all data might not fit to one packet. for (byte[] it : client.AARQRequest()) { reply.clear(); reply = readDLMSPacket(it, reply); } //Parse reply. client.parseAAREResponse(reply.getData());
GXReplyData reply = new GXReplyData(); byte[] data; data = client.SNRMRequest(); if (data != null) { ReadDLMSPacket(data, reply); //Has server accepted client. client.ParseUAResponse(reply.Data); } //Generate AARQ request. //Split requests to multiple packets if needed. //If password is used all data might not fit to one packet. foreach (byte[] it in client.AARQRequest()) { reply.Clear(); reply = ReadDLMSPacket(it, reply); } //Parse reply. client.ParseAAREResponse(reply.Data);
data := Client.SNRMRequest; if (data <> Nil) then begin reply := ReadDLMSPacket(data); Client.ParseUAResponse(reply); end; for data in Client.AARQRequest(nil) do begin reply := ReadDLMSPacket(data); end; Client.ParseAAREResponse(reply);
//Initialize connection to the meter. int InitializeConnection() { std::vector< std::vector<unsigned char> > data; vector<unsigned char> reply; int ret = 0; //Get meter's send and receive buffers size. if ((ret = m_Parser->SNRMRequest(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = m_Parser->ParseUAResponse(reply)) != 0) { TRACE("SNRMRequest failed %d.\r\n", ret); return ret; } reply.clear(); if (m_Parser->GetIntefaceType() == GXDLMS_INTERFACETYPE_NET) { InitializeBuffers(0xFFFF, 0xFFFF); } else { //Initialize send and receive buffers to same as meter's buffers. GXDLMSLimits limits = m_Parser->GetLimits(); CGXDLMSVariant rx = limits.GetMaxInfoRX(); rx.ChangeType(DLMS_DATA_TYPE_INT32); CGXDLMSVariant tx = limits.GetMaxInfoTX(); tx.ChangeType(DLMS_DATA_TYPE_INT32); InitializeBuffers(rx.lVal, tx.lVal); } if ((ret = m_Parser->AARQRequest(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = m_Parser->ParseAAREResponse(reply)) != 0) { if (ret == ERROR_CODES_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED) { TRACE1("Use Logical Name referencing is wrong. Change it!\r\n"); return ret; } TRACE("AARQRequest failed %d.\r\n", ret); return ret; } return ERROR_CODES_OK; }
//Initialize connection to the meter. int com_init( connection *connection) { int ret = DLMS_ERROR_CODE_OK; message messages; gxReplyData reply; printf("InitializeConnection\r\n"); mes_init(&messages); reply_init(&reply); //Get meter's send and receive buffers size. if ((ret = cl_snrmRequest(&connection->settings, &messages)) != 0 || (ret = com_readDataBlock(connection, &messages, &reply)) != 0 || (ret = cl_parseUAResponse(&connection->settings, &reply.data)) != 0) { mes_clear(&messages); reply_clear(&reply); printf("SNRMRequest failed %s\r\n", hlp_getErrorMessage(ret)); return ret; } mes_clear(&messages); reply_clear(&reply); if ((ret = cl_aarqRequest(&connection->settings, &messages)) != 0 || (ret = com_readDataBlock(connection, &messages, &reply)) != 0 || (ret = cl_parseAAREResponse(&connection->settings, &reply.data)) != 0) { mes_clear(&messages); reply_clear(&reply); if (ret == DLMS_ERROR_CODE_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED) { printf("Use Logical Name referencing is wrong. Change it!\r\n"); return ret; } printf("AARQRequest failed %s\r\n", hlp_getErrorMessage(ret)); return ret; } mes_clear(&messages); reply_clear(&reply); if (connection->settings.maxPduSize == 0xFFFF) { con_initializeBuffers(connection, connection->settings.maxPduSize); } else { //Allocate 50 bytes more because some meters count this wrong and send few bytes too many. con_initializeBuffers(connection, 50 + connection->settings.maxPduSize); } // Get challenge Is HLS authentication is used. if (connection->settings.isAuthenticationRequired) { if ((ret = cl_getApplicationAssociationRequest(&connection->settings, &messages)) != 0 || (ret = com_readDataBlock(connection, &messages, &reply)) != 0 || (ret = cl_parseApplicationAssociationResponse(&connection->settings, &reply.data)) != 0) { mes_clear(&messages); reply_clear(&reply); return ret; } mes_clear(&messages); reply_clear(&reply); } return DLMS_ERROR_CODE_OK; }
reply = GXReplyData() data = client.snrmRequest() if data: self.readDLMSPacket(data, reply) #Has server accepted client. client.parseUAResponse(reply.Data) #Generate AARQ request. #Split requests to multiple packets if needed. #If password is used all data might not fit to one packet. for it in client.aarqRequest(): reply.clear() reply = self.readDLMSPacket(it, reply) #Parse reply. client.parseAAREResponse(reply.Data)
Association View
Association View describes what kind of objects meter offers. The association view is requested using data from GetObjects method. The reply data is very large so this can take very long time (even hours with slow connection like terminal and if the connection is bad). Because this takes so long it is recommended to save this information for future use as it doesn't change unless meter software is updated. The data structure varies between manufacturers and device models. The reply data is parsed using ParseObjects method in GXCOSEM Component. The method returns a collection of DLMS Objects. Some of the data object may be typed as Profile Generic. These are special objects that have columns and rows instead of single value. You can think Profile Generic as a Table. You should read captures objects (Column names) of Profile Generics here. Note!Some meters can return different objects depending from Authentication level. Example Actaris returns only Device ID with Low authentication level. Also Indian Standard IS 15959 defines what what kind of objects are available different authentication levels.
/// Read Association View from the meter. GXReplyData reply = new GXReplyData(); readDataBlock(client.getObjects(), reply); GXDLMSObjectCollection objects = client.parseObjects(reply.getData(), true);
/// Read Association View from the meter. GXReplyData reply = new GXReplyData(); ReadDataBlock(client.GetObjects(), reply); GXDLMSObjectCollection objects = client.ParseObjects(reply.Data, true);
/// Read Association View from the meter. reply := ReadDataBlock(Client.GetObjectsRequest()); Client.ParseObjects(reply, True); Result := Client.Objects;
/// Read Association View from the meter. if ((ret = Client->GetObjectsRequest(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = Client->ParseObjects(reply, objects)) != 0) { TRACE("GetObjects failed %d.\r\n", ret); return ret; }
/// Read Association View from the meter. int com_getObjects(connection *connection) { int ret; message data; gxReplyData reply; mes_init(&data); reply_init(&reply); if ((ret = cl_getObjectsRequest(&connection->settings, &data)) != 0 || (ret = com_readDataBlock(connection, &data, &reply)) != 0 || (ret = cl_parseObjects(&connection->settings, &reply.data)) != 0) { printf("GetObjects failed %s\r\n", hlp_getErrorMessage(ret)); } mes_clear(&data); reply_clear(&reply); return ret; }
#Read Association View from the meter. reply = new GXReplyData() self.readDataBlock(client.getObjects(), reply) objects = client.parseObjects(reply.Data, True)
Reading values
Reading is generally split in two. Reading of "regular" data objects and profile generic objects.General Data Objects
Data objects are read using Read method. The parameters are defined as follows: Item: COSEM Object to read.Attribute Ordinal: The ordinal number of the requested attribute. The data is updated using UpdateValue method that returns the value in the format that the device provided.
Profile Generic objects
There are two ways to read Profile Generic data. By Entry or Range. In entry parameters tell where read is started (Zero index) and how many items are read. In range parameters tell starting and ending time. Note! All meters do not support read by entry or range. You can check is this supported from conformance. The request is generated using ReadRowsByEntry or ReadRowsByRange methods.Object readObject(GXDLMSObject item, int attributeIndex) throws Exception { GXReplyData reply = new GXReplyData(); byte[] data = Client.read(item, attributeIndex)[0]; readDataBlock(data, reply); return Client.updateValue(item, attributeIndex, reply.getValue()); }
// Read attribute value. public object Read(GXDLMSObject it, int attributeIndex) { GXReplyData reply = new GXReplyData(); ReadDataBlock(Client.Read(it.Name, it.ObjectType, attributeIndex), reply); return Client.UpdateValue(it, attributeIndex, reply.Value); }
function Read(it : TGXDLMSObject; attributeIndex : Integer) : TValue; var reply : TBytes; begin reply := ReadDataBlock(Client.Read(it.Name, it.ObjectType, attributeIndex)[0]); Result := Client.UpdateValue(reply, it, attributeIndex); end;
int Read(CGXDLMSObject* pObject, int attributeIndex) { int ret; std::vector<CGXByteBuffer> data; CGXReplyData reply; //Read data from the meter. if ((ret = m_Parser->Read(pObject, attributeIndex, data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = m_Parser->UpdateValue(*pObject, attributeIndex, reply.GetValue())) != 0) { return ret; } return DLMS_ERROR_CODE_OK; }
//Read object. int com_readObject( connection *connection, gxObject* object, unsigned char attributeOrdinal) { int ret; message data; gxReplyData reply; mes_init(&data); reply_init(&reply); if ((ret = cl_read(&connection->settings, object, attributeOrdinal, &data)) != 0 || (ret = com_readDataBlock(connection, &data, &reply)) != 0 || (ret = cl_updateValue(&connection->settings, object, attributeOrdinal, &reply.dataValue)) != 0) { } mes_clear(&data); reply_clear(&reply); return ret; }
def read(self, item, attributeIndex): data = self.client.read(item, attributeIndex)[0] reply = GXReplyData() self.readDataBlock(data, reply) if item.getDataType(attributeIndex) == DataType.NONE: item.setDataType(attributeIndex, reply.valueType) return self.client.updateValue(item, attributeIndex, reply.value)
Writing values
Writing values to the meter is very simple. You just Update Object's propery and then write it. In this example we want to update clock time of the meter. Note!Data type must be correct or meter returns usually error. If you are reading byte value you can't write UIn16.
void writeObject(GXDLMSObject item, int attributeIndex) throws Exception { GXReplyData reply = new GXReplyData(); byte[] data = Client.write(item, attributeIndex); readDataBlock(data, reply); }
// Write attribute value. public void Write(GXDLMSObject it, int attributeIndex) { GXReplyData reply = new GXReplyData(); ReadDataBlock(Client.Write(it, attributeIndex), reply); }
// Write attribute value. procedure TGXProgram.Write(it : TGXDLMSObject; attributeIndex : Integer); begin ReadDataBlock(Client.Write(it, attributeIndex)[0]); end;
//Write selected object. int Write(CGXObject* pObject, int attributeIndex, CGXDLMSVariant& value) { int ret; vector< vector<unsigned char> > data; vector<unsigned char> reply; //Get meter's send and receive buffers size. CGXDLMSVariant name = pObject->GetName(); if ((ret = Client->Write(name, pObject->GetObjectType(), attributeIndex, value, data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0) { TRACE("Write failed %d.\r\n", ret); return ret; } return ERROR_CODES_OK; }
//Write selected object. int com_write( connection *connection, gxObject* object, unsigned char attributeOrdinal) { int ret; message data; gxReplyData reply; mes_init(&data); reply_init(&reply); if ((ret = cl_write(&connection->settings, object, attributeOrdinal, &data)) != 0 || (ret = com_readDataBlock(connection, &data, &reply)) != 0) { printf("Write failed %s\r\n", hlp_getErrorMessage(ret)); } mes_clear(&data); reply_clear(&reply); return ret; }
# Write attribute value. reply = new GXReplyData() self.readDataBlock(client.write(it, attributeIndex), reply) }
Disconnecting
Last you must close the connection by sending disconnecting request. If you do not close connection correctly your next connection attempt will fail.void close() throws Exception { if (Media != null) { GXReplyData reply = new GXReplyData(); readDLMSPacket(Client.disconnectRequest(), reply); Media.close(); } }
void Close() { if (Media != null && Client != null) { try { GXReplyData reply = new GXReplyData(); ReadDLMSPacket(Client.DisconnectRequest(), reply); Media.Close(); } catch { //Ignore if close fails. } Media = null; Client = null; } }
procedure Close(); begin if (Client <> Nil) then begin ReadDLMSPacket(Client.DisconnectRequest()); end; FreeAndNil(socket); FreeAndNil(Client); end;
int Close() { int ret; vector< vector<unsigned char> > data; vector<unsigned char> reply; if ((ret = m_Parser->DisconnectRequest(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0) { //Show error. } #if _MSC_VER > 1000 if (m_hComPort != INVALID_HANDLE_VALUE) { CloseHandle(m_hComPort); m_hComPort = INVALID_HANDLE_VALUE; CloseHandle(m_osReader.hEvent); CloseHandle(m_osWrite.hEvent); } #endif if (m_socket != -1) { closesocket(m_socket); m_socket = -1; } return ret; }
int com_close( connection *connection) { int ret = DLMS_ERROR_CODE_OK; gxReplyData reply; message msg; //If client is closed. if (!connection->settings.server) { reply_init(&reply); mes_init(&msg); if ((ret = cl_disconnectRequest(&connection->settings, &msg)) != 0 || (ret = com_readDataBlock(connection, &msg, &reply)) != 0) { //Show error but continue close. printf("Close failed."); } reply_clear(&reply); mes_clear(&msg); } if (connection->socket != -1) { connection->closing = 1; #if defined(_WIN32) || defined(_WIN64)//Windows includes closesocket(connection->socket); #else close(connection->socket); #endif connection->socket = -1; } else if (connection->comPort != INVALID_HANDLE_VALUE) { #if defined(_WIN32) || defined(_WIN64)//Windows includes CloseHandle(connection->comPort); connection->comPort = INVALID_HANDLE_VALUE; CloseHandle(connection->osReader.hEvent); CloseHandle(connection->osWrite.hEvent); #else close(connection->comPort); #endif connection->comPort = INVALID_HANDLE_VALUE; } cl_clear(&connection->settings); return ret; }
def close(self): if self.media: print("DisconnectRequest") reply = GXReplyData() try: self.readDataBlock(self.client.releaseRequest(), reply) except Exception as e: pass # All meters don't support release. reply.clear() self.readDLMSPacket(self.client.disconnectRequest(), reply) self.media.close()
Keep Alive
If you do not read anything from the meter you must send a keep alive message. Best way is make a connection, read all data from the meter and close connection.GXReplyData reply = new GXReplyData(); readDLMSPacket(Client.keepAlive(), reply);
GXReplyData reply = new GXReplyData(); ReadDLMSPacket(Client.KeepAlive(), reply);
ReadDLMSPacket(Client.GetKeepAlive());
int KeepAlive() { int ret; vector< vector<unsigned char> > data; vector<unsigned char> reply; //Get meter's send and receive buffers size. CGXDLMSVariant name = pObject->GetName(); if ((ret = Client->GetKeepAlive(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = Client->GetValue(reply, value)) != 0) { TRACE("Read failed %d.\r\n", ret); } return ret; }
int com_keepAlive( connection *connection) { int ret; message data; gxReplyData reply; mes_init(&data); reply_init(&reply); if ((ret = cl_keepAlive(&connection->settings, &data)) != 0 || (ret = com_readDataBlock(connection, &data, &reply)) != 0) { printf("Write failed %s\r\n", hlp_getErrorMessage(ret)); } mes_clear(&data); reply_clear(&reply); return ret; }
reply = new GXReplyData() self.readDLMSPacket(client.keepAlive(), reply)
Image updating
Updating new firmware to the meter is easy.GXReplyData reply = new GXReplyData(); //Check that image transfer ia enabled. readDataBlock(Client, Media, Client.read(target, 5), reply); Client.updateValue(target, 5, reply.getValue()); if (!target.getImageTransferEnabled()) { throw new Exception("Image transfer is not enabled"); } //Step 1: Read image block size. readDataBlock(Client, Media, Client.read(target, 2), reply); Client.updateValue(target, 2, reply.getValue()); // Step 2: Initiate the Image transfer process. readDataBlock(Client, Media, target.imageTransferInitiate(Client, Identification, data.length), reply); // Step 3: Transfers ImageBlocks. int[] imageBlockCount = new int[1]; readDataBlock(Client, Media, target.imageBlockTransfer(Client, data, imageBlockCount), reply); //Step 4: Check the completeness of the Image. readDataBlock(Client, Media, Client.read(target, 3), reply); Client.updateValue(target, 3, reply.getValue()); // Step 5: The Image is verified; readDataBlock(Client, Media, target.imageVerify(Client), reply); // Step 6: Before activation, the Image is checked; //Get list to imaages to activate. readDataBlock(Client, Media, Client.read(target, 7), reply); Client.updateValue(target, 7, reply); boolean bFound = false; for (GXDLMSImageActivateInfo it : target.getImageActivateInfo()) { if (it.getIdentification().equals(Identification)) { bFound = true; break; } } //Read image transfer status. readDataBlock(Client, Media, Client.read(target, 6), reply); Client.updateValue(target, 6, reply); if (target.getImageTransferStatus() != ImageTransferStatus.IMAGE_VERIFICATION_SUCCESSFUL) { throw new RuntimeException("Image transfer status is " + target.getImageTransferStatus().toString()); } if (!bFound) { throw new RuntimeException("Image not found."); } //Step 7: Activate image. readDataBlock(Client, Media, target.imageActivate(Client), reply);
//Check that image transfer ia enabled. GXReplyData reply = new GXReplyData(); ReadDataBlock(Client, Media, Client.Read(target, 5), reply); Client.UpdateValue(target, 5, reply.Value); if (!target.ImageTransferEnabled) { throw new Exception("Image transfer is not enabled"); } //Step 1: Read image block size. ReadDataBlock(Client, Media, Client.Read(target, 2), reply); Client.UpdateValue(target, 2, reply.Value); // Step 2: Initiate the Image transfer process. ReadDataBlock(Client, Media, target.ImageTransferInitiate(Client, Identification, data.Length), reply); // Step 3: Transfers ImageBlocks. int imageBlockCount; ReadDataBlock(Client, Media, target.ImageBlockTransfer(Client, data, out imageBlockCount), reply); //Step 4: Check the completeness of the Image. ReadDataBlock(Client, Media, Client.Read(target, 3), reply); Client.UpdateValue(target, 3, reply.Value); // Step 5: The Image is verified; ReadDataBlock(Client, Media, target.ImageVerify(Client), reply); // Step 6: Before activation, the Image is checked; //Get list to images to activate. ReadDataBlock(Client, Media, Client.Read(target, 7), reply); Client.UpdateValue(target, 7, reply.Value); bool bFound = false; foreach (GXDLMSImageActivateInfo it in target.ImageActivateInfo) { if (it.Identification == Identification) { bFound = true; break; } } //Read image transfer status. ReadDataBlock(Client, Media, Client.Read(target, 6), reply); Client.UpdateValue(target, 6, reply); if (target.ImageTransferStatus != ImageTransferStatus.VerificationSuccessful) { throw new Exception("Image transfer status is " + target.ImageTransferStatus.ToString()); } if (!bFound) { throw new Exception("Image not found."); } //Step 7: Activate image. ReadDataBlock(Client, Media, target.ImageActivate(Client), reply);
//Delphi do not support Image update yet.
CGXReplyData reply; std::string value; std::vector<CGXByteBuffer> blocks; CGXDLMSImageTransfer target("0.0.44.0.0.255"); //Check that image transfer ia enabled. if ((ret = Read(&target, 5, value)) != DLMS_ERROR_CODE_OK) { return ret; } if (!target.GetImageTransferEnabled()) { //Image transfer is not enabled. return DLMS_ERROR_CODE_INVALID_PARAMETER; } //Step 1: Read image block size. if ((ret = Read(&target, 2, value)) != DLMS_ERROR_CODE_OK) { return ret; } // Step 2: Initiate the Image transfer process. char* identification = "Image identifier. This depends from the manufacturer and version."; if ((ret = target.ImageTransferInitiate(m_Parser, identification, strlen(identification), blocks)) != 0 || (ret = ReadDataBlock(blocks, reply)) != 0) { return ret; } reply.Clear(); blocks.clear(); // Step 3: Transfers ImageBlocks. unsigned long imageBlockCount = 0; //Image to update. Read this from the file. CGXByteBuffer image; if ((ret = target.ImageBlockTransfer(m_Parser, image, imageBlockCount, blocks)) != 0 || (ret = ReadDataBlock(blocks, reply)) != 0) { return ret; } reply.Clear(); blocks.clear(); //Step 4: Check the completeness of the Image. if ((ret = Read(&target, 3, value)) != DLMS_ERROR_CODE_OK) { return ret; } // Step 5: The Image is verified; if ((ret = target.ImageVerify(m_Parser, blocks)) != 0 || (ret = ReadDataBlock(blocks, reply)) != 0) { return ret; } reply.Clear(); blocks.clear(); // Step 6: Before activation, the Image is checked; //Get list to imaages to activate. if ((ret = Read(&target, 7, value)) != DLMS_ERROR_CODE_OK) { return ret; } boolean bFound = false; for (std::vector<CGXDLMSImageActivateInfo*>::iterator it = target.GetImageActivateInfo().begin(); it != target.GetImageActivateInfo().end(); ++it) { if ((*it)->GetIdentification().Compare((unsigned char*)identification, strlen(identification)) == 0) { bFound = true; break; } } //Read image transfer status. if ((ret = Read(&target, 6, value)) != DLMS_ERROR_CODE_OK) { return ret; } if (target.GetImageTransferStatus() != DLMS_IMAGE_TRANSFER_STATUS_VERIFICATION_SUCCESSFUL) { //Invalid Image transfer status. return DLMS_ERROR_CODE_INVALID_PARAMETER; } if (!bFound) { //Image not found; return DLMS_ERROR_CODE_INVALID_PARAMETER; } //Step 7: Activate image. if ((ret = target.ImageActivate(m_Parser, blocks)) != 0 || (ret = ReadDataBlock(blocks, reply)) != 0) { return ret; }
Sending and receiving data
Data is not always fit to one packet. In this case data can be split to blocks and one block to frames. Gurux DLMS component handles this automatically. When a full frame is received check if there is more data available using IsMoreData method. If there is more data, generate a new read message using ReceiverReady method and check are there all data received again. After receiving the full frame check that there wasn't any errors from err. Reading Frame is implemented as follows:public void readDLMSPacket(byte[] data, GXReplyData reply) throws Exception { if (data == null || data.length == 0) { return; } Object eop = (byte) 0x7E; // In network connection terminator is not used. if (dlms.getInterfaceType() == InterfaceType.WRAPPER && Media instanceof GXNet) { eop = null; } Integer pos = 0; boolean succeeded = false; ReceiveParameters<byte[]> p = new ReceiveParameters<byte[]>(byte[].class); p.setAllData(true); p.setEop(eop); p.setCount(5); p.setWaitTime(WaitTime); synchronized (Media.getSynchronous()) { while (!succeeded) { writeTrace("<- " + now() + "\t" + GXCommon.toHex(data)); Media.send(data, null); if (p.getEop() == null) { p.setCount(1); } succeeded = Media.receive(p); if (!succeeded) { // Try to read again... if (pos++ != 3) { System.out.println("Data send failed. Try to resend " + pos.toString() + "/3"); continue; } throw new RuntimeException("Failed to receive reply from the device in given time."); } } // Loop until whole DLMS packet is received. while (!dlms.getData(p.getReply(), reply)) { if (p.getEop() == null) { p.setCount(1); } if (!Media.receive(p)) { throw new Exception("Failed to receive reply from the device in given time."); } } } writeTrace("-> " + now() + "\t" + GXCommon.toHex(p.getReply())); if (reply.getError() != 0) { throw new GXDLMSException(reply.getError()); } }
public void ReadDLMSPacket(byte[] data, GXReplyData reply) { if (data == null) { return; } object eop = (byte)0x7E; //In network connection terminator is not used. if (Client.InterfaceType == InterfaceType.Wrapper && Media is GXNet) { eop = null; } int pos = 0; bool succeeded = false; ReceiveParameters<byte[]> p = new ReceiveParameters<byte[]>() { AllData = true, Eop = eop, Count = 5, WaitTime = WaitTime, }; lock (Media.Synchronous) { while (!succeeded && pos != 3) { WriteTrace("<- " + DateTime.Now.ToLongTimeString() + "\t" + GXCommon.ToHex(data, true)); Media.Send(data, null); succeeded = Media.Receive(p); if (!succeeded) { //If Eop is not set read one byte at time. if (p.Eop == null) { p.Count = 1; } //Try to read again... if (++pos != 3) { System.Diagnostics.Debug.WriteLine("Data send failed. Try to resend " + pos.ToString() + "/3"); continue; } throw new Exception("Failed to receive reply from the device in given time."); } } //Loop until whole COSEM packet is received. while (!Client.GetData(p.Reply, reply)) { //If Eop is not set read one byte at time. if (p.Eop == null) { p.Count = 1; } if (!Media.Receive(p)) { //Try to read again... if (pos != 3) { System.Diagnostics.Debug.WriteLine("Data send failed. Try to resend " + pos.ToString() + "/3"); continue; } throw new Exception("Failed to receive reply from the device in given time."); } } } if (reply.Error != 0) { throw new GXDLMSException(reply.Error); } }
function ReadDLMSPacket(data: TBytes): TBytes; var error: Integer; cnt, pos: Integer; eop: Variant; Description : string; stream: TWinSocketStream; begin Result := nil; if (data = nil) then Exit; try stream := TWinSocketStream.Create(socket.Socket, WaitTime); if Trace then writeln(TGXCommon.ToHexString(data)); //Send data. stream.Write(data, 0, Length(data)); eop := (Byte(126)); if Client.InterfaceType = TInterfaceType.Net then eop := VarEmpty; pos := 0; repeat //Wait new data. if stream.WaitForData(WaitTime) = false then raise Exception.Create('Failed to received reply from the meter.'); cnt := socket.Socket.ReceiveLength; if cnt <> 0 then begin SetLength(Result, pos + cnt); socket.Socket.ReceiveBuf(Result[pos], cnt); pos := pos + cnt; //If all data is received. if Client.IsDLMSPacketComplete(Result) then begin break; end; end; Until Length(Result) > 2000; if Trace then writeln('-> ' + TimeToStr(Time) + ' ' + TGXCommon.ToHexString(Result)); error := Client.CheckReplyErrors(data, Result, Description); if (error <> 0) then begin raise TGXDLMSException.Create(error); end; finally FreeAndNil(stream); end; end;
// Read DLMS Data frame from the device. int ReadDLMSPacket(CGXByteBuffer& data, CGXReplyData& reply) { int ret; CGXByteBuffer bb; std::string tmp; if (data.GetSize() == 0) { return DLMS_ERROR_CODE_OK; } Now(tmp); tmp = "<- " + tmp; tmp += "\t" + data.ToHexString(); if (m_Trace) { printf("%s\r\n", tmp.c_str()); } int len = data.GetSize(); if (m_hComPort != INVALID_HANDLE_VALUE) { #if defined(_WIN32) || defined(_WIN64)//If Windows DWORD sendSize = 0; BOOL bRes = ::WriteFile(m_hComPort, data.GetData(), len, &sendSize, &m_osWrite); if (!bRes) { DWORD err = GetLastError(); //If error occurs... if (err != ERROR_IO_PENDING) { return DLMS_ERROR_CODE_SEND_FAILED; } //Wait until data is actually sent ::WaitForSingleObject(m_osWrite.hEvent, INFINITE); } #else //If Linux ret = write(m_hComPort, data.GetData(), len); if (ret != len) { printf("send failed %d\n", errno); return DLMS_ERROR_CODE_SEND_FAILED; } #endif } else if ((ret = send(m_socket, (const char*)data.GetData(), len, 0)) == -1) { //If error has occured #if defined(_WIN32) || defined(_WIN64)//If Windows printf("send failed %d\n", WSAGetLastError()); #else printf("send failed %d\n", errno); #endif return DLMS_ERROR_CODE_SEND_FAILED; } // Loop until whole DLMS packet is received. tmp = ""; do { if (m_hComPort != INVALID_HANDLE_VALUE) { if (Read(0x7E, bb) != 0) { return DLMS_ERROR_CODE_SEND_FAILED; } } else { len = RECEIVE_BUFFER_SIZE; if ((ret = recv(m_socket, (char*)m_Receivebuff, len, 0)) == -1) { #if defined(_WIN32) || defined(_WIN64)//If Windows printf("recv failed %d\n", WSAGetLastError()); #else printf("recv failed %d\n", errno); #endif return DLMS_ERROR_CODE_RECEIVE_FAILED; } bb.Set(m_Receivebuff, ret); } if (tmp.size() == 0) { Now(tmp); tmp = "-> " + tmp + "\t"; } else { tmp += " "; } tmp += GXHelpers::BytesToHex(m_Receivebuff, ret); } while ((ret = m_Parser->GetData(bb, reply)) == DLMS_ERROR_CODE_FALSE); tmp += "\r\n"; if (m_Trace) { printf("%s", tmp.c_str()); } GXHelpers::Write("trace.txt", tmp); if (ret == DLMS_ERROR_CODE_REJECTED) { #if defined(_WIN32) || defined(_WIN64)//Windows Sleep(1000); #else usleep(1000000); #endif ret = ReadDLMSPacket(data, reply); } return ret; }
// Read DLMS Data frame from the device. int readDLMSPacket( connection *connection, gxByteBuffer* data, gxReplyData* reply) { char *hex; #if defined(_WIN32) || defined(_WIN64)//Windows unsigned long sendSize = 0; #endif int ret = DLMS_ERROR_CODE_OK, cnt; if (data->size == 0) { return DLMS_ERROR_CODE_OK; } reply->complete = 0; connection->data.size = 0; connection->data.position = 0; if (connection->trace) { hex = bb_toHexString(data); printf("<- %s\r\n", hex); free(hex); } if (connection->comPort != INVALID_HANDLE_VALUE) { #if defined(_WIN32) || defined(_WIN64)//Windows ret = WriteFile(connection->comPort, data->data, data->size, &sendSize, &connection->osWrite); if (ret == 0) { DWORD err = GetLastError(); //If error occurs... if (err != ERROR_IO_PENDING) { return DLMS_ERROR_CODE_SEND_FAILED; } //Wait until data is actually sent ret = WaitForSingleObject(connection->osWrite.hEvent, connection->waitTime); if (ret != 0) { DWORD err = GetLastError(); return DLMS_ERROR_CODE_SEND_FAILED; } } #else ret = write(connection->comPort, data->data, data->size); if (ret != data->size) { return DLMS_ERROR_CODE_SEND_FAILED; } #endif } else { if (send(connection->socket, (const char*)data->data, data->size, 0) == -1) { //If error has occurred printf("DLMS_ERROR_CODE_SEND_FAILED"); return DLMS_ERROR_CODE_SEND_FAILED; } } //Loop until packet is complete. do { if (connection->comPort != INVALID_HANDLE_VALUE) { if (com_read(connection, 0x7E) != 0) { return DLMS_ERROR_CODE_SEND_FAILED; } } else { cnt = connection->data.capacity - connection->data.size; if (cnt < 1) { return DLMS_ERROR_CODE_OUTOFMEMORY; } if ((ret = recv(connection->socket, (char*)connection->data.data + connection->data.size, cnt, 0)) == -1) { return DLMS_ERROR_CODE_RECEIVE_FAILED; } connection->data.size += ret; if (connection->trace) { hex = bb_toHexString(&connection->data); printf("-> %s\r\n", hex); free(hex); } } ret = cl_getData(&connection->settings, &connection->data, reply); if (ret != 0 && ret != DLMS_ERROR_CODE_FALSE) { break; } } while (reply->complete == 0); return ret; }
def readDLMSPacket2(self, data, reply): if data == None or len(data) == 0: return notify = GXReplyData() reply.error = 0 succeeded = False rd = GXByteBuffer() if not reply.isStreaming(): self.writeTrace("TX: " + self.now() + "\t" + GXByteBuffer.hex(data), TraceLevel.VERBOSE) self.media.sendall(data) msgPos = 0 count = 100 pos = 0 try: while not self.client.getData(rd, reply, notify): if notify.data.size != 0: if not notify.isMoreData(): t = GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML) xml = t.dataToXml(notify.data) print(xml) notify.clear() msgPos = rd.position continue rd.position = msgPos rd.set(self.media.recv(100)) if pos == 3: raise ValueError("Failed to receive reply from the device in given time.") if pos != 0: print("Data send failed. Try to resend " + str(pos) + "/3") ++pos except Exception as e: self.writeTrace("RX: " + self.now() + "\t" + rd.__str__(), TraceLevel.ERROR) raise e self.writeTrace("RX: " + self.now() + "\t" + rd.__str__(), TraceLevel.VERBOSE) if reply.error != 0: raise GXDLMSException(reply.error)
/** * Reads next data block. * * @param data * @return * @throws Exception */ void readDataBlock(byte[] data, GXReplyData reply) throws Exception { if (data.length != 0) { readDLMSPacket(data, reply); while (reply.isMoreData()) { data = dlms.receiverReady(reply.getMoreData()); readDLMSPacket(data, reply); } } }
////// Read data block from the device. /// /// data to send /// Progress text. /// ///Received data. public void ReadDataBlock(byte[] data, GXReplyData reply) { ReadDLMSPacket(data, reply); while (reply.IsMoreData) { data = Client.ReceiverReady(reply.MoreData); ReadDLMSPacket(data, reply); if (!Trace) { //If data block is read. if ((reply.MoreData & RequestTypes.Frame) == 0) { Console.Write("+"); } else { Console.Write("-"); } } } }
function ReadDataBlock(data: TBytes): TBytes; var tmp, moredata: Integer; allData, reply: TBytes; begin reply := ReadDLMSPacket(data); moredata := Integer(Client.GetDataFromPacket(reply, allData)); while (Integer(moredata) <> 0) do begin while ((moredata and Integer(TRequestTypes.Frame)) <> 0) do begin data := Client.ReceiverReady(tRequestTypes.Frame); reply := ReadDLMSPacket(data); tmp := Integer(Client.GetDataFromPacket(reply, allData)); if (Trace = False) then writeln('-'); if ((tmp and Integer(TRequestTypes.Frame)) = 0) then begin moredata := moredata and (not Integer(TRequestTypes.Frame)); break; end; end; if (moredata and Integer(TRequestTypes.DataBlock)) <> 0 then begin data := Client.ReceiverReady(TRequestTypes.DataBlock); reply := ReadDLMSPacket(data); moredata := Integer(Client.GetDataFromPacket(reply, allData)); if (Trace = False) then writeln('+'); end; end; Result := allData; end;
int ReadDataBlock(CGXByteBuffer& data, CGXReplyData& reply) { //If ther is no data to send. if (data.GetSize() == 0) { return DLMS_ERROR_CODE_OK; } int ret; CGXByteBuffer bb; //Send data. if ((ret = ReadDLMSPacket(data, reply)) != DLMS_ERROR_CODE_OK) { return ret; } while (reply.IsMoreData()) { bb.Clear(); if ((ret = m_Parser->ReceiverReady(reply.GetMoreData(), bb)) != 0) { return ret; } if ((ret = ReadDLMSPacket(bb, reply)) != DLMS_ERROR_CODE_OK) { return ret; } } return DLMS_ERROR_CODE_OK; }
int com_readDataBlock( connection *connection, message* messages, gxReplyData* reply) { gxByteBuffer rr; int pos, ret = DLMS_ERROR_CODE_OK; //If there is no data to send. if (messages->size == 0) { return DLMS_ERROR_CODE_OK; } bb_init(&rr); //Send data. for (pos = 0; pos != messages->size; ++pos) { //Send data. if ((ret = readDLMSPacket(connection, messages->data[pos], reply)) != DLMS_ERROR_CODE_OK) { return ret; } //Check is there errors or more data from server while (reply_isMoreData(reply)) { if ((ret = cl_receiverReady(&connection->settings, reply->moreData, &rr)) != DLMS_ERROR_CODE_OK) { bb_clear(&rr); return ret; } if ((ret = readDLMSPacket(connection, &rr, reply)) != DLMS_ERROR_CODE_OK) { bb_clear(&rr); return ret; } bb_clear(&rr); } } return ret; }
def readDataBlock(self, data, reply): if data != None and len(data): self.readDLMSPacket(data, reply) while reply.isMoreData(): if reply.isStreaming(): data = None else: data = self.client.receiverReady(reply.moreData) self.readDLMSPacket(data, reply)
Authentication using challenge
When authentication is High or above secret is used. After connection is made client must send challenge to the server and server must accept this challenge. This is done checking is Is Authentication Required after AARE message is parsed. If authentication is required client sends challenge to the server and if everything succeeded server returns own challenge that client checks.//Parse reply. Client.parseAAREResponse(reply); //Get challenge Is HLS authentication is used. if (Client.getIsAuthenticationRequired()) { reply = readDLMSPacket(Client.getApplicationAssociationRequest()); Client.parseApplicationAssociationResponse(reply); }
//Parse reply. Client.ParseAAREResponse(reply); //Get challenge Is HSL authentication is used. if (Client.IsAuthenticationRequired) { reply = ReadDLMSPacket(Client.GetApplicationAssociationRequest()); Client.ParseApplicationAssociationResponse(reply); }
//Delphi do not support this at the moment.
// Get challenge Is HLS authentication is used. if (m_Parser->IsAuthenticationRequired()) { if ((ret = m_Parser->GetApplicationAssociationRequest(data)) != 0 || (ret = ReadDataBlock(data, reply)) != 0 || (ret = m_Parser->ParseApplicationAssociationResponse(reply.GetData())) != 0) { return ret; } }
// Get challenge Is HLS authentication is used. if (connection->settings.isAuthenticationRequired) { if ((ret = cl_getApplicationAssociationRequest(&connection->settings, &messages)) != 0 || (ret = com_readDataBlock(connection, &messages, &reply)) != 0 || (ret = cl_parseApplicationAssociationResponse(&connection->settings, &reply.data)) != 0) { mes_clear(&messages); reply_clear(&reply); return ret; } mes_clear(&messages); reply_clear(&reply); }
//Parse reply. client.parseAAREResponse(reply) #Get challenge Is HSL authentication is used. if client.isAuthenticationRequired: reply = self.readDLMSPacket(client.getApplicationAssociationRequest()) client.parseApplicationAssociationResponse(reply)
Transport security
DLMS supports tree different transport security . When transport security is used each packet is secured using GMAC security. Security level are:- Authentication
- Encryption
- AuthenticationEncryption
- Security
- SystemTitle
- AuthenticationKey
- BlockCipherKey
- FrameCounter
GXDLMSSecureClient sc = new GXDLMSSecureClient(); sc.getCiphering().setSecurity(Security.ENCRYPTION); //Default security when using Gurux test server. sc.getCiphering().setSystemTitle("GRX12345".GetBytes("ASCII");
GXDLMSSecureClient sc = new GXDLMSSecureClient(); sc.Ciphering.Security = Security.Encryption; //Default security when using Gurux test server. sc.Ciphering.SystemTitle = ASCIIEncoding.ASCII.GetBytes("GRX12345");
//Delphi do not support this at the moment.
CGXDLMSSecureClient cl(true); cl.GetCiphering()->SetSecurity(DLMS_SECURITY_ENCRYPTION); CGXByteBuffer st; st.AddString("GRX12345"); cl.GetCiphering()->SetSystemTitle(st);
connection con; con_init(&con, 1); //Initialize settings using Logican Name referencing and HDLC. cl_init(&con.settings, 1, 1, 1, DLMS_AUTHENTICATION_NONE, NULL, DLMS_INTERFACE_TYPE_WRAPPER); con.settings.cipher.security = DLMS_SECURITY_ENCRYPTION; //Clear old values. bb_clear(&con.settings.cipher.systemTitle); bb_clear(&con.settings.cipher.authenticationKey); bb_clear(&con.settings.cipher.blockCipherKey); //Add new ciphering settings. bb_addString(&con.settings.cipher.systemTitle, "GRX12345"); bb_addHexString(&con.settings.cipher.authenticationKey, "D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF"); bb_addHexString(&con.settings.cipher.blockCipherKey, "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
sc = GXDLMSSecureClient() sc.ciphering.security = Security.ENCRYPTION #Default security when using Gurux test server. sc.ciphering.systemTitle = "GRX12345".encode()