
Using Gurux DLMS ANSI C component with microcontroller
If you are using Gurux DLMS ANSI C component in microcontroller there are usually few things that is good to keep in mind. Memory amount is usually causing problems. I have explained here how to avoid common mistakes.General
In general: Use static memory as much as possible.Not like this. This will use stack and it's usually limited.
void main() { gxData ldn, id1, id2, fw; }This is correct way to use it.
gxData ldn, id1, id2, fw; gxAssociationLogicalName noneAssociation, lowAssociation; int main() { }Note! Some compilers are using clock as reserved word. Don't use clock as variable name:
gxClock clock;
PDU and frame
In DLMS data is serialized to PDU. Each PDU is send then in frame(s). For this reason we must have enough memory to handle received frames and convert them to PDU's. When data is sent back data is serialized to PDU and finally frames. Communication is faster if PDU and frame size are bigger, but memory might cause some limitations again. Standard says that minimum PDU size if 64 bytes. Maximum is 65535 (0xFFFF).Note! No real meter is using 0xFFFF for PDU size.
Accessing data
Gurux DLMS framework can handle all DLMS data types.static gxData dataObject; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { GX_INT8(dataObject.value) = 5; //OR GX_UINT8(dataObject.value) = 5; //OR GX_UINT16(dataObject.value) = 5; //OR GX_UINT16(dataObject.value) = 5; //OR GX_UINT32(dataObject.value) = 5; //OR GX_UINT32(dataObject.value) = 5; }
static gxData dataObject; static char STRING_BUFF[17]; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { sprintf(LDN, "%s", "Gurux"); GX_STRING(dataObject.value, STRING_BUFF, 5); }
static gxData dataObject; static unsigned char OCTET_STRING_BUFF[17]; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { GX_OCTET_STRING(dataObject.value, OCTET_STRING_BUFF, sizeof(OCTET_STRING_BUFF)); }
static gxData dataObject; static unsigned char BIT_STRING_BUFF[1] = { 0x80 }; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { //First bit is set. There is only one bit. GX_BIT_STRING(dataObject.value, BIT_STRING_BUFF, 1); }
static gxtime dt; time_now(&dt, 1); static gxData dataObject; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { GX_DATETIME(dataObject.value) = &dt; //OR GX_DATE(dataObject.value) = &dt; //OR GX_TIME(dataObject.value) = &dt; }
static gxData dataObject; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { //String array where are 5 items. Each max size is 17 bytes. static char BUFFER[5][17]; static dlmsVARIANT VARIANT_BUFFER[5]; static variantArray arr; //String lenght is 5 bytes. GX_STRING(VARIANT_BUFFER[0], BUFFER[0], 5); GX_STRING(VARIANT_BUFFER[1], BUFFER[1], 5); GX_STRING(VARIANT_BUFFER[2], BUFFER[2], 0); GX_STRING(VARIANT_BUFFER[3], BUFFER[3], 0); GX_STRING(VARIANT_BUFFER[4], BUFFER[4], 0); sprintf(BUFFER[0], "%s", "Gurux"); sprintf(BUFFER[1], "%s", "Rocks"); //There are 2 items and max string count is 5. va_attach(&arr, VARIANT_BUFFER, 2, sizeof(VARIANT_BUFFER)/sizeof(VARIANT_BUFFER[0])); GX_ARRAY(dataObject.value, arr);}
static uint32_t IntValue = 5; time_now(&dt, 1); static gxData dataObject; if ((ret = INIT_OBJECT(dataObject, DLMS_OBJECT_TYPE_DATA, ln)) == 0) { //If dataObject is using a pointer to IntValue. GX_UINT32_BYREF(dataObject.value, IntValue); }
Serializing data
Serializing data is try to make as easy as possible. You can save object data with ser_saveObjects -method and load data with ser_loadObjects -method. Because serializing all data will take a lot of memory you can decide what to serialize using gxSerializerSettings structure like this://////////////////////////////////////////////////// //Define what is serialized to decrease EEPROM usage. gxSerializerIgnore NON_SERIALIZED_OBJECTS[] = { //Nothing is saved when authentication is not used. IGNORE_ATTRIBUTE(BASE(associationNone), GET_ATTRIBUTE_ALL()), //Only password is saved for low and high authentication. IGNORE_ATTRIBUTE(BASE(associationLow), GET_ATTRIBUTE_EXCEPT(7)), IGNORE_ATTRIBUTE(BASE(associationHigh), GET_ATTRIBUTE_EXCEPT(7)), //Only scaler and unit are saved for all register objects. IGNORE_ATTRIBUTE_BY_TYPE(DLMS_OBJECT_TYPE_REGISTER, GET_ATTRIBUTE(2))};Using IGNORE_ATTRIBUTE you can define what is NOT seriliazed for selected COSEM object. Using IGNORE_ATTRIBUTE_BY_TYPE you can define what is NOT serialized for given COSEM object type.
Implementing server
As described before we must have enough data to save received and transmitted PDUs and frames to the memory. It's good again to reserve this space from static are. Like this:#define HDLC_BUFFER_SIZE 0x80 #define PDU_BUFFER_SIZE 256 unsigned char pduBuff[PDU_BUFFER_SIZE]; unsigned char dataBuff[HDLC_BUFFER_SIZE]; unsigned char replyBuff[PDU_BUFFER_SIZE]; dlmsServerSettings settings; int main() { //Initialize server settings. svr_init2(&settings, //Logical name referencing is used. 1, //Interface type, DLMS_INTERFACE_TYPE_HDLC, //Allocate space to save received PDU. pduBuff, PDU_BUFFER_SIZE, //Allocate space to save received frame. dataBuff, HDLC_BUFFER_SIZE, //Allocate space to save generated (send) frame. replyBuff2, PDU_BUFFER_SIZE); //Start server return svr_start(&settings); } /** * Start server. All all COSEM objects here and initialize server. */ int svr_start(dlmsServerSettings *settings){ char buff[17]; //Serial number. unsigned long sn = 123456; unsigned char NONE_ASSOCIATION[] = {0, 0, 40, 0, 1, 255}; unsigned char LOW_ASSOCIATION[] = {0, 0, 40, 0, 2, 255}; unsigned char LOGICAL_DEVICE_NAME[] = {0, 0, 42, 0, 0, 255}; //Add Logical Name Association object for None authentication. cosem_init2(&noneAssociation.base, DLMS_OBJECT_TYPE_ASSOCIATION_LOGICAL_NAME, NONE_ASSOCIATION); oa_push(&settings->base.objects, &noneAssociation.base); //Add Logical Name Association object for Low authentication. cosem_init2(&lowAssociation.base, DLMS_OBJECT_TYPE_ASSOCIATION_LOGICAL_NAME, LOW_ASSOCIATION); oa_push(&settings->base.objects, &lowAssociation.base); //Add Logical device name. /////////////////////////////////////////////////////////////////////// //Add Logical Device Name. 123456 is meter serial number. /////////////////////////////////////////////////////////////////////// // COSEM Logical Device Name is defined as an octet-string of 16 octets. // The first three octets uniquely identify the manufacturer of the device and it corresponds // to the manufacturer's identification in IEC 62056-21. // The following 13 octets are assigned by the manufacturer. //The manufacturer is responsible for guaranteeing the uniqueness of these octets. cosem_init2(&ldn.base, DLMS_OBJECT_TYPE_DATA, LOGICAL_DEVICE_NAME); sprintf(buff, "GRX%.13lu", sn); var_addBytes(&ldn.value, (unsigned char*)buff, 16); oa_push(&settings->base.objects, &ldn.base); //Add Logical Device Name to object list. //This will cause that only Logical Device Name and Association View are available if //connection is made without authentication (None Authentication). //If authentication list is empty all objects are added when svr_initialize is called. oa_push(&noneAssociation.objectList, &ldn.base); svr_initialize(settings); }Earlier we was using cosem_init to initialize COSEM objects.
cosem_init(&lowAssociation.base, DLMS_OBJECT_TYPE_ASSOCIATION_LOGICAL_NAME, "0.0.40.0.2.255");It's not recommendable because it will use sscanf and it will use stack and it's never released.
Use cosem_init2 instead of cosem_init.
Reading large amount of data on Profile Generic
Reading large amount of data, example historical data (Profile Generic) might cause problems. There might be so many rows that they usually can't read to the memory at once. Problem here is that in DLMS protocol amount of rows must told before actual data. This will cause that we must know how many rows there are on the buffer before we send any data. Client can ask data from profile generic in three different ways.a) All data.
b) Read by entry (Using start and count index).
c) Read by range (Start and End time is used).
Cases A and B are trivial if we know how many rows there are on the buffer. We can say that there will be X amount of rows and then send data that can fit to PDU. After all data is sent, we'll send next PDU until all data is sent. Case C where data is retrieved using start and end time is most difficult. Me must loop data and check how many rows there are. It's common mistake to say that there are 24 rows on one day (if values are saved once a hour). But if there is a power break, there might be only 22 rows. SO we must loop all rows. We are usually implementing this in that way that we loop data and save start and end rows.