Skip to main content
Home
for DLMS smart meters

Main navigation

  • Home
  • Products
  • About us
  • Open Source
  • Community
  • Forum
  • Downloads
  • Gurux Club
User account menu
  • Log in

Breadcrumb

  1. Home
  2. Profile generic
gxdn
Profile picture for user Administrator
By Administrator, 23 January, 2018
Profile generic object is used to save historical data.

Properties

  • 1. Logical Name
    Logical name of the object.
  • 2. Buffer
    Saved historical data.

    Note! Capture objects must be read before buffer can be read.

  • 3. Capture objects
    Captured object list includes all objects whose values are saved to the buffer.
  • 4. Capture period
    How often values of captured objects are saved to the buffer. Value is given in seconds. Automatic capture is not used if value is zero.
  • 5. Sort method
    How buffer is sorted.
  • 6. Sort object
    Target how buffer is sorted. Usually this is Clock object.
  • 7. Entries in use
    Amount of entries in use in the buffer.
  • 8. Profile entries
    Amount of total entries in the buffer.

Actions

  • 1. Reset
    Clears the buffer.
  • 2. Capture
    Captures values from the capture object list to the buffer.

Access data from ANSI C

  • Show capture objects
//How to loop through all capture objects.

Reading data

Data can be read in three different ways from profile generic objects.
  • Read all data
  • Read by range (between start and end time)
  • Read by entry (using index and count)

Note!
You need to read or update Capture objects before reading the buffer.

Read by range might cause problems with some meters. Some meters expect that start and end time are rounded to the nearest hour and they can't handle the seconds or minutes if they are not equal to zero.
Some meters are sending rows where the timestamp is higher than start time and some meters are sending rows where the timestamp is equal to start time.

Read by range is problematic, because data must loop throught to find where data starts and where it ends. Also amount of rows is not known before data is filtered. In this example, data is sorted by time. Start and end indexed of the data are retrieved from the file. Start and end indexes are saved and data between those indexes are retrieved.

  • Retrieve indexes
/**
* Find start index and row count using start and end date time.
*
* @param p
*            Start and end time are get from the parameters..
*/
int getProfileGenericDataByRange(gxValueEventArg* e)
{
    int len, month = 0, day = 0, year = 0, hour = 0, minute = 0, second = 0, value = 0;
    dlmsVARIANT *it;
    gxtime tm, start, end;
    int ret;
    dlmsVARIANT tmp;
    var_init(&tmp);
    if ((ret = va_getByIndex(e->parameters.Arr, 1, &it)) != 0)
    {
        return ret;
    }
    if ((ret = dlms_changeType(it->byteArr, DLMS_DATA_TYPE_DATETIME, &tmp)) != 0)
    {
        var_clear(&tmp);
        return ret;
    }
    //Start time.
    start = *tmp.dateTime;
    var_clear(&tmp);
    if ((ret = va_getByIndex(e->parameters.Arr, 2, &it)) != 0)
    {
        return ret;
    }
    if ((ret = dlms_changeType(it->byteArr, DLMS_DATA_TYPE_DATETIME, &tmp)) != 0)
    {
        var_clear(&tmp);
        return ret;
    }
    end = *tmp.dateTime;
    var_clear(&tmp);

    FILE* f = fopen(DATAFILE, "r");
    if (f != NULL)
    {
        while ((len = fscanf(f, "%d/%d/%d %d:%d:%d;%d", &month, &day, &year, &hour, &minute, &second, &value)) != -1)
        {
            //Skip empty lines.
            if (len == 7)
            {
                time_init3(&tm, year, month, day, hour, minute, second, 0);
                if (time_compare(&tm, &end) > 0)
                {
                    // If all data is read.
                    break;
                }
                if (time_compare(&tm, &start) < 0)
                {
                    // If we have not find first item.
                    ++e->transactionStartIndex;
                }
                ++e->transactionEndIndex;
            }
            else
            {
                break;
            }
        }
        fclose(f);
    }
    return 0;
}
After start and end indexes are known data can be read between those indexes. The size of the buffer is usually so huge that it can't read to memory at one time. For this reason, only data that fits the PDU is read.
  • Get data

/**
* Return data using start and end indexes.
*
* @param p
*            ProfileGeneric
* @param index start index.
* @param count Amount of the rows.
*/
void getProfileGenericDataByEntry(gxProfileGeneric* p, long index, long count)
{
    dlmsVARIANT *tmp;
    variantArray *row;
    int len, month = 0, day = 0, year = 0, hour = 0, minute = 0, second = 0, value = 0;
    if (count != 0)
    {
        FILE* f = fopen(DATAFILE, "r");
        if (f != NULL)
        {
            while ((len = fscanf(f, "%d/%d/%d %d:%d:%d;%d", &month, &day, &year, &hour, &minute, &second, &value)) != -1)
            {
                // Skip row
                if (index > 0)
                {
                    --index;
                }
                else if (len == 7)
                {
                    if (p->buffer.size == count)
                    {
                        break;
                    }
                    row = (variantArray*)malloc(sizeof(variantArray));
                    va_init(row);
                    arr_push(&p->buffer, row);

                    //Add date time.
                    tmp = (dlmsVARIANT*)malloc(sizeof(dlmsVARIANT));
                    var_init(tmp);
                    tmp->dateTime = (gxtime*)malloc(sizeof(gxtime));
                    time_init3(tmp->dateTime, year, month, day, hour, minute, second, 0);
                    tmp->vt = DLMS_DATA_TYPE_DATETIME;
                    va_push(row, tmp);

                    //Add register value.
                    tmp = (dlmsVARIANT*)malloc(sizeof(dlmsVARIANT));
                    var_init(tmp);
                    var_setInt32(tmp, value);
                    va_push(row, tmp);
                }
                if (p->buffer.size == count)
                {
                    break;
                }
            }
            fclose(f);
        }
        //Read values from the begin if ring buffer is used.
        if (p->buffer.size != count)
        {
            getProfileGenericDataByEntry(p, index, count);
        }
    }
}
Now framework handles data. When PDU is sent svr_preRead is called again and next data block can be retrieved.

  • svr_preRead
void svr_preRead(
    dlmsSettings* settings,
    gxValueEventCollection* args)
{
    gxValueEventArg *e;
    dlmsVARIANT* value;
    int ret, pos;
    DLMS_OBJECT_TYPE type;
    for (pos = 0; pos != args->size; ++pos)
    {
        if ((ret = vec_getByIndex(args, pos, &e)) != 0)
        {
            return;
        }
        if (e->target == &profileGeneric.base)
        {
            gxProfileGeneric* p = (gxProfileGeneric*)e->target;
            // If buffer is read and we want to save memory.
            if (e->index == 7)
            {
                // If client wants to know EntriesInUse.
                p->entriesInUse = getProfileGenericDataCount();
            }
            else if (e->index == 2)
            {
                // Read rows from file.
                // If reading first time.
                if (e->transactionEndIndex == 0)
                {
                    if (e->selector == 0)
                    {
                        e->transactionEndIndex = getProfileGenericDataCount();
                    }
                    else if (e->selector == 1)
                    {
                        //Read by entry.
                        if (useRingBuffer)
                        {
                            GetProfileGenericDataByRangeFromRingBuffer(e);
                        }
                        else
                        {
                            getProfileGenericDataByRange(e);
                        }
                    }
                    else if (e->selector == 2)
                    {
                        dlmsVARIANT *it;
                        if ((ret = va_getByIndex(e->parameters.Arr, 0, &it)) != 0)
                        {
                            continue;
                        }
                        unsigned int begin = var_toInteger(it);
                        if ((ret = va_getByIndex(e->parameters.Arr, 1, &it)) != 0)
                        {
                            continue;
                        }
                        e->transactionStartIndex = begin;
                        e->transactionEndIndex = begin + var_toInteger(it);
                        // If client wants to read more data what we have.
                        int cnt = getProfileGenericDataCount();
                        if (e->transactionEndIndex - e->transactionStartIndex > cnt - e->transactionStartIndex)
                        {
                            if (useRingBuffer)
                            {
                                e->transactionEndIndex = cnt;
                            }
                            else
                            {
                                e->transactionEndIndex = cnt - e->transactionStartIndex;
                            }
                            if (e->transactionEndIndex < 0)
                            {
                                e->transactionEndIndex = 0;
                            }
                        }
                    }
                }
                unsigned short count = e->transactionEndIndex - e->transactionStartIndex;
                // Read only rows that can fit to one PDU.
                if (e->transactionEndIndex - e->transactionStartIndex > p->maxRowCount)
                {
                    /**
                    * Max row count is used with Profile Generic to tell how many rows are read
                    * to one PDU. Default value is 1. Change this for your needs.
                    */
                    count = p->maxRowCount;
                }
                // Clear old data. It's already serialized.
                obj_clearProfileGenericBuffer(&p->buffer);

                if (e->selector == 1)
                {
                    getProfileGenericDataByEntry(p, e->transactionStartIndex, count);
                }
                else
                {
                    //Index where to start.
                    unsigned short index = e->transactionStartIndex;
                    if (useRingBuffer)
                    {
                        index += getHead();
                    }
                    getProfileGenericDataByEntry(p, index, count);
                }
            }
        }
    }
}

Reading data from ring buffer

The idea of reading data from ring buffer is same. The only difference is that we must know head position. Here we are reading head position from the file every time.

  • Get head position.

/**
*  Get head position where next new item is inserted.
*
* This is used with the ring buffer.
*
* @return Position where next item is inserted.
*/
unsigned short getHead() {
    unsigned short head = 0;
    gxtime tm, last;
    int len, month = 0, day = 0, year = 1971, hour = 0, minute = 0, second = 0, value = 0;
    time_init3(&last, year, month, day, hour, minute, second, 0);
    FILE* f = fopen(DATAFILE, "r");
    if (f != NULL)
    {
        while ((len = fscanf(f, "%d/%d/%d %d:%d:%d;%d", &month, &day, &year, &hour, &minute, &second, &value)) != -1)
        {
            time_init3(&tm, year, month, day, hour, minute, second, 0);
            if (time_compare(&last, &tm) > 0)
            {
                break;
            }
            ++head;
            last = tm;
        }
        fclose(f);
    }
    return head;
}
Data is read from the file in same way.
  • Get data from the ring buffer.

/**
* Find start index and row count using start and end date time.
*
* @param start
*            Start time.
* @param end
*            End time
* @param index
*            Start index.
* @param count
*            Item count.
*/
int GetProfileGenericDataByRangeFromRingBuffer(gxValueEventArg* e)
{
    int len, month = 0, day = 0, year = 1971, hour = 0, minute = 0, second = 0, value = 0;
    dlmsVARIANT *it;
    gxtime tm, start, end, last;
    int ret;
    unsigned short pos = 0;
    dlmsVARIANT tmp;
    var_init(&tmp);
    time_init3(&last, year, month, day, hour, minute, second, 0);
    if ((ret = va_getByIndex(e->parameters.Arr, 1, &it)) != 0)
    {
        return ret;
    }
    if ((ret = dlms_changeType(it->byteArr, DLMS_DATA_TYPE_DATETIME, &tmp)) != 0)
    {
        var_clear(&tmp);
        return ret;
    }
    //Start time.
    start = *tmp.dateTime;
    var_clear(&tmp);
    if ((ret = va_getByIndex(e->parameters.Arr, 2, &it)) != 0)
    {
        return ret;
    }
    if ((ret = dlms_changeType(it->byteArr, DLMS_DATA_TYPE_DATETIME, &tmp)) != 0)
    {
        var_clear(&tmp);
        return ret;
    }
    end = *tmp.dateTime;
    var_clear(&tmp);
    FILE* f = fopen(DATAFILE, "r");
    if (f != NULL)
    {
        while ((len = fscanf(f, "%d/%d/%d %d:%d:%d;%d", &month, &day, &year, &hour, &minute, &second, &value)) != -1)
        {
            //Skip emmpty lines.
            if (len == 7)
            {
                time_init3(&tm, year, month, day, hour, minute, second, 0);
                //If value is inside of start and end time.
                if (time_compare(&tm, &start) >= 0 && time_compare(&tm, &end) <= 0)
                {
                    if (last.value.tm_year == 71)
                    {
                        e->transactionStartIndex = pos;
                        //Save end position if we have only one row.
                        e->transactionEndIndex = pos + 1;
                    }
                    else
                    {
                        if (time_compare(&tm, &last) > 0)
                        {
                            e->transactionEndIndex = pos + 1;
                        }
                        else
                        {
                            gxProfileGeneric* p = (gxProfileGeneric*)e->target;
                            if (e->transactionEndIndex == 0)
                            {
                                ++e->transactionEndIndex;
                            }
                            e->transactionEndIndex += getProfileGenericDataCount(p);
                            e->transactionStartIndex = pos;
                            break;
                        }
                    }
                    time_copy(&last, &tm);
                }
                ++pos;
            }
            else
            {
                break;
            }
        }
        fclose(f);
    }
    return 0;
}

Book traversal links for Profile generic

  • PPP setup
  • Up
  • Push Setup
  • Create new account
  • Reset your password

Book navigation

  • Activity calendar
  • Association Logical Name
  • Auto Connect
  • Auto answer
  • Clock
  • Compact data
  • Data
  • Demand register
  • Disconnect control
  • Extended register
  • GPRS modem setup
  • GSM diagnostic
  • IEC HDLC setup
  • IEC local port setup
  • IPv4 setup
  • IPv6 setup
  • Image transfer
  • Limiter
  • M-Bus Client
  • M-Bus master port setup
  • M-Bus slave port setup
  • MAC address setup
  • Modem configuration
  • PPP setup
  • Profile generic
  • Push Setup
  • Register
  • Register Monitor
  • Register activation
  • Register table
  • SAP assignment
  • Script table
  • Security setup
  • Single action schedule
  • Special days table
  • Status mapping
  • Tcp Udp Setup
  • Utility tables

Hire Us!

Latest Releases

Fri, 03/24/2023 - 14:22
gurux.dlms.c 20230324.1
Thu, 03/23/2023 - 11:01
GXDLMSDirector 9.0.2303.2301
Thu, 03/23/2023 - 09:10
Gurux.DLMS.Python 1.0.142
Wed, 03/22/2023 - 13:51
Gurux.DLMS.Net 9.0.2303.2201
Wed, 03/22/2023 - 10:15
gurux.dlms.c 20230322.1

Open bugs

Gurux.DLMS.AMI4
1
Gurux.DLMS.Android
1
gurux.dlms.c
3
gurux.dlms.cpp
3
gurux.dlms.delphi
1
RSS feed
Privacy FAQ GXDN Issues Contact
Follow Gurux on Twitter Follow Gurux on Linkedin