На нефтебазе, использующей SCADA Schneider Vijeo Designer (версии 6.2.1), установили уровнемеры системы СТРУНА+. (а точнее, поменяли). Была задача обеспечить сбор данных с новых уровнемеров в имеющуюся SCADA.
Новые уровнемеры используют протокол “Modbus STRUNA+” (перепиленный Modbus). Также, изначально для проверки связи был использован устаревший, но более простой, протокол “Кедр”.
1 ЧАСТЬ – Создание подключения для приема-передачи данных по протоколу Modbus в Vijeo Designer
Создаем драйвер ввода-вывода:
IO Manager => New Driver => General Drivers => Script Driver
Настраиваем под Modbus:
Создаем скрипт для приема-передачи данных через созданный драйвер (в Vijeo Designer используются скрипты на какой-то старой Java):
Actions => New Action
, указываем периодичность (у нас опрос был большим, поэтому ставили 1 мин.)Открываем созданный Action => жмакаем
New Script
Запись данных:
1 2 3 4 5 6 7 8 9 10 |
byte[] sendData = new byte[lenToSend]; // инициализация данных sendData[indexID] = strunaID; // ... // формирование CRC // ... sendData[lenToSend - 2] = (byte)(crc); sendData[lenToSend - 1] = (byte)(crc >> 8); // передача StrunaModbusPlus.write(true, lenToSend, sendData); |
Чтение данных:
1 2 3 4 5 6 7 |
byte[] dataReceive = new byte[1024]; // прием int bytesReceived = StrunaModbusPlus.read(timeout, 100, dataReceive); // проверка if (bytesReceived <= 0) { // ... } |
2 ЧАСТЬ – Проблема верной интерпретации байтов вещественных чисел
Беда оказалась с правильным преобразованием принятых 4 байт данных в вещественное число.
Первоначально, для проверки работы скрипта и драйвера использовался протокол Кедр. Там все просто:
* Первые 12 бит – целая часть числа
* Остальные 4 бита – дробная часть в виде целого числа (<=9)
1 2 3 4 5 6 7 8 9 10 11 |
int b1 = (int)dataReceive[1] & 0xFF; int b2 = (int)dataReceive[2] & 0xFF; int b3 = (int)dataReceive[3] & 0xFF; // формируем целую часть int p1 = ((((b3 & 0xF0) >> 4) << 16) | (b2 << 8) | b1); // берем дробную часть byte p2 = (byte)(b3 & 0x0F); // надо БОЛЬШЕ приведений типов! float res = (float)p1 + (float)(p2)/10; // запихиваем в REAL REAL01.write(res); |
Но с протоколом Modbus все иначе. Формат передачи данных вещественных чисел другой, уже с плавающей запятой стандарта IEEE 754.
Тут 4-х байтовое число, пришедшее по Modbus, состоит из следующих частей:
* знак (указывает на отрицательность или положительность числа),
* мантисса (выражает значение числа без учёта порядка),
* порядок (выражает степень основания числа, на которое умножается мантисса).
В Java есть готовый метод преобразования бит в переменную типа float – Float.intBitsToFloat(int), но т.к. в скриптах подключение библиотек не предусмотрено, то код был тупо скопирован реализован смостоятельно.
1 2 3 4 5 6 7 8 |
int bits = ((b3 << 24) | (b4 << 16) | (b1 << 8) | b2); int s = ((bits >> 31) == 0) ? 1 : -1; // знак int e = ((bits >> 23) & 0xff); // порядок int m = (e == 0) ? // мантисса (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000; float res = s * m * (float)Math.pow(2, e-150); REAL01.write(res); |
UPDATE:
На хабре вышла статья, в которой приводится история о разработчике, который запихнул вышеприведенные вычисления в виде схемы XML DDF (XML, Карл!!!).