gxdn

Profile generic

Profile generic object is used to save historical data.

Properties

  • 1. Logical Name
    Logical name of the object.
  • 2. Buffer
    Saved historical data.
  • 3. Capture objects
    Captured objects that are saved to the buffer..
  • 4. Capture period
    How often values of captured objects are saved to the buffer.
  • 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

//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.

/**
* 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.

/**
* 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.

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 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.

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