Наш канал в telegram

Парсер архивных файлов для Vijeo Citect от Schneider Electric

По работе возникла задача выгрузить по нескольким тегам данные из SCADA-системы Vijeo Citect. Выгружать нужно было мгновенные значения с интервалом в 5 секунд за достаточно большой промежуток времени (чуть меньше года). Данные у нас архивируются с интервалом в 1 секунду, так что в этом смысле всё нормально, однако выгрузка прямо из скады, в лоб, большого количества данных невозможна (за раз при заданном интервале можно выгрузить значения примерно за неделю). Соответственно, встала задача как-то облегчить процесс.

О том, что данные в Vijeo Citect хранятся в папке Data (в файлах имя_тега.000, имя_тега.001, имя_тега.002 и так далее) было известно давно, но теперь я решил разобраться с форматом этих файлов, а также написать парсер, позволяющий извлекать хранящиеся там данные вообще не заходя в скаду.

В итоге выяснилось следующее:

  • Каждый файл начинается с заголовка, размером 0x110 байт
  • По адресам 0x22-0x27 лежит имя тега
  • По адресам 0x2A-0x41 лежит время начала архива (локальное время, поскольку новый файл всегда начинается в 00:00 UTC)
  • По адресам 0x70..0x7F лежат шкалы в формате float (по 4 байта, little endian). 0x70-0x073 — начало шкалы АЦП из Unity, 0x74-0x77 — конец шкалы АЦП из Unity, 0x78-0x7B — начало шкалы в инженерных единицах, 0x7C-0x7F — конец шкалы в инженерных единицах.
  • По адресам, начиная с 0x110 и до самого конца лежат сами архивные данные (по два байта задом-наперёд значений типа int). Чтобы перевести записанные здесь целые значения в инженерные единицы — нужно воспользоваться следующей формулой:
    Result = ScaleSTART + (INT/32000)*(ScaleEND-ScaleSTART), где
    ScaleSTART — начальная шкала в инженерных единицах (float по адресам 0x78-0x7B)
    ScaleEND — начальная шкала в инженерных единицах (float по адресам 0x7C-0x7F)
    INT — прочитанное целое значение
    откуда взялись 32000 — не знаю, но это именно так (не 32768, как можно было бы предположить)
  • Максимальное количество данных в файле — 604800 значений (604 800*2=1 209 600 байт)
  • Для указания недостоверных данных используется число -32001 (0x82 0xFF)
Файл архива в HEX-редакторе

файл архива в HEX-редакторе

[свернуть]

По результатам проведённых исследований на С++ Builder был написан парсер, позволяющий в один клик выгружать в csv из указанного файла архива мгновенные значения или формировать и выгружать часовые отчёты (интеграл за час). Последнее может быть актуально для трендов расхода (данные в архиве должны измеряться в единицах относительно часа: кг/ч, т/ч, м3/ч).

Внешний вид парсера

внешний вид архива

[свернуть]

Ниже выкладываю сам парсер с исходниками (может кому пригодится).

Скачать парсер

Скачать исходники

Изначально планировалось в этом же парсере реализовать расчёт суточных интеграторов с привязкой к определённому часу, склейку всех архивов по одному тегу и некоторые другие плюшки, однако в итоге Virtual решил эти задачи, просто обработав мгновенные выгрузки парсера скриптами на баше. Естественно, сразу после этого, мне стало сильно лень доделывать все первоначальные задумки. Впрочем, это уже совсем другая история.

P.S. bash-скрипт для склейки файлов выгрузки:

нажмите, чтобы увидеть скрипт

#!/bin/bash
#
# скрипт для склеивания всех файлов из каталога input
# переберём все csv-шники, у каждого отрежем лишнее
# (первые 8 строк), а остальное склеим в один файл
 
if [ -d output ];			# проверяем наличие папки output
then					# если папка существует
    rm output/*				# удаляем в ней все файлы
else					# если папки нет
    mkdir output			# создаём папку output
fi
 
cd input		# переходим в папку input
dos2unix *.csv		# переделываем концы строк Windows в Unix
for i in *.csv		# перебираем все файлы *.csv, которые есть в каталоге input
    do
	echo -n ".";	# прогресс бар
	tail -n +9 $i>../output/$i;	# отрезаем от содержимого файла первые строки, а начиная с 9-й
					# и до окнца - выводим в файл с таким же именем в папке output
    done
cd ..					# возвращаемся в папку со скриптом
rm all_in_one.csv			# удаляем файл all_in_one.csv, если он уже был создан
cat output/*.csv> all_in_one.csv;	# склеиваем всё в один файл
rm output/*				# удаляем все файлы в папке output
rmdir output				# и удаляем саму папку output
 
echo "";
counter=`cat all_in_one.csv |grep '05:00:00' |wc -l`;	# считаем сколько раз в итоговом файле встречается
					# строка '05:00:00' и сохраняем результат в переменную counter
let "result = counter/2"		# делим пополам - получаем количество обработанных суток
echo "Количество суток в обработанных файлах: "$result;
echo "Well done!";

[свернуть]

P.P.S. Ещё раз обращаю внимание, что каждый новый файл архива начинает писаться в 00:00 по UTC, а время начала, которое записано в файле — это локальное время. В выгрузке указано локальное время того места, где формировался файл архива (это нужно учитывать, если вы обрабатываете архивы, находясь не в том часовом поясе, в котором они были сформированы).

Добавить комментарий