Getting started

Read basic information from Gurux.DLMS component before you start to create DLMS client.

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
Usually you can use 1 as Server address and 16 as Client address when authentication is not used. If authentication is used Client address changes.
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
Note! ANSI C++ and Delphi supports only None and Low authentication levels at the moment.

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
    byte($21), 
  //Server ID
    byte($3),
  //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 Logican 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);

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;
}

If parameters are right connection is made. Next you can read Association view and show all objects that meter can offer.

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;
}

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;
}

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;
}

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;
}

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;
}

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.
//C++ do not support Image update yet.

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;
}

Reading Block is implemeted as follows:

/**
    * 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;
}

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);
}

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
Using secured messages (glo services) is easy. Before security can be used following properties must set:
  • 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");
Thats it. If you have any questions or ideas how to improve Gurux.DLMS component let us know or ask question in a Forum