четверг, 26 января 2017 г.

TrnGetTable в Citect для лічильних даних



Ще один запис на сьогодні.
Вирішив записувати сюди деякі наробки. Стиль вільний, можна писати як хочеш, а деколи приходиться колупатися в своїх старих проектах, щоб згадати як робив. Думає це буде хороша практика, принаймні треба себе заставляти.
Друга задача, яку вирішував в Citect заставила мене трохи попотіти. Зараз є робочий прототип, тому фіксую результати. Отже задача – виводити оператору значення лічильника енергії (див попередній пост) за останню добу та 30 діб (типу за останній місяць). Задача попахує звітами, але я вирішив зробити її поки трохи лайтовою. Колись робив таке на Integraxor'і - запарився. Взагалі, дослідження проблеми формування нормальних несамопальних звітів для СКАД ще в мене висить в треї як високо пріоритетна, але поки не настільки.
Так от, лайтовість вирішення задачі передбачає не запарюватися зі звітами, а користуватися звичайними тегами. Її (задачу) варто розглядати з 2-х боків – запис та читання.
Запис значень в архів (30 діб все таки) можна проводити через бази даних (у сайтекту багато можливостей у цьому плані). Я думав піти по цьому шляху, але стало ліньки. Замість цього я вирішив скористатися трендами. Хоч дані лічильників мало корисні для відображення в якості трендів, трендовий архів дає можливість витягувати дані через сайкод функцію TrnGetTable. Отже, сказано, зроблено. Тут (стосовно конфігурування запису тренду) особливо й коментувати нічого, єдине, що аналоговий тренд треба сконфігурувати на 8-байтовий формат (про це я писав в минулому пості).
Читання - задача цікавіша. Згадую фразу одного свого викладача "Складних задач не буває. Є задачі легкі і цікаві!". Отже мені попалася задача десь на рівні між цікавою та легкою. Витягування даних за певний період з трендів проводиться за назвою трендового тегу, і як я вже казав робиться через  TrnGetTable:
       TrnGetTable(Tag, Time, Period, Length, Table, DisplayMode, Milliseconds [, sClusterName] )
Тут я трохи запарився, поки розколупав як в дійсності вона(функція) працює. Хоч усі нюанси так і залишилися за кадром (нІколи зара). Параметр Tag вказує на ім'я трендового тегу.
Time – вже вносить плутаницю. Цитую
 "The end time and date (long integer) of the desired trend section. Once you have entered the end time and date (Time), period (Period), and number of trend tag values collected (Length), the start time and date will be calculated automatically. For example, if Time = StrToDate("18/12/96") + StrToTime("09:00"), Period = 30, and Length = 60, the start time would be 08:30. In other words, the trend values for the period between 8.30am and 9am (on December 18, 1996) would be tabulated.
If this argument is set to 0 (zero), the time used will be the current time"
"end time and date" значить, що це сама свіжа дата в отриманих даних. Іншими словами, витягувати ці дані будуть з цієї дати/часу вглиб архіву. 0 – це типу "від зараз". Глибина ж архіву задається параметрами Period (задається в секундах) та Length (кількість точок виборки). Тобто діапазон даних буде  Period * Length з дискретністю Period. Дані будуть розміщені в Table, яка попередньо створюється як масив REALів. Чомусь сайтект не дає цей масив робити всередині функції, його треба виносити за межі функції. Не розбирався чому, якщо чесно. Так от, цей масив бажано заздалегідь зробити більше ніж кількість даних, що очікуються.  
DisplayMode – до сих пір під напівмраком. Він задає, як ці точки, що витягуються будуть себе вести при інвалідних даних, в якому порядку будуть слідувати в масиві (нові чи старі з 0-го елементу),  Condense та  Stretch методи (тут у мене не в усьому віра в свої догадки, тому тільки пару слів нижче), Gap Fill – тут у мене повний прольот. Condense на скільки я зрозумів задає як будуть усереднюватися дані в міждіапазонні. Stretch  що робити з точками, якщо вони разом попадають в діапазон (тут я все придумав сам, тому віри мені в цьому місці немає самому собі).
Усі інші параметри мене вже не цікавили, тому що імя кластера це стандартно а Milliseconds навіть не вчитувався.
Розбір роботи функції (хоть я вже її юзав на своїх курсах, але то ж для демки, а тут проект) я робив з використанням девайса текстового файлу, куди і вів лог всього що твориться в сайкоді. Вирішив надалі робити тонку відладку сайкода саме таким чином.
Кінцева сира реалізація, яка наче працює базується на наступних принципах:

REAL TableVAL[3600];
FUNCTION GetLastTrendData (STRING TagName, STRING TagWrName, INT Period, INT CountRec, INT EndTime)
    INT TimeREP, i;
    REAL VALold, VALnew;
    INT iold, inew, CountRec1;
    STRING Msg;
    //TrnGetTable(Tag, Time, Period, Length, Table, DisplayMode, Milliseconds [, sClusterName] )
    VALnew = 0;
    CountRec1 = TrnGetTable(TagName,EndTime, 60, 60, TableVAL[0], 12 + 2 + 256,0);
    //найбільший запис за годину буде останнім значенням 
    FOR i=0 TO CountRec1-1 DO
        IF TableVAL[i]>0 AND TableVAL[i]>VALnew THEN
         VALnew = TableVAL[i]; inew=i;
        END
    END

    //шукамо ненульову точку з мінімальним значенням в записах за період
    CountRec1=TrnGetTable(TagName,EndTime,Period, CountRec, TableVAL[0], 12 + 256,0);
    VALold = VALnew;
    FOR i=0 TO CountRec1-1 DO
        IF TableVAL[i] < VALold AND TableVAL[i]>0  THEN
            VALold=TableVAL[i]; iold=i;
        END;
    END;

    //уточнюємо в околі точки вниз з глибиною в 2 періоди, з точністю до хвилини
    CountRec1=TrnGetTable(TagName,EndTime - iold*Period, 60, Period*2/60, TableVAL[0], 12 + 256,0);
    FOR i=0 TO CountRec1-1 DO
        IF TableVAL[i] < VALold AND TableVAL[i]>0  THEN
            VALold=TableVAL[i]; iold=i;
        END;
    END;

    TagWrite(TagWrName , VALnew - VALold);
END


- шукаємо останню архівну точку в околі стартової (глибина – година: 60 точок по 60 секунд кожна); оскільки стартова в даній постановці задачі завжди "зараз" - в околі є якісь дані, на це і розраховую; нас цікавить максимум;
-  далі шукаємо мінімальне значення в записах, але ненульове; вважаємо, що спонтанних значень там немає, інакше логіку треба перебудовувати повністю (колись і таке витворяв на інтеграксорі); чому ми не беремо просто останні точки? тому що їх там просто може не бути: ну, наприклад, СКАДА тоді була в ауті, або лічильник; тому ми шукаємо "живі" значення
- однак при місячних пошуках періодичність не може бути малою, наприклад година (кількість буде 24*30); тому в області найменшого старого значення за місяць, визначеного з точністю до години, шукаємо найменше з точністю, визначеною до хвилини (думаю тут можна було би погратися з стречами та конденсами функції, але мені було ліньки).
Далі за допомогою івентів ця функція запускається з необхідною періодичністю. Думаю, тут достатньо раз в хвилину викликати. І далі юзати ці змінні як кі-пі-айні.  
Вот приблизно так.      

Комментариев нет:

Отправить комментарий