Profile generic object is used to save historical data.
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.
Now framework handles data. When PDU is sent svr_preRead is called again and next data block can be retrieved.
Data is read from the file in same way.
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
//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.
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.
/** * 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; }
/** * 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); } } }
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; }
/** * 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; }