Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus...

71
© InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов Руководство пользователя

Transcript of Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus...

Page 1: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

© InSAT Company 2009-2012

Modbus Universal MasterOPC сервер

Реализация собственных протоколов

Руководство пользователя

Page 2: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 2

© InSAT Company 2009-2012

ОГЛАВЛЕНИЕ

Реализация собственных протоколов .......................................................................................... 3

1 Введение ................................................................................................................................. 3

2 Расширенный Modbus ........................................................................................................... 4

3 Поддержка протокола DCON .............................................................................................. 16

4 Поддержка протокола Rnet ................................................................................................ 24

5 Чтение архивов .................................................................................................................... 33

5.1 Автоматическая запись параметров ........................................................................... 33

5.2 Использование OPC HDA в MasterSCADA .................................................................... 34

5.3 Формирование архива при помощи скриптов. .......................................................... 37

5.4 Чтение архивов счетчика «Пульсар» ........................................................................... 44

5.5 Чтение файлов по Modbus протоколу ......................................................................... 54

5.6 Получение архива станции управления Электон-5 по функции 0x14. ..................... 58

6 Сопровождение разработанного кода .............................................................................. 65

7 Заключение .......................................................................................................................... 70

Page 3: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 3

© InSAT Company 2009-2012

Реализация собственных протоколов

1 Введение

При создании систем управления инженерам автоматизации приходится

сталкиваться с различными протоколами – как со стандартными (Modbus, DCON), так и с

протоколами – разработками фирм-производителей приборов.

Интеграция SCADA систем и приборов с открытыми протоколами, как правило, не

является сложной задачей – например, для протокола Modbus, существует большое

количество OPC серверов, многие SCADA системы поддерживают его на уровне

собственных драйверов. С нестандартными протоколами все может быть сложнее, так как

не все производители сопровождают свои приборы OPC серверами. В этом случае

инженеру автоматизации приходится самостоятельно решать задачу подключения к

SCADA системам.

Чтобы создать OPC с собственным протоколом, можно приобрести MasterOPC Toolkit

(© InSAT Company). В этом случае от разработчика не потребуется глубоких знаний

технологии OPC – потребуется лишь написать подключаемую библиотеку, реализующую

особенности протокола. Однако данный подход имеет недостатки. Во-первых, Toolkit

стоит недешево. Во-вторых, хотя разработка OPC на готовом Toolkit и проще, чем

создание OPC «с нуля», но его освоение потребует определенного количества времени.

Кроме того, библиотеку потребуется написать на языке высокого уровня C# (или С++),

который инженер автоматизации может и не знать на достаточном уровне. Таким

образом, использование MasterOPC Toolkit целесообразно при многотиражном

использовании, сложном протоколе или повышенных требований к быстродействию.

В остальных случаях удобнее использовать Modbus Universal MasterOPC Server

(далее MasterOPC) – универсальный OPC сервер, разработанный компанией ИнСАТ.

Помимо возможности опроса устройств по протоколу Modbus, он также имеет

встроенные инструменты разработки.

Основная функция данного OPC сервера – опрос регистров Modbus устройств.

Поддержаны HoldingRegisters (функция чтения 0х03, записи 0х06 и 0х10), InputRegisters

(функция чтения 0х04), Coils (функция чтения 0х01, записи 0х05 и 0х0F), DiscreteInputs

(функция чтения 0х02), а также чтение архивов по функции 0х14.

Кроме того, MasterOPC имеет возможность создания программ на простом

скриптовом языке Lua. Используя скрипты можно выполнять различные действия –

обрабатывать полученные по Modbus значения, манипулировать OPC признаками

качества, а также реализовать поддержку собственных протоколов – как основанных на

Modbus (поддержка расширенных функций), так и полностью самостоятельных.

В данной документации мы рассмотрим несколько примеров, в которых с помощью

скриптов будет реализована поддержка нескольких нестандартных протоколов.

Предполагается, что читатель знаком с Modbus Universal MasterOPC сервером, а также

Page 4: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 4

© InSAT Company 2009-2012

посмотрел видеопримеры по созданию конфигураций и скриптов для данного OPC

сервера.

Демонстрационную версию OPC сервера, с ограничением на 32 тега, а также

видеопримеры можно скачать со страницы загрузки

http://www.masteropc.ru/prices/info.php?pid=6944

Также желательно ознакомится с текстом стандарта протокола Modbus, который

можно скачать с официального сайта организации Modbus-IDA

http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf

2 Расширенный Modbus

С момента выпуска первой версии стандарта Modbus (1979 год) прошло немало

времени. За это время были выпущены новые спецификации протокола,

поддерживающие новые функции. Кроме того, поскольку Modbus является открытым

протоколом, многие фирмы расширяют стандарт собственными функциями. Например,

фирма ICP DAS использует собственную функцию 0х46 с несколькими подфункциями для

настроек модулей M-серии.

2.1 Примеры реализации

В качестве примера рассмотрим датчик температуры и влажности МАВ-ТС100

фирмы «Микрофор».

http://www.microfor.ru/products/catalog/mav_ts/

Данный датчик предназначен для измерения температуры и передачи этих данных

по протоколу Modbus. Считывание измеренного значения температуры осуществляется

при помощи функции 0х19. Данная функция не является стандартной, и не

поддерживается ни одним OPC сервером. Команда имеет следующий формат.

Посылка:

Назначение команды Посылаемое значение

номер преобразователя 0x01

идентификатор команды 0x19

адрес регистра, старший байт 0x02

адрес регистра, младший байт 0x00

контрольная сумма, младший байт Crc_lo

контрольная сумма, старший байт Crc_hi

Page 5: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 5

© InSAT Company 2009-2012

Ответ:

Назначение команды Принимаемое значение

номер преобразователя 0x01

идентификатор команды 0x19

содержимое регистра, старший байт 0x09

содержимое регистра, младший байт 0xF5

контрольная сумма, младший байт Crc_lo

контрольная сумма, старший байт Crc_hi

Для получения значения температуры необходимо опросить два регистра - 0x200 и

0x202. Из значений этих двух регистров и нужно получить значение температуры в

формате float. Считанные значения представляются как 32-х битное число, причем

старшие 16 бит считываются из регистра 0x200, а младшие 16 бит – из регистра 0x202.

Затем, полученное 32-х битное число преобразуется в число с плавающей запятой

следующим образом:

Разряд 31 (MSB) ...... 24 23 22 ..... 0 (LSB)

е.7 ...... е.0 Знак m.22 ..... m.0

Экспонента (от -126 до +127) Мантисса (от 0 до 222)

Данный способ представления вещественного числа соответствует стандарту IEEE

754, за исключением того, что бит знака находится на 23 позиции вместо 31. Такой тип

представления принят производителями микроконтроллеров – компаниями Texas

Instruments и Microchip.

Создадим конфигурацию для данного прибора. Добавим коммуникационный узел

типа «COM», назовем его «Порт», скорость установим 19200.

Page 6: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 6

© InSAT Company 2009-2012

Добавим в порт устройство, тип устройства «Modbus», назовем его «МАВ-ТС100»,

адрес установим равным 1, период опроса установим равным 1000 мс.

В устройство добавим тег – «Температура». Тип тегов (регион) – Server_Only

(программный тег), тип данных в сервере – float, тип доступа ReadOnly. Скрипт

включать пока не будем.

Page 7: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 7

© InSAT Company 2009-2012

Запрос к прибору мы будем осуществлять из скрипта расположенного в самом теге.

Выделим тег, разрешим выполнение скрипта, и откроем редактор скрипта.

Скрипт устройства содержит три функции:

OnInit () - код из этой функции вызывается при инициализации узла. То есть при

старте OPC сервера.

OnClose() – код из этой функции вызывается при закрытии узла, то есть при

остановке OPC сервера.

OnRead() – код из этой функции вызывается каждый раз перед опросом тега.

Page 8: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 8

© InSAT Company 2009-2012

Для отправки запроса в COM порт и получения ответа в OPC сервере есть

специальная функция server.SendAndReceiveDataByMask(). Для большего удобства

данная функция отправляет и получает данные заданные по маске.

Добавить функцию в код можно двойным щелчком в окне функций.

Нам необходимо опросить два регистра – 0x200 и 0х202. Чтобы избежать

дублирования кода, мы создадим собственную функцию, в которую будем передавать

номер запрашиваемого регистра. Функция будет возвращать два параметра – флаг

корректности запроса (истина или ложь) и вычисленное значение (0 если запрос не

корректен).

Приведем весь код функции (назовем ее Query), необходимый для отправки

Modbus запроса и получения ответа, а затем разберем его построчно.

Листинг 1.1 Функция Query – запрос чтения регистра датчика

function Query(num_reg)

local send={}; --массив отправляемых чисел

Page 9: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 9

© InSAT Company 2009-2012

local Addr=server.GetCurrentDeviceAddress();--получения адреса

устройства

table.insert(send,Addr); --добавляем в таблицу первый элемент - адрес

table.insert(send,0x19); --добавляем в таблицу второй элемент -

идентификатор команды

table.insert(send,num_reg); --добавляем третий элемент - номер регистра

local sendmask={"byte","byte","int16:10"}; --маска отправляемого запроса

local dest={}; --массив полученных чисел

local destmask={"byte","byte","int16:10"}; --маска принимаемого запроса

local err,len;

local n=0;

repeat

--посылка и получение запроса в устройство

err,dest,len=server.SendAndReceiveDataByMask(2,3,sendmask,send,destmask,20);

n=n+1;

--условие выхода - корректный ответ или превышение запросов

until err>=0 or n>=server.GetCurrentDeviceRetry()

--обрабатываем полученные данные

if err>=0 then

--запрос выполнен корректно

return true, dest[3]; --возвращаем флаг что запрос корректен и третий

элемент массива – значение

else

return false,0; --запрос некорректен, возвращаем соответствующий флаг

end;

end;

Итак, функция Query получает аргумент – номер регистра.

Начинаем формировать данные для запроса.

local send={} – в данной строчке происходит объявление локальной переменной

send и ее инициализация (инициализация происходит в фигурных скобках – в данном

случае создается пустая таблица). Данная переменная представляет собой таблицу

(массив), в которой будут содержаться числа для отправки в устройство.

local Addr=server.GetCurrentDeviceAddress()– в данной строчке происходит

объявление локальной переменной Addr. В данную переменную, при помощи

специальной функции (ее также можно добавить из окна функций), записывается

заданный адрес устройства.

table.insert(send,Addr) – в данной строчке происходит добавление элемента в

таблицу, при помощи функции table.insert(). В качестве аргумента в эту функцию нужно

передать таблицу, в которую нужно произвести вставку, и вставляемый элемент. Мы

добавляем первый элемент – адрес.

Примечание. Для работы с таблицами (массивами) используется раздел функций table – с их помощью можно добавлять, удалять, сортировать элементы в таблице.

В последующих строчках, мы добавляем в массив остальные элементы – номер

функции (0х19), номер регистра (переменная num_reg).

Page 10: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 10

© InSAT Company 2009-2012

table.insert(send,0x19); --добавляем в таблицу второй элемент -

идентификатор команды

table.insert(send, num_reg); --добавляем третий элемент - номер регистра.

Теперь мы сформировали массив send в котором содержаться элементы –

(0x01,0x19,0x200).

local sendmask={"byte","byte","int16:10"} – в данной строчке объявляется

структура маски запроса. Переменная sendmask – переменная типа таблица. При

инициализации, в нее добавляются строковые элементы, каждый из которых будет

описывать элемент отправляемой таблицы (в данном случае переменной send). Формат

маски – String1:String2, где String1 – формат данных, String2 – строка перестановки.

Переменные типа string и byte не требуют строки перестановки, для остальных форматов,

ecли строка перестановки не введена, используется "10325476". Рассмотрим каждый

элемент таблицы масок. Первые два элемента – типа byte, это соответственно элементы

«адрес» и «идентификатор команды». Третий элемент – типа int16 (двухбайтовое

знаковое целое), перестановка байт – старшим вперед. Этот элементы описывает

«номер регистра». Контрольная сумма ни в таблице send, ни в маске sendmask не

описана – так как мы будем использовать стандартную контрольную сумму Modbus. Если

контрольная сумма будет использоваться нестандартная – то ее элементы также нужно

добавить в маску.

Примечание. Подробнее про работу функции server.SendAndReceiveDataByMask с нестандартной контрольной суммой будет рассказано в одном из следующих примеров.

local dest={} – в данной строчке объявляется переменная-таблица для

принимаемых байт.

local destmask={"byte","byte","int16:10"} – в данной строчке объявляется

структура маски ответа. Поскольку прибор отвечаем «эхом», то маска ответа имеет такую

же структуру как маска запроса.

local err,len - в данной строчке объявляются переменные ошибки и длины

ответа.

И наконец, выполнение запроса:

err,dest,len=server.SendAndReceiveDataByMask(2,4,sendmask,send,destmask,20) – в

данной строчке происходит посылка данных в устройство и ожидание ответа.

Функция server.SendAndReceiveDataByMask, имеет 6 входных параметров. Разберем

их:

Параметр №1 – константа 2. Данный параметр определяет способ подсчета

контрольной суммы. Поддержаны контрольные суммы протокола Modbus и DCON.

Параметр установлен равный двум – это подсчет контрольной CRC16 (Modbus). Если

Page 11: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 11

© InSAT Company 2009-2012

установить параметр равным 0, то контрольная сумма не будет использоваться, и в этом

случае пользователь должен проверять ее корректность самостоятельно.

Параметр №2 – константа 4. Данный параметр задает количество элементов в

исходной таблице. В таблице send у нас находится 4 значения.

Параметр №3 – переменная sendmask. Данный параметр – это переменная маски

запроса.

Параметр №4 – переменная send. Данный параметр – таблица отправляемых

значений.

Параметр №5 – переменная destmask. Данный параметр - таблица масок байт

принимаемого ответа. Или nil (пусто) – если функция работает только на передачу.

Параметр №6 – константа 20. Максимальное количество принимаемых байт. Если

указать 0, то функция работает только на передачу.

Функция возвращает 5 значений.

Первый возвращаемый параметр (присваивается переменной err) – код ошибки.

Если код меньше нуля, то произошла ошибка. Расшифровка кодов ошибок есть в справке.

Второй возвращаемый параметр (присваивается dest) – таблица, каждый элемент

которой равен числу (или строке) сформированный согласно маске преобразований.

Третий элемент (присваивается len) – количество элементов в принятой таблице.

Четвертый элемент (в данном примере не используется) – строка ответа принятого

от устройства. Данный элемент нужен, если используется нестандартная контрольная

сумма, и необходимо произвести ее расчет вручную используя каждый байт ответа.

Пятый элемент (в данном примере не используется) – количество принятых от

устройства байт. Данный элемент также нужен для ручного расчета нестандартной

контрольной суммы.

Таким образом, в случае корректного выполнения запроса и получения ответа, в

переменной dest будет содержаться набор преобразованных по маске чисел, в

переменной len – количество принятых элементов.

Но если возникнет ошибка, то в переменной err будет отрицательное число - код

ошибки. В этом случае нам нужно попытаться выполнить запрос снова – например, на

линии возникла помеха и наш запрос был поврежден. Для этого упакуем вызов функции

server.SendAndReceiveDataByMask в цикл.

Для подобных случаев, наиболее удобным является цикл repeat – until, это цикл с

выходом по условию. Этот цикл всегда выполняется как минимум один раз (что нам и

нужно). Цикл имеет следующий синтаксис:

Page 12: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 12

© InSAT Company 2009-2012

repeat

--код цикла

until условие_выхода

Запрос нам нужно послать такое количество раз, какое задал пользователь у

устройства, в параметре «Повторы при ошибке», по умолчанию – 3.

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

воспользоваться функцией server.GetCurrentDeviceRetry() – данная функция возвращает

значение настройки у текущего устройства.

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

превышенное количество запросов. Для количества выполненных запросов заведем

специальную переменную n и инициализируем ее нулем, после каждого запроса мы

будем ее инкрементировать. Получим следующий код:

Листинг 1.2 Повтор запроса при ошибке

local n=0;

repeat

--посылка и получение запроса в устройство

err,dest,len=server.SendAndReceiveDataByMask(2,4,sendmask,send,destmask,20);

n=n+1;

--условие выхода - корректный ответ или превышение запросов

until err>=0 or n>=server.GetCurrentDeviceRetry()

После этого, если запрос был выполнен корректно, мы можем вернуть (используя

оператор return) флаг корректности запроса и полученное от прибора значение. Для

этого нужно вернуть третий элемент таблицы dest. Если запрос выполнить не удалось

(возникла ошибка), то возвращаем флаг ошибки и ноль, в качестве значения.

Листинг 1.3 Возврат значения

if err>=0 then

--запрос выполнен корректно

return true, dest[3]; --возвращаем флаг что запрос корректен и третий

элемент массива – значение

else

return false,0; --запрос некорректен, возвращаем соответствующий флаг

end;

Page 13: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 13

© InSAT Company 2009-2012

Теперь мы можем вызывать функцию Query из основной функции OnRead() и

обрабатывать полученные значения.

Листинг 1.4 Код функции OnRead

function OnRead()

noerr,RegH=Query(0x200); --выполняем запрос чтения регистра 200

if noerr==false then --если ответ некорректный

--записываем сообщение в лог и записываем в тег плохой признак качества

server.Message("Ошибка получения данных");

server.WriteCurrentTag(0,OPC_QUALITY_BAD );

return;

end;

noerr,RegL=Query(0x202); --выполняем запрос чтения регистра 202

if noerr==false then

server.Message("Ошибка получения данных");

server.WriteCurrentTag(0,OPC_QUALITY_BAD );

return;

end;

local F=ConvertToFloat(RegH,RegL);

server.WriteCurrentTag(F,OPC_QUALITY_GOOD );

end

В первой строчке кода, мы вызываем написанную нами функцию Query, и

передаем ей в качестве аргумента константу – номер регистра 0x200. После выполнения

функции, нам необходимо проверить – корректно ли был выполнен запрос. Если запрос

был выполнен некорректно и переменная noerr равна false, то нам нужно записать

сообщение в журнал сервера и записать значение в тег значение с плохим признаком

качества, после чего выйти из функции при помощи оператора return.

Для записи сообщения в журнал используется функция server.Message() – в

качестве аргументов ей можно передавать любое количество аргументов (как строковых,

так и числовых), они будут преобразованы в строку и выданы в раздел журнала

«Сообщения от скриптов», а также будут выведены в файл-лог. На рисунке пример

записи в журнал значения переменной.

Для записи значения в текущий тег мы используем функцию

server.WriteCurrentTag(). В качестве первого аргумента ей нужно передать значение тега,

а вторым – его признак качества. Если наш запрос был выполнен некорректно, то мы

запишем признак качества Bad («Плохой»). Константу признака качества можно добавить

из дерева функций – из раздела Константы – OPC.

Затем аналогичные операции мы проделываем с регистром 0x202.

Page 14: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 14

© InSAT Company 2009-2012

Если оба запроса были выполнены корректно, то мы получим две переменных RegH

и RegL, из которых можно получить 32-битное число, для последующего преобразования

в вещественное число. Для преобразования этих регистров во float число, напишем

специальную функцию ConvertToFloat, в которую передадим исходные регистры.

Чтобы получить вещественное число нам необходимо:

1) Собрать из двух чисел int16 одно число с 32 битами

2) Получить знак числа (s) – для этого нужно получить состояние 23 бита

3) Получить экспоненту (e) – биты с 24 по 31

4) Получить мантиссу (M) – биты с 0 по 22

Для того чтобы из сформированных элементов получить вещественное число,

можно воспользоваться следующей формулой:

𝐹 = (−1)𝑠 ∙ 2𝑒−127 ∙ (1 +𝑀

223)

Рассмотрим код функции выполняющий данные действия.

Листинг 1.5 Функция ConvertToFloat - преобразования двух регистров во Float

function ConvertToFloat(RegH,RegL)

local val=bit.BitLshift(RegH,16);--побитовый сдвиг влево

val=bit.BitOr(val,RegL); --побитовое или - заполнение младших бит

local znak=bit.BitFromData(val,23); --получения знака числа

local exp=bit.BitRshift(val,24 ); --получение экспоненты сдвигом вправо

local mant=bit.BitAnd(val,8388607 ); --получение мантиссы

local F=2^(exp-127)*(1+mant/2^23);--получения искомого числа

if znak==true then F=F*(-1); end;

return F;

end;

В первой строчке кода, мы производим сдвиг старшего регистра на 16 битов влево,

при помощи функции bit.BitLshift(). При этом младшие биты переменной val заполняются

нулями. Теперь можно разместить в младших битах значения битов из младшего регистра

RegL – это делается при помощи функции bit.BitOr() («Побитовое ИЛИ»).

В результате мы сформировали 32-битное значение, которое сохранили в

переменную val. Теперь можно работать с отдельными его битами. Сначала получаем

знак – для этого, при помощи функции bit.BitFromData(), получаем 23 бит числа и

сохраняем его в переменную znak.

После этого получаем значение экспоненты. Экспонента находится в битах 24-31,

поэтому, чтобы получить ее значение, достаточно просто выполнить сдвиг регистра на 24

бита вправо (старшие биты заполняться нулями) – для этого используем функцию

bit.BitRshift(), и сохраняем результат в переменную exp.

Чтобы получить значение мантиссы, нам нужно получить значение из битов 0-22.

Для этого достаточно просто заполнить нулями биты 23-31. Это можно сделать выполнив

операцию «Побитовое И» между исходным числом и числом, у которого биты с 0 по 22

Page 15: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 15

© InSAT Company 2009-2012

забиты единицами, а биты 23-31 – нулями. В десятичном видео это число 8388607.

Операцию «побитовое И» можно выполнить используя функцию bit.BitAnd(). Результат

сохраним в переменную mant.

Теперь мы получили все необходимые нам элементы формулы и можем найти

значение вещественного числа. Результат сохраним в переменную F. После этого, если

знак числа – отрицательный, то умножим наш результат на «минус единицу». Результат

(число F) – вернем в вызываемый код.

После того как мы получили вещественное число, его можно записать в тег. Для

этого мы будем использовать, уже знакомую нам функцию server.WriteCurrentTag(). Но

теперь в качестве аргументов мы передадим ей вычисленное значение и хороший

признак качества.

Код готов.

2.2 Обработка ошибок.

Наш код работает и способен опрашивать датчик. Однако мы никак не позаботились

об обработке ошибок. Мы реализовали повтор запроса на случай некорректного ответа,

но возможен ли случай когда ответ корректный (количество байт, контрольная сумма), но

при этом он неправильный? Да, возможен.

Например, представим себе следующую ситуацию. На одной шине Modbus у нас

подключены несколько датчиков влажности МАВ-ТС100 и несколько модулей ввода,

которые например, нужно опрашивать функцией 0х03. При создании конфигурации OPC

сервера мы случайно перепутали адреса, и указали у датчика МАВ-ТС100 адрес модуля

ввода – теперь OPC сервер начнет посылать запросы функцией 0х19 модулю, который

данную функцию не поддерживает.

Что в этом случае ответит устройство? В стандарте Modbus, на этот случай прописан

ответ содержащий функцию ошибки – 0x80 + номер функции, то есть в данном случае код

ошибки будет равен 0x99. Данная функция также имеет вспомогательные коды, по

которым можно определить тип ошибки. Например, код 0х01 – это некорректная

функция, 0х02 – некорректный адрес регистра и т.д. (полный список кодов можно

прочитать в описании к стандарту).

Таким образом, наше устройство должно ответить (допустим, устройство имеет

адрес 1):

01 99 01 crc_hi crc_lo

Данную ситуацию также можно обработать в коде – например, можно просто

проверить второй элемент таблицы. Если он равен 0x19, то считать, что запрос

выполнен корректно, иначе – записать сообщение в журнал и вернуть в код флаг

недостоверности.

Page 16: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 16

© InSAT Company 2009-2012

Код будет выглядеть следующим образом:

Листинг 1.6 Обработка ошибки устройства

if err>=0 then

--запрос выполнен корректно

if dest[2]==0x19 then

return true,dest[3]; --возвращаем флаг что запрос корректен

else

server.Message("Неизвестная функция");--пишем сообщение в лог

return false,0; --запрос некорректен

end;

else

return false,0; --запрос некорректен, возвращаем соответствующий флаг

end;

В этой ситуации, также можно возвращать флаг ошибки, и в основном коде

записывать в тег признак качества «Ошибка устройства» (Device Failure).

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/Микрофор.mbc

3 Поддержка протокола DCON

Протокол DCON является простым, открытым, символьным протоколом обмена по сетям

RS-232, RS-422 и RS-485. Протокол был разработан фирмой Advantec и в настоящий

момент активно применяется в устройствах многих производителей (Advantec, ICP DAS,

ОВЕН, Контравт).

К недостаткам протоколам можно отнести отсутствие единого стандарта (подобного

стандарту Modbus), поэтому реализация одних и тех же функций у разный

производителей может отличаться.

Структура запроса у протокола DCON выглядит следующим образом:

Команда

(1 символ)

Адрес

(2 символа)

Данные для

передачи

(могут

отсутствовать)

Контрольная

сумма

(2 символа)

Символ CR

(«перевод

каретки»)

Команда – это определенный символ, который указывает модулю на тип запроса.

Например, символ «#» указывает что выполняется запрос чтения аналогового входа или

значения счетчика, символ «@» указывает что выполняется запрос состояния дискретных

входов. Типы и предназначение команд может отличаться у разных производителей.

Адрес – адрес устройства на шине к которому выполняется запрос. Адреса могут быть от

«00» до «FF». Адрес 0 – широковещательный.

Page 17: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 17

© InSAT Company 2009-2012

Данные для передачи – предназначены для передачи данных в устройство (например

включение выхода) или, например, для указания номера аналогового ввода при

поканальном считывании. Данный элемент запроса может отсутствовать – например, при

считывании всех каналов аналогового модуля ввода или при считывании маски состояния

дискретных входов.

Контрольная сумма – два символа, подсчитанных на основе значений всех символом

запроса, за исключением символа «возврат каретки».

Символ CR («возврат каретки») – флаг окончания кадра запроса/передачи.

Структура ответа у протокола DCON выглядит следующим образом:

Команда

(1 символ) Данные

Контрольная сумма

(2 символа)

Символ CR («перевод

каретки»)

Команда – содержит символ корректности ответа. Например, если запрос к модулю

аналогового ввода (команда «#») прошел успешно, то будет возвращен символ «>». Если

же возникла ошибка (некорректный номер канала, неподдерживаемая функция), то

прибор вернет символ «?». Типы и предназначение символов корректности ответа у

разных производителей могут отличаться.

Данные – полученные от прибора данные. Структура данных может отличаться у разных

производителей.

В качестве примера мы рассмотрим поддержку 8-канального модуля аналогового ввода

МВ110-8АС, фирмы ОВЕН. Описание устройства можно найти на официальном сайте

производителя:

http://www.owen.ru/catalog/25995284

Мы будем производить считывание показаний всех входов. Для этого применяется

команда «#». Пример посылки запроса выглядит следующим образом:

#AA[CHK](cr)

Где, АА – адрес модуля, [CHK] – контрольная сумма.

Если запрос будет выполнен корректно, то прибор вернет данные в следующем формате:

>(данные)[CHK](cr)

Пример такого ответа будет выглядеть так:

>+100.23+34.050+124.56+07.331-101.45+1038.9-50.501+05.880[CHK](cr).

Page 18: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 18

© InSAT Company 2009-2012

Как мы видим вначале идет символ «>», который сообщает что запрос был выполнен

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

Каждый блок – это измеренное значение в канале модуля. Блок содержит символ

отрицательного и положительного числа. Затем следует измеренное значение в виде

числа с плавающей запятой. Положение разделительной точки меняется в зависимости

от результата измерения.

Если в каком-либо из каналов была ошибка измерения (например обрыв датчика), то

прибор возвращает значение «-999.90».

После блоков с данными следует контрольная сумма и перевод каретки.

Аналогичная структура запроса и ответа используется и во множестве других модулей

аналогового ввода – ОВЕН МВА, ICP DAS I-7017, I-7018 и других.

Реализуем опрос данного модуля в нашем OPC сервере.

Создадим новую конфигурацию, добавим в нее узел типа «COM», зададим скорость и

номер порта.

Добавим устройство. Тип устройства зададим «PROGRAM», также зададим имя,

комментарий и адрес (наш модуль имеет адрес 16).

Page 19: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 19

© InSAT Company 2009-2012

Добавим в устройство теги. Имя зададим «Вход», тип данных – «Float», тип доступа

«Read Only». Включим тиражирование – создадим 8 копий.

В результате у нас получилась конфигурация как на рисунке.

Page 20: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 20

© InSAT Company 2009-2012

Поскольку мы будем производить считывание сразу всех каналов, то разместим скрипт

на уровне устройства – включим у устройства настройку «Выполнение скрипта» и

откроем редактор.

Разместим в функции OnBeforeReading() следующий код, а затем разберем его построчно.

Листинг 1.7 Пример реализации протокола DCON

-- функция,выполняющаяся перед чтением тегов

function OnBeforeReading()

local Addr=server.GetCurrentDeviceAddress(); --считываем адрес устройства

local send={}; --инициализируем таблицу запроса

send[1] = "#"; --оператор типа запроса

send[2] = string.format ("%02X",Addr); --преобразование адреса

--к нужному виду

send[3]="\r"; --добавление символа перевода каретки

local sendmask={"string","string","string"}; --маска запроса

local dest={}; --инициализация таблицы принятых значений

local destmask={"string:1","sdouble:8:7"}; --маска ответа

--дополнительные переменные: ошибка, количество элементов в таблице dest,

--количество принятых байт

local err,len; --объявление переменных - флаг ошибки

--и количество принятых элементов

local n=0; --количество попыток запроса

--запрос к устройству в цикле

repeat

--функция опроса устройства

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(send),sendmask,

send,destmask,200);

--если ответ получен и первый символ - признак корректного ответа (">")

if err>=0 and dest[1]==">" then

--то начинаем записывать значения из принятой таблицы в теги

for i=2,len,1 do

local NameTag="Вход"..(i-1);

if dest[i]>(-999) then

--значение корректно - записываем его в тег

server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD);

else

--если принятое значение менее -999, значит произошел обрыв

--датчика - записываем плохой признак в тег

local val=server.ReadTagByRelativeName(NameTag);

server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_SENSOR_FAILURE);

end;

end;

end;

--счетчик запросов

n=n+1;

--условие выхода из цикла - корректный ответ или превышение количества

--повторов запроса (задается в свойствах устройства)

until err>=0 or n>=server.GetCurrentDeviceRetry( );

--если корректный ответ так и не был принят

if err<0 or dest[1]~=">" then

--то записываем во все теги плохой признак качества

for i=1,8,1 do

local NameTag="Вход"..(i);

local val=server.ReadTagByRelativeName(NameTag);

server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_BAD);

end;

end;

Page 21: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 21

© InSAT Company 2009-2012

В первой строке уже знакомая нам функция для получения адреса устройства. Затем мы

инициализируем таблицу send – таблицу с данными для запроса.

local Addr=server.GetCurrentDeviceAddress(); --считываем адрес устройства

local send={}; --инициализируем таблицу запроса

После этого, мы начинаем заполнять таблицу send данными.

send[1] = "#"; --оператор типа запроса

send[2] = string.format ("%02X",Addr); --преобразование адреса к нужному

виду

send[3]="\r"; --добавление символа перевода каретки

Первый элемент таблицы – символ команды запроса («#» - чтение аналоговых

значений).

Второй элемент таблицы – это адрес модуля. Как мы писали в начале, протокол DCON

является символьным протоколом – то есть данные в нем передаются в виде символов.

Поэтому нам необходимо преобразовать номер модуля в строку. При этом, согласно

стандарту, адрес всегда должен составлять два символа, т.е. если устройство например

имеет адрес 1, то устройству нужно передать два символа «0» и «1».

Для преобразования в строку используется стандартная Lua функция string.format. Данная

функция является аналогом С-функции printf:

http://www.cplusplus.com/reference/cstdio/printf/

Первый аргумент функции – это строка формата. В данном случае строка формата

имеет вид «%02X». Первый символ («%») – это признак начала управляющей

последовательности, т.е. символ указывает что c данного символа начинается строка

форматирования. Второй символ («0») является флагом, и означает что строку нужно

дополнить слева символами «ноль», если размер строки меньше указанного в поле

ширина (следующий символ). Третий символ («2») указывает что ширина поля должна

как минимум 2 символа. Четвертый символ («X») – спецификатор, он определяет способ

интерпретации преобразуемого элемента. В данном случае X – указывает, что

необходимо произвести преобразование числа в шестнадцатеричный формат.

Таким образом, функция выполнит преобразование адреса – преобразует в

шестнадцатеричный формат, и дополнит слева нулем если адрес состоит из одного

символа, после чего преобразует его в строковый тип.

Третий элемент таблицы – символ перевода каретки. На языке Lua он обозначается как

«\r».

А как же контрольная сумма? Контрольная сумма будет автоматически вычисляться в

функции server.SendAndReceiveDataByMask(), и подставляться перед символом перевода

каретки.

Page 22: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 22

© InSAT Company 2009-2012

Затем мы производим инициализацию таблиц масок для запроса и ответа, и

инициализацию маски принимаемых значений.

local sendmask={"string","string","string"}; --маска запроса

local dest={}; --инициализация таблицы принятых значений

local destmask={"string:1","sdouble:8:7"}; --маска ответа

Маска запроса sendmask состоит из трех элементов типа «string».

Подробнее остановимся на маске приема destmask. Как мы писали ранее ответ состоит из

одного символа достоверности и 8 блоков по 7 символов, каждый блок – вещественное

число. Для преобразования получаемых чисел указываем маску «sdouble:8:7». Первый

элемент этой маски – тип преобразования, данный тип предназначен для преобразования

строки в вещественное число. Строка может содержать цифры и символы «+», «-», «.».

Второй элемент маски (цифра «8») – количество принимаемых элементов. Третий

элемент маски (цифра «7») – количество байт.

Контрольная сумма и перевод каретки при выполнении функции

server.SendAndReceiveDataByMask() в таблицу принятых элементов не попадает, поэтому

описывать их в маске не нужно.

Теперь можно приступить к выполнению запроса. Как и в предыдущем примере мы

будем выполнять запрос в цикле repeat. Но предварительно создадим переменные для

флага ошибки, количества принятых элементов, а также переменную для счетчика

запросов.

--дополнительные переменные: ошибка, количество элементов в таблице dest,

--количество принятых байт

local err,len; --объявление переменных - флаг ошибки,количество принятых

элементов

local n=0; --количество попыток запроса

--запрос к устройству в цикле

repeat

--функция опроса устройства

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(send),sendmask,

send,destmask,200);

Непосредственно сам запрос к устройству выполняется в строке:

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(send),sendmask,

send,destmask,200);

Функция знакома нам по предыдущем примеру. Единственное отличие – первый

параметр (способ вычисления контрольной суммы). В прошлом примере он был равен

двум (мы вычисляли контрольную сумму Modbus), в текущем примере мы вычисляем

контрольную сумму DCON, поэтому здесь номер равен единице.

После того как запрос был выполнен можно приступать к проверке его достоверности.

Убеждаемся, что флаг err больше нуля (отрицательное число говорит, что ответ от

Page 23: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 23

© InSAT Company 2009-2012

устройства не был получен), и проверяем что первый элемент в таблице dest равен

символу «>» - то есть проверяем, что получен достоверный ответ.

Если ответ получен достоверный, то можно приступать к записи значений в теги. Когда

мы создавали теги, мы специально задали им возрастающие имена («Вход1», «Вход2»…

«Вход8»), теперь это позволит нам записывать значения в теги используя цикл.

for i=2,len,1 do

local NameTag="Вход"..(i-1);

if dest[i]>(-999) then

--значение корректно - записываем его в тег

server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD);

else

--если принятое значение менее -999, значит произошел обрыв датчика -

--записываем плохой признак в тег

local val=server.ReadTagByRelativeName(NameTag);

server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_SENSOR_FAILURE);

end;

Цикл мы начинаем с номера 2, шаг приращения – 1, продолжаем до достижения

переменной i значения количества принятых элементов (переменная len).

Переменная NameTag хранит имя тега, в который будет производится запись. Наш тег

состоит из статичной части («Вход») и номера от 1 до 8. Для их объединения нужно

использовать операцию конкатенации – операцию сложения строк. На языке Lua она

имеет вид двух точек (..).

Даже если получен достоверный ответ, один или несколько каналов могут передать

ошибку измерения. В этом случае, как мы писали ранее, модуль передает число -999.90.

Введем проверку этой ситуации. Если значение более -999, то можно производить запись

значения в тег с хорошим признаком качества. Это делается в строке:

server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD);

В противном случае нам нужно записать в тег признак качества «Ошибка датчика». При

этом лучше будет сохранить старое значение, которое было в теге. Поэтому сначала

считываем значение из тега, а затем запишем в него это же значение, но уже с плохим

признаком качества:

local val=server.ReadTagByRelativeName(NameTag);

server.WriteTagByRelativeName(NameTag,val, OPC_QUALITY_SENSOR_FAILURE);

Условие выхода из цикла опроса – корректный ответ или превышение количества

попыток запроса. Счетчик количества попыток инкрементируем каждый цикл опроса.

n=n+1;

--условие выхода из цикла - корректный ответ или превышение количества

повторов запроса (задается в свойствах устройства)

until err>=0 or n>=server.GetCurrentDeviceRetry( );

Page 24: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 24

© InSAT Company 2009-2012

Если все же ответ так и не будет принят, или был получен признак недостоверного ответа,

то производим в теги запись плохого признака качества.

if err<0 or dest[1]~=">" then

--то записываем во все теги плохой признак качества

for i=1,8,1 do

local NameTag="Вход"..(i);

local val=server.ReadTagByRelativeName(NameTag);

server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_BAD);

end;

end;

Итак, мы поддержали протокол DCON для данного модуля. Используя данный код как

шаблон можно поддержать и другие функции этого протокола.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/МВ110-8АС DCON.mbc. Кроме того, в поставляемой с OPC сервером конфигурации simulator.mbc есть пример работы с данным модулем, с дополнительной

поддержкой поканального считывания, а также реализация протокола для модуля дискретного ввода-вывода ОВЕН МДВВ.

4 Поддержка протокола Rnet

С помощью функции server.SendAndReceiveDataByMask() можно выполнять запросы

не только Modbus или DCON протоколу, но и реализовать собственные протоколы. Для

реализации собственного протокола необходимо самостоятельно производить подсчет

контрольной суммы.

Для примера рассмотрим поддержку протокола Rnet. Это протокол фирмы

«Контравт», предназначенный для опроса многоканальных измерителей-регуляторов.

Описание протокола можно найти на официальном сайте

http://www.contravt.nnov.ru/?id=1661

Кадр протокола состоит из следующих полей:

Название поля

Условн

ое

обозначение

Длина

поля (байт) Примечания

Поле сетевого

адреса прибора DEV 1 Сетевой номер прибора

Поле адреса

канала прибора CHA 1

Номер канал прибора.

Нумерация идет с нуля

Поле адреса

регистра REG 1 Регистр канала

Page 25: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 25

© InSAT Company 2009-2012

Поле команды CMD 1 00 – чтение регистра, 01 -

запись

Поле типа данных TYP 1 Тип регистра

Поле данных DATA 1..32 Данные

Поле

контрольной суммы CRC 1

Циклический код вычисляемый

по специальному алгоритму

Для чтения состояния регистра от OPC нужно послать следующий набор команд:

DEV CHA REG RD CRC

Если запрос будет принят, то прибор ответит:

DEV CHA REG RD TYP DATA CRC

Для примера будет опрашивать прибор «Метакон 562», первый канал, будем

опрашивать измеренное значение – оно имеет номер регистра 0x01.

Архитектура конфигурации будет следующая. В устройство будут добавлены

подустройства – для каждого канала. В каждом подустройстве будет находиться тег,

содержащий скрипт читающий соответствующий ему регистр. Номера регистров в

каждом канале одинаковые, поэтому мы сможем, создав теги с регистрами, просто

скопировать подустройства – изменив только их номера.

Создадим конфигурацию. Добавим узел типа «COM», в него добавим устройство,

назовем его «Метакон 562», тип «Program», в устройство добавим подустройство с

именем номера канала, а в него – тег «Вход».

Реализацию протокола начнем с написания функции подсчета контрольной суммы.

В описании протокола, есть пример ее реализации на языках С и Pascal. Ниже приведен ее

код переведенный на Lua:

Page 26: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 26

© InSAT Company 2009-2012

Не будем разбирать код этой функции – он лишь повторяет указанный

производителем алгоритм. Эту функцию мы расположим в устройстве – в этом случае мы

сможем вызывать ее из всех тегов устройства. В качестве аргумента в функцию нужно

передать кадр запроса, по которому нужно произвести подсчет контрольной суммы. Кадр

передается в функцию как массив, упакованный в строку – это связано с особенностями

реализации языка Lua, из-за которых передача таблиц во внешние функции невозможна.

Ниже будет описано, как обходить данное ограничение.

Нам необходимо вызывать данную функцию перед посылкой запроса в прибор,

считать контрольную сумму и добавлять полученный байт к запросу. При ответе, мы

должны убрать последний байт из запроса, посчитать контрольную сумму на оставшийся

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

продолжать обработку кадра.

Для упрощения данной задачи в функции server.SendAndReciveDataByMask есть

дополнительные параметры 7 и 8 – в данных параметрах нужно указать строковые имена

функций которые будут вызываться перед посылкой запроса (параметр 7) и после

получения ответа (параметр 8). В качестве аргумента данные функции должны принимать

таблицу байт. В коде можно сделать любую модификацию данной таблицы – убрать

лишние байты (например, если в конце есть символ окончания кадра), добавить байт

контрольной суммы и т.д. После выполнения обработки, функции должны возвращать

первым параметром – целое число, которое указывает или количество байт в таблице,

или, если число отрицательное - код ошибки. Данное значение будет передано

переменной err (первый выходной аргумент функции SendAndReciveDataByMask). При

этом не рекомендуется в качестве кода ошибки возвращать -1, так как это число

зарезервировано за отсутствием ответа от прибора. Вторым параметром функции должна

возвращаться измененная таблица байт. На эту таблицу байт будет наложена таблица

масок посылки/запроса.

Page 27: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 27

© InSAT Company 2009-2012

Начнем написание скрипта с того, с того что напишем данные функции – назовем их

SendCrc и GetCrc. Функции будут находится в коде скрипта тега – включим у тега «Вход»

выполнение скрипта, и напишем вне основных функций (например, в самом начале

скрипта):

Листинг 1.8 Код функций SendCrc и GetCRC

--функция вызываемая перед отправкой в прибор

function SendCrc(SendTable)

--вызываем функцию расчета контрольной суммы из устройства

Byte1=server.RunFunctionFromDevice("CRCsum",1,server.TableToString(SendTable)

);

table.insert(SendTable,Byte1);

--возвращаем количество байт и измененную таблицу

return table.maxn(SendTable),SendTable;

end

--функция вызываемая после приема ответа

function GetCrc(GetTable)

--вызываем функцию расчета контрольной суммы из устройства

GetByte1=table.remove(GetTable); --удаляем последний и сохраняем в GetByte

CalcByte1=server.RunFunctionFromDevice("CRCsum",1,server.TableToString(GetTab

le));

if GetByte1~=CalcByte1 then

return -10,GetTable; --возвращаем ошибку контрольной суммы

end

--возвращаем количество байт и измененную таблицу

return table.maxn(GetTable),GetTable;

end

Разберем код функции GetCrc. В первой строке мы убираем последний байт

таблицы с помощью функции table.remove, при этом происходит сохранение удаляемого

значения в переменную GetByte1.

Затем происходит вызов функции, находящейся в скрипте устройства – код функции

был приведен ранее. Для вызова внешних функций используются специальные функции,

в частности для вызова функции в устройстве - server.RunFunctionFromDevice(). В качестве

аргументов в функцию нужно передать:

1) Имя функции – в данном случае «CRCsum»

2) Количество параметров, которое возвращает функция – в данном случае

один (результат контрольной суммы)

3) Передаваемые параметры – в данном случае кадр (таблица) запроса.

Как мы говорили ранее, во внешние функции (то есть находящиеся в узле,

устройстве или подустройстве) нельзя передавать таблицы. Это ограничение можно

обойти если преобразовать таблицу в строку – для этого есть специальная функция

server.TableToString(). Именно результат этой функции передается 3 параметром. В

вызываемой внешней функции нужно будет сделать обратное преобразование – из

строки в таблицу – при помощи функции server.StringToTable().

Page 28: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 28

© InSAT Company 2009-2012

Затем мы сравниваем сохраненный байт GetByte1 и байтом высчитанным функцией

CRCSum. Если байты не совпадают – возвращаем код ошибки -10 (данный код закреплен

за ошибкой контрольной суммы) и таблицу. В противном случае с помощью функции

table.maxn подсчитываем количество элементов в таблице и возвращаем это значение,

вместе с самой таблицей. Обратите внимание, что таблица будет уже без байта

контрольной суммы, поэтому указывать его в таблице маски приема не нужно

(аналогично и функцией приема GetCrc).

Функция SendCrc работает аналогично – по байтам запроса считается контрольная

сумма, и результат добавляется в таблицу с помощью функции table.insert. Затем функция

возвращает количество байт в новой таблице и саму таблицу. Если по какой-то причине

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

параметром можно вернуть -1 – в таком случае запрос к устройству выполнен не будет.

Функции обработки буфера приема и отправки готовы, можно переходить к коду

скрипта – он будет располагаться в теле функции OnRead. Приведем код функции OnRead

для выполнения запроса к прибору полностью, а потом разберем его построчно.

Листинг 1.9 Запрос измеренного значения по протоколу RNet

function OnRead()

local Addr=server.GetCurrentDeviceAddress();

local send = {Addr,0,1,0}; --кадр запроса чтения без контрольной суммы

local sendmask={"byte","byte","byte","byte"}; --маска запроса

local dest={}; --таблица принятых значений по маске

local destmask={"byte","byte","byte","byte","byte","int16:1:01"}; --

маска ответа

--дополнительные переменные: ошибка, количество элементов в таблице

dest, количество принятых байт

local err,len;

local n=0; --количество попыток запроса

--выполняем запросы в цикле

repeat

--запрос к устройству

err,dest,len=server.SendAndReceiveDataByMask

(0,table.maxn(send), sendmask,send,destmask,200,"SendCrc","GetCrc");

if err==-1 then --нет ответа от устройства

break; --выходим из опроса

end

--проверим что количество ожидаемых элементов равно принятому

if err>0 and table.maxn(dest)==table.maxn(destmask) then

--проверяем что совпадает адрес прибора, номер канал и номер регистра

if dest[1]~=send[1] or dest[2]~=send[2] or dest[3]~=send[3] then

err=-2; --ошибка

end;

end;

n=n+1;

--выход из цикла - корректный прием или превышение количества попыток

until err>=0 or n>=3

if err<0 then

Page 29: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 29

© InSAT Company 2009-2012

--данные не приняты или данные некорректы. Устанавливаем плохой признак

качества

server.WriteCurrentTag(0,OPC_QUALITY_BAD); --

else

--приняты корректные данные. Записываем в тег принятое значение

server.WriteCurrentTag(dest[4],OPC_QUALITY_GOOD);

end;

end

Вначале:

local Addr=server.GetCurrentDeviceAddress();

local send = {Addr,0,1,0}; --кадр запроса чтения без контрольной суммы

Мы создаем таблицу send (эти данные мы будем отправлять в порт) и

инициализируем ее значениями. Первый элемент – адрес устройства; второй элемент –

канал (нумерация идет с нуля); третий элемент – регистр (1 регистр – измеренное

значение); четвертый элемент – тип запроса (0 – чтение).

local sendmask={"byte","byte","byte","byte","byte"}; --маска запроса

local dest={}; --таблица принятых значений по маске

local destmask={"byte","byte","byte","byte","byte","int16:1:01","byte"};

--маска ответа

В данных строках происходит инициализация таблиц – масок запроса и ответа.

err,dest,len=server.SendAndReceiveDataByMask

(0,table.maxn(send), sendmask,send,destmask,200,"SendCrc","GetCrc");

Затем происходит непосредственное выполнение запроса к устройству с помощью

функции sendAndReciveDataByMask, 6 и 7 аргументом указываются строковые(!) имена

функций SendCrc и GetCrc. Как и в предыдущем примере, запрос упакован в цикл.

--проверим что количество ожидаемых элементов равно принятому

if err>0 and table.maxn(dest)==table.maxn(destmask) then

--проверяем что совпадает адрес прибора, номер канал и номер регистра

if dest[1]~=send[1] or dest[2]~=send[2] or dest[3]~=send[3] then

err=-2; --ошибка

end;

end;

После получения ответа сначала нужно проверить переменную err на число -1, за

которым закреплено отсутствие ответа от прибора. При этом лучше сразу выйти из цикла

с помощью функции break, и не делать повторы запросов – дело в том, что если от

прибора не пришло ответа, то ОРС сервер сам делает нужное количество попыток опроса.

Если же данные получены нужно убедиться, что нет ошибок контрольной суммы –

число err больше нуля, а также сравнить что количество принятых элементов в таблицу

dest равно ожидаемому количеству элементов по таблице destmask.

После этого рекомендуется проверить, что первый элементов таблицы равен адресу,

второй номеру каналу, третий – типу запроса. Для этого можно сравнить эти элементы со

значениями таблицы запроса.

Page 30: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 30

© InSAT Company 2009-2012

После этого полученные данные, а именно - четвертый элемент из таблицы dest

можно записать в тег. Если значение не корректно, то в тег записывается 0 и плохой

признак качества.

Теперь созданный тег можно тиражировать - сделать копии и изменить адреса

регистров, для опроса уставок и состояния выходов.

Прибор передает измеренное значение как целое число, положение десятичной

точки устанавливается в приборе при настройке на конкретный тип датчика и должно

быть учтено при записи значения в тег. Таким образом, перед тем как записать

измеренное значение в тег, нужно сместить положение десятичной точки на заданное

пользователем значение – то есть поделить измеренное значение на 10 в заданной

степени.

Данную настройку лучше вынести из кода скрипта – для облегчения создания

конфигурации пользователю. Для этого в MasterOPC есть возможность добавить к

устройству или подустройству дополнительные свойства. Рассмотрим создание

дополнительного свойства «Положение десятичной точки». Скомпилируем скрипт и

закроем редактор скрипта.

Вызовем контекстное меню у подустройства и выберем пункт «Дополнительные

свойства».

В окне дополнительных свойств добавим новое свойство и назовем его «Pos», в

поле «Описание» напишем «Положение десятичной точки», тип свойства – целое

число. Нажмем ОК.

Page 31: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 31

© InSAT Company 2009-2012

Сделаем, чтобы максимальное вводимое значение было равно четырем - это

максимальное значение точки, которое может ввести пользователь. Сохраним свойство.

Теперь в свойствах подустройства появилось дополнительное свойство, которое

может изменять пользователь.

Page 32: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 32

© InSAT Company 2009-2012

Теперь введенное пользователем значение мы можем прочитать из скрипта. Снова

откроем редактор скрипта тега «Вход». В той части кода, где осуществляется запись в

тег, произведем деление.

Чтобы получить введенное у подустройства пользователем значение

дополнительного свойства, воспользуемся функцией server.ReadSubDeviceExtProperty().

В качестве аргумента ей нужно передать имя нужного свойства. Код будет выглядеть

следующим образом:

Листинг 1.10 Чтение значения дополнительного свойства

if err<0 then

--данные не приняты или данные некорректы. Устанавливаем плохой признак

качества

server.WriteCurrentTag(0,OPC_QUALITY_BAD); --

else

--приняты корректные данные

--получаем значение положения десятичной точки

local Pos=server.ReadSubDeviceExtProperty("Pos");

--Записываем в тег принятое значение учитывая смещение

server.WriteCurrentTag(dest[4]/10^Pos,OPC_QUALITY_GOOD);

end;

Аналогичным образом можно сделать свойство «Номер канала» у каждого

подустройства – что облегчит их копирование для создания конфигураций

многоканальных приборов. Дополнительные свойства также можно создавать у

устройств.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/Контравт.mbc

Page 33: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 33

© InSAT Company 2009-2012

5 Чтение архивов

Modbus Universal MasterOPC сервер является не только DA сервером, но и HDA – то

есть способен передавать OPC клиенту не только текущие, но и архивные данные.

5.1 Автоматическая запись параметров

Создадим новую конфигурацию и назовем ее «HDA», добавим в нее

последовательный порт, в него устройство, в устройство добавим тег.

Чтобы включить HDA, необходимо у тега включить настройку HDA доступ. Появятся

дополнительные настройки.

Настройка Количество записей в архиве - позволяет задать, какое количество

записей данного тега может храниться во внутреннем архиве.

Автоматическая запись – если настройка включена, то будет производиться

автоматическая запись значений в архив. Режим записи зависит от настройки Запись по

изменению значения тега. Если данная настройка выключена, то запись в тег будет

производиться только по команде из скрипта.

Запись по изменению значения тега – если данная настройка включена и включена

автоматическая запись, то в архив происходит запись изменений значений. Если данная

настройка выключена, то запись происходит с периодом опроса устройства.

Как мы видим, существует возможность автоматической записи значений. Такой

режим может быть полезен в следующих случаях:

Page 34: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 34

© InSAT Company 2009-2012

1) Период опроса OPC сервера клиентом больше чем скорость изменения

параметра. Например, мы производим опрос устройства с периодом 10-20 мс.

Опрашивать OPC сервер с таким периодом нежелательно. В этом случае мы можем

включить запись значений и настроить OPC клиент на получение исторических данных –

тогда в SCADA системе будет точный момент времени всех изменений параметра

2) Между OPC сервером и OPC клиентом возможен обрыв связи. Например,

OPC сервер работает в удаленном режиме (то есть, установлен на другом компьютере в

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

без потерь.

Рассмотрим пример работы с автоматической записью. «Тег1» у нас будет получать

данные от прибора, передающего пилообразный сигнал. Запустим режим исполнения

OPC сервера, и выделим в дереве наш OPC тег.

У данного тега присутствует дополнительная закладка – «HDA: Тег1», перейдем на

нее.

В ней отображается состояние архива данной переменной – порядковый номер

записи, значение, признак качества и метка времени.

5.2 Использование OPC HDA в MasterSCADA

Теперь рассмотрим использование HDA в OPC клиенте – SCADA системе

MasterSCADA. Создадим новый проект, и добавим в компьютер OPC HDA сервер

InSAT.ModbusOPCServer.HDA

Page 35: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 35

© InSAT Company 2009-2012

Выделим его и перейдем на закладку Настройки. Отметим галочками

необходимые нам теги (в данном случае всего один).

На этой же закладке можно настроить способ получения данных, в настройке

«Получение данных» - для нашего OPC сервера, можно оставить значение по умолчанию

«Чтение и подписка». В таком режиме SCADA сначала получит недостающие данные,

используя метод «Чтение», а для получения новых данных будет использоваться метод

«Подписка», т.е. новые данные будут поступать с периодом заданным в настройке

«Период получения данных (с)».

Если вам требуется получить архив из прибора за несколько прошедших дней, то в

настройках OPC HDA сервера нужно задать глубину запроса в днях в поле «Период

запроса (д)».

Также на этой закладке можно задать расположение удаленного OPC сервера в

режиме разработки и в режиме исполнения, на тот случай если ваш OPC сервер находится

на другом компьютере.

Page 36: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 36

© InSAT Company 2009-2012

После нажатия кнопки Применить в дерево системы будут добавлены выбранные

нами теги.

Добавим в корневой объект новый объект и создадим у него тренд. Перетащим тег

в объект, и добавим созданное значение на тренд.

Page 37: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 37

© InSAT Company 2009-2012

Запустим режим исполнения. С интервалом равным заданному нами периоду

получения данных в тренд будут поступать архивные значения из OPC сервера.

5.3 Формирование архива при помощи скриптов.

Рассмотрим способы получения архивных данных от приборов. Многие

промышленные приборы (теплосчетчики, электросчетчики) предоставляют архивные

данные по Modbus протоколу. Как правило, предоставляется участок Modbus регистров,

в которых по определенной схеме хранятся архивные значения. Рассмотрим такой случай

на простом примере.

Промышленный контроллер производит запись значений в Modbus регистры в

своей памяти. Контроллер осуществляет запись в архив при изменении величины выше

определенной мертвой зоны – в этот момент контроллер записывает в память значение и

метку времени. Архив представляет собой очередь – «кольцевой буфер», т.е. по

достижению конца архива (последней записи) новое значение записывается в начало

списка – на место самой старой записи записывается новое. Архив на 200 записей

располагается в регистрах 0-1000.

Каждая запись представляет собой 5 Modbus регистров:

1 запись – год и месяц. Старший байт – год, младший – месяц. Год представлен в

формате 2000 год – 0, 2001 – 1, 2002 – 2 и т.д.

2 запись – день и час. Старший байт – день месяца, младший байт – час дня.

3 запись – минута и секунда. Старший байт – минута записи, младший байт – секунда

записи.

4 и 5 запись – значение в формате float (4 байта – 2 регистра Modbus). Чередование

байт – старшим словом вперед.

Page 38: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 38

© InSAT Company 2009-2012

Создадим конфигурацию в OPC сервере которая позволит опрашивать подобный

архив.

Сделаем скрипт в этой же конфигурации. Как правило, приборы с архивом

опрашиваются редко – обычно это удаленные приборы, опрос которых осуществляется по

беспроводным каналам (GSM, GPRS, радио). Увеличим период опроса у устройства –

зададим его равным 5 минутам.

Удалим тег из устройства, и добавим новый, но установим ему регион Server_Only

(то есть программный тег), тип в сервере – Float, тип доступа – только чтение.

Включим HDA доступ и отключим автоматическую запись – архив мы будем

формировать при помощи скрипта.

Добавим тег, включим выполнение скрипта после чтения и откроем редактор.

Нам необходимо выполнять запросы к устройству, для этого в скриптах есть

специальные функции – они находятся в специальном разделе функций – «Modbus».

Page 39: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 39

© InSAT Company 2009-2012

Данные функции позволяют опрашивать и записывать различные типы регистров:

InputRegisters, HoldingRegister, Coils, а также функция ReadFileRecord(0x14 функция), и

расширенные (функции 0x65-0x72).Описание всех функций есть в справочной системе.

Рассмотрим функцию чтения целого числа – Int16 (остальные типы читаются

аналогично) регистра типа HoldingRegisters.

err,w = modbus.ReadHoldingRegistersAsInt16(0,10,true,"10325476");

В качестве аргументов в функцию нужно передать 4 параметра - Address, Count,

ChangeBytes, StringChangeBytes

Address - стартовый адрес регистра.

Count - количество запрашиваемых элементов. Обратите внимание что

запрашивается количество элементов, без учета того сколько Modbus регистров они

занимают. То есть при опросе float чисел, которые занимают 2 Modbus регистра, нужно

указывать количество запрашиваемых чисел, а не Modbus адресов.

ChangeBytes - разрешение или запрет использования перестановки байт заданного в

параметре StringChangeBytes (следующий параметр).

StringChangeBytes – строковый параметр, представляющий собой 8

неповторяющихся байт (0-7). С помощью данного параметра можно задать чередование

байт.

Функция возвращает два параметра: ошибка при запросе (true – возникла ошибка) и

таблицу с полученными данными. Ошибка возникает если после заданного в настройках

устройства количества попыток опроса не пришел корректный ответ. Таблица с

данными в этом случае пуста.

Page 40: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 40

© InSAT Company 2009-2012

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

записать их в архив. Для этого воспользуемся циклом for. Цикл for имеет следующий

синтаксис:

for var=start_val, max_val, step do

--тело цикла

end;

Где:

Var – инкрементируемая каждый цикл переменная

Start_val – стартовое значение переменной

Max_val – максимальное значение переменной, по достижении переменной var

этого значения цикл будет завершен

Step – шаг на который происходит увеличение переменной var на каждом цикле.

Нам нужно опросить регистры от 0 до 1000, с шагом 5, поэтому цикл будет иметь

следующий вид:

Листинг 1.11 Цикл опроса регистров от 0 до 1000

local i,err;

local w={}; --таблица принятых значений

for i=0, 1000, 5 do

--здесь мы расположим код, считывающий и обрабатывающий регистры

end;

Теперь мы можем вызвать функцию опрашивающую регистры метки времени и

измеренное значение:

Листинг 1.12 Считывание значений регистров цикле

for i=0,1000,5 do

--опрашиваем регистры времени

err,w = modbus.ReadHoldingRegistersAsInt16(i,3,true,"10325476");

--опрашиваем регистр измеренного значения

err,w = modbus.ReadHoldingRegistersAsFloat(i+3,1,true,"32107654");

end;

Значения получены, и нам нужно записать их в архив, но предварительно нужно

преобразовать полученное значение метки времени к необходимому формату. Для

записи в архив HDA используется функция server.WriteCurrentTagToHda() (а также

функции server.WriteTagByRelativeNameToHda и server.WriteTagToHda). Функция имеет

следующий синтаксис:

server.WriteCurrentTagToHda(Value,OPC_QUALITY,time_stamp)

Функция server.WriteCurrentTagToHda() в качестве аргументов, может принимать

три значения:

Page 41: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 41

© InSAT Company 2009-2012

Value – обязательный параметр, записывающий в архив значение переменной

OPC_QUALITY – обязательный параметр, записывающий в архив признак качества

значения.

Time_stamp – необязательный параметр. Если он указан, то значение записывается с

указанной меткой времени, если не указан – то с меткой времени равной текущему

системному времени. Параметр должен иметь формат TimeStamp, то есть дата + время (с

количеством миллисекунд).

Итак, чтобы записать значение в архив, нам нужно получить метку времени. Метка

времени у нас поступает в Modbus регистрах как набор отдельных чисел. Чтобы

преобразовать их во время нужно выполнить следующий код:

Err,timesec = time.PackTime(2010,05,03,12,34,45);

timemsec=200;

ts = time.TimeToTimeStamp(timesec,timemsec);

server.WriteCurrentTagToHda(120,OPC_QUALITY_GOOD,ts)

В первой строчке, при помощи функции time.PackTime() происходит инициализация

значения - из отдельных элементов формируется (собирается) время, в данном случае из

отдельных чисел собирается дата 03 мая 2010, время 12:34:45. Функция возвращает два

параметра: первый (переменная err) – код ошибки, если ошибки нет то равен нулю

(расшифровка кодов ошибок есть в справке), второй параметр (переменная timesec) –

сформированное значение времени, в случае ошибки возвращается 1 января 1970 года

(нулевое время по UnixTime).

Затем при помощи функции time.TimeToTimeStamp происходит преобразование

переменной времени к формату TimeStamp. Эту переменную можно использовать в

функции записи архива.

Таким образом, нужно написать функцию, которая разберет полученные от

устройства Modbus регистры на элементы, и преобразовать их во время.

Элементы времени хранятся в регистрах – в старшем и младшем байтах. Чтобы

разобрать число Uint16 на два отдельных байта, напишем специальную функцию

ConvertInt. Код функции будет выглядеть так:

Листинг 1.13 Функция получения старшего и младшего байта

function ConvertInt(value)

local low=bit.BitAnd(value,255);

local high=bit.BitRshift(value,8);

return high,low;

end;

В качестве аргумента мы передаем в функцию число, которое нужно разложить на

два отдельных байта.

Page 42: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 42

© InSAT Company 2009-2012

В первой строке кода мы получаем младший байт. Для этого нам нужно старший

байт заполнить нулями. Для этого мы выполняем операцию «Побитовое И» с числом 255

(в двоичной форме это 00000000 11111111), а результат операции присваиваем

переменной low.

Во второй строке мы получаем старший байт. Для этого нам нужно просто

выполнить операцию «Арифметический сдвиг вправо» на 8 бит. Старшие биты при этой

операции замещаются нулями.

При помощи оператора return мы возвращаем результат функции. Теперь эту

функцию можно вызывать из другой функции – создание отдельной функции упростит

код и сделает его более компактным.

Теперь напишем функцию, которая вернет время в формате TimeStamp. Назовем ее

ConvertToTimeStamp, код функции приведен ниже:

Листинг 1.14 Преобразование в формат TimeStamp

function ConvertToTimeStamp(year_month,day_hour,min_sek)

local year,month,day,hour,minute,second;

--получаем значения из каждого байта используя функцию ConvertInt

year,month=ConvertInt(year_month);

year=year+2000; --прибавляем 2000 к полученному номеру года

day,hour=ConvertInt(day_hour);

minute,second=ConvertInt(min_sek);

local err,timesec;

--получаем время

err,timesec=time.PackTime(year,month,day,hour,minute,second};

--преобразуем время в тип TimeStamp

ts = time.TimeToTimeStamp(timesec,0);

--возвращаем значение

return ts;

end;

Теперь эту функцию можно вызывать из основной функции, и записывать значение в

архив с этой меткой времени.

Листинг 1.15 Запись полученных значений в архив

for i=0,1000,5 do

--опрашиваем регистры времени

err,w = modbus.ReadHoldingRegistersAsInt16(i,3,true,"10325476");

-- преобразуем значения во время

ts=ConvertToTimeStamp(w[1],w[2],w[3]);

err,w = modbus.ReadHoldingRegistersAsFloat(i+3,1,true,"32107654");

--записываем значение в архив

server.WriteCurrentTagToHda(w[1],192,ts);

end;

Как быть с обработкой ошибок? Например, при опросе метки времени или

значения, после всех попыток выполнить запрос, функция вернула ошибку. Вероятнее

всего это обозначает, что нет связи с прибором и нужно выйти из цикла. Для этого нужно

выполнить команду break

Page 43: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 43

© InSAT Company 2009-2012

Листинг 1.16 Проверка ошибок

err,w = modbus.ReadHoldingRegistersAsInt16(i,3,true,"10325476");

if err==true then break; --проверяем флаг ошибки

end;

При этом, можно записать какое либо сообщение при помощи функции

Server.Message:

Листинг 1.17 Проверка ошибок с записью в журнал

if err==true then

server.Message("Нет связи");

break;

end;

Или при помощи функции server.WriteTagByRelativeName() записать в другой тег

флаг ошибки – все это зависит от конкретной задачи.

Еще одна возможная ошибка – это некорректные значения, полученные от прибора.

Например, если прибор не успел заполнить все ячейки архива, то в определенный

момент OPC сервер будет получать нулевые значения регистров. В этом случае

дальнейшее чтение архива не имеет смысла – так как следующие ячейки тоже будут

содержать нули, поэтому также необходимо выйти из цикла. Реализовать данную задачу

можно следующим образом – если регистры содержат нулевые значения, то значит и в

функцию ConvertToTimeStamp поступят нулевые значения. То есть функция

time.PackTime() будет пытаться сформировать дату, имеющую нулевой месяц и нулевой

день, что приведет к ошибке и к установке соответствующего кода ошибки. Этот код

можно вернуть в вызывающую функцию и проверить его. Таким образом, код функции

ConvertToTimeStamp, будет выглядеть так:

Листинг 1.18 Функция преобразования в TimeStamp с контролем ошибки

function ConvertToTimeStamp(year_month,day_hour,min_sek)

local year,month,day,hour,minute,second;

--получаем значения из каждого байта используя функцию ConvertInt

year,month=ConvertInt(year_month);

year=year+2000; --прибавляем 2000 к полученному номеру года

day,hour=ConvertInt(day_hour);

minute,second=ConvertInt(min_sek);

local err,timesec;

--получаем время

err,timesec=time.PackTime(year,month,day,hour,minute,second};

--преобразуем время в тип TimeStamp

ts = time.TimeToTimeStamp(timesec,0);

--возвращаем значение

return err,ts;

end;

Как мы видим, единственное что добавилось – теперь функция возвращает два

значения – статус ошибки и время в формате TimeStamp (строчка return err,ts).

Поскольку функция time.PackTime в случае ошибки возвращает 1 января 1970 года, то

Page 44: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 44

© InSAT Company 2009-2012

ошибки при вызове функции time.TimeToTimeStamp не произойдет, а значит

контролировать флаг err при вызове этой функции необязательно.

Теперь нужно подкорректировать код, после вызова функции ConvertToTimeStamp –

проверить, что переменная err не равна нулю, при помощи оператора ~=

Листинг 1.19 Выход из цикла при получении некорректного времени

--если ошибки нет - преобразуем значения во время

err,ts=ConvertToTimeStamp(w[1],w[2],w[3]);

if err~=0 then

break; --если ошибка преобразования - выходим из цикла

end;

Проверяем переменную err, если она не равна нулю – выходим из цикла без всяких

сообщений в журнал.

На указанном примере, мы рассмотрели, как получать данные из архивных

приборов и передавать их OPC клиенту по HDA интерфейсу. Данный пример является

учебным, поэтому несколько упрощен - приборы с архивами, передающие данные по

Modbus интерфейсу, как правило, имеют более сложный принцип передачи данных.

Например, вначале требуется записать в определенные регистры дату и время начала и

конца требуемой части архива, после этого прибор, в определенной последовательности

записывает значения в Modbus регистры, которые и нужно считать. Но, используя

мощный функционал скриптового языка MasterOPC, данную задачу также можно решить.

Разумеется, считывать архива приборов можно не только по протоколу Modbus, но и

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

полученные данные записывать в архив HDA.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/HDA.mbc

5.4 Чтение архивов счетчика «Пульсар»

В качестве более сложного примера чтения архивов по Modbus протоколу,

рассмотрим счетчик импульсов «Пульсар», фирмы «Тепловодохран».

Данный прибор, позволяет передавать архивы по стандартному Modbus-протоколу.

Полное описание реализации Modbus можно прочитать в документации к устройству:

http://www.teplovodokhran.ru/products/Files/PulsarModBus.pdf

Для того чтобы считать архив счетчика, за какой-либо промежуток времени,

необходимо выполнить следующие действия:

1) Записать (функцией 0x10 – Write_Multiple_Registers) в регистры 0x03, 0x04 и

0x05 время с которого надо начинать считывание архива.

Page 45: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 45

© InSAT Company 2009-2012

2) Записать (функцией 0x10) в регистры 0x06, 0x07 и 0x08 время, по которое нужно

произвести считывание архива.

3) Записать (функцией 0x10) в регистр 0x09, номер канала чей архив нужно

считать, и тип архива (часовой, суточный, месячный).

4) Выждать 50-100 мс.

5) Контроллер запишет значения архива в регистры 0x100 – 0x17B. Значения

записываются в формате Float, чередование байт – «Старшим словом вперед»,

количество значений в архиве - 62. Эти значения нужно считать при помощи

функции Read_Holding_Registers (0x03). Значения идут со времени начала, с

шагом в 1 интервал времени (час, день, месяц – в зависимости от типа архива).

Подобный алгоритм считывания архивов является достаточно распространенным (и

может быть реализованным не только на Modbus протоколе) – он встречается и

теплосчетчиках, счетчиках электроэнергии и некоторых других приборов.

Создадим конфигурацию для считывания архива из данного счетчика. В качестве

примера мы будем считывать часовой архив. Создадим новую конфигурацию, добавим

узел – COM-порт, в него добавим устройство – назовем его «Пульсар». В счетчике есть

три типа архива (часовой, суточный, месячный), кроме того у него и есть и обычные (не

архивные) параметры. Поэтому для удобства пользователя и упрощения программы, мы

сделаем реализацию чтения архивов в отдельном подустройстве. Кроме того, это

позволит для каждого типа архива сделать отдельные настройки, используя функцию

дополнительные свойства. Добавим в наше устройство подустройство, и назовем его

«Часовой архив», период опроса зададим – 1 минута.

В подустройство добавим 10 тегов – для каждого входа. Наши теги будут

программными – зададим настройку Регион в «Server_Only», тип доступа тегов –

«ReadOnly» (только чтение), настройку тип в сервере установим во Float. Поскольку теги

будут хранить архив, то включим режим HDA, отключим автоматическую запись,

Page 46: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 46

© InSAT Company 2009-2012

количество записей оставим по умолчанию – 1000. Имена тегам дадим «Вход1»,

«Вход2».. «Вход10», для этого используем функцию тиражирования.

Наша конфигурация будет выглядеть так:

Для архива желательны дополнительные настройки, которые пользователь мог бы

задать сам – в зависимости от задачи. Например, можно сделать настройку, которая

позволит задавать глубину считывания при старте. Сделаем такую настройку.

Для этого, вызовем у подустройства «Часовой архив» контекстное меню,

выберем пункт Дополнительные свойства. В появившемся окне добавим новое

свойство, имя свойства (по нему мы будем обращаться из кода) установим –

«NumRecStart», описание (его будет видеть пользователь) – «Кол-во считываемых

записей при старте», тип установим «Целое число». Ограничим диапазон задания

Page 47: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 47

© InSAT Company 2009-2012

количества считываемых записей. Глубина часового архива у счетчика «Пульсар 10»

составляет 1080 часов – это будет максимальное значение. Минимальное значение

зададим равным количеству доступных для считывания за один запрос регистров – 62,

значение по умолчанию зададим – 124 (2 запроса).

Сохраним нашу конфигурацию, а после этого включим у подустройства исполнение

скрипта.

Приведем код скрипта полностью, а потом разберем его построчно.

Листинг 1.20 Считывание архива счетчика «Пульсар»

--Скрипт считывания часового архива счетчика Пульсар.

local LastTime; --метка времени последнего считанного значения

local NumRecStart; --количество считываемых регистров при старте

local TypeArch=1; -- тип архива - часовой

local GetInput={}; --наличие входов в подустройстве

local NameTagStatic="Вход"; --статическая часть имени входа

local NoInput=true; --в подустройстве нет входов

--функция разбора времени с учетом типа архива

function UnPack(TimeVal)

local Table=time.UnpackTime(TimeVal); --разбиваем время

--начала на составляющие

Table[5]=0; --задаем начало часа

Table[6]=0;

--формируем новое время

local err,NewTime=

time.PackTime(Table[1],Table[2],Table[3],Table[4],Table[5],Table[6]);

Table[1]=Table[1]-2000; --вычитаем 2000 из года

return Table,NewTime;

end;

--преобразования отдельных байт в регистр типа int16

function ByteToInt16(ByteH,ByteL)

local reg=bit.BitLshift(ByteH,8);

reg=bit.BitOr(reg,ByteL);

return reg;

end;

-- инициализация

function OnInit()

Page 48: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 48

© InSAT Company 2009-2012

--считываем настройку количества считываемых записей при старте

NumRecStart=server.ReadSubDeviceExtProperty("NumRecStart" );

local TimeNow=time.TimeNow(); --получаем текущее время

--получаем метку времени значения с которого начинаем чтение

LastTime=time.TimeAddHour(TimeNow,NumRecStart*(-1));

--проверяем наличие тегов в подустройстве

for i=1,10,1 do

local NameTag=NameTagStatic..(i);

--считываем атрибуты тега

local Err=server.GetAttributeTagByRelativeName(NameTag);

GetInput[i]=not Err; --проверяем существование тега

if Err==false then NoInput=false; end; --теги в подустройстве есть

end;

end

-- функция,выполняющаяся перед чтением тегов

function OnBeforeReading()

if NoInput==true then return; end; --нет ни одного тега в подустройстве –

--выходим

local StartTime; --время начала архива

local EndTime;--время конца архива

repeat

local SaveLastTime=LastTime; --сохраняем прошлую метку времени

local TimeStartTable={};

--разбиваем время начала на составляющие с учетом типа архива

TimeStartTable,StartTime=UnPack(LastTime);

EndTime=time.TimeAddHour(StartTime,62); --получаем время конца

--разбиваем время конца на составляющие с учетом типа архива

local TimeEndTable=UnPack(EndTime);

local Send={}; --таблица отправляемых регистров

--упаковка байт в регистры

Send[1]=ByteToInt16(TimeStartTable[1],TimeStartTable[2]);

Send[2]=ByteToInt16(TimeStartTable[3],TimeStartTable[4]);

Send[3]=ByteToInt16(TimeStartTable[5],TimeStartTable[6]);

Send[4]=ByteToInt16(TimeEndTable[1],TimeEndTable[2]);

Send[5]=ByteToInt16(TimeEndTable[3],TimeEndTable[4]);

Send[6]=ByteToInt16(TimeEndTable[5],TimeEndTable[6]);

local err=false; --объявление переменной ошибки

for i=1,10,1 do

--если данный номер тега есть в подустройстве опрашиваем его архив

if GetInput[i]==true then

Send[7]=ByteToInt16(TypeArch,i); --тип архива и номер канала

err=modbus.WriteHoldingRegistersAsUInt16

(3,table.maxn(Send),true,"10325476",false,Send);

if err==true then break; end; --ошибка - выходим из цикла

server.Sleep(100); --ожидание 100 мс.

local Dest={}; --архив принятых значений

local TimeS=StartTime; --метка времени

--запрос чтения данных

err,Dest=modbus.ReadHoldingRegistersAsFloat(0x100,62,true,"32105476");

if err==true then break; end; --если ошибка - выходим им цикла

for j=1,table.maxn(Dest),1 do --разбор принятых значений

--если значение корректно, и метка времени не превысила текущее время

if Dest[j]>(-3590324220) and TimeS<=time.TimeNow() then

LastTime=TimeS; --сохраяем время как последнее считанное

--преобразуем в формат TimeStamp

local TimeStamp=time.TimeToTimeStamp(TimeS,0);

--записываем значение в HDA

server.WriteTagByRelativeNameToHda

(NameTagStatic..(i),Dest[j],OPC_QUALITY_GOOD,TimeStamp );

end;

Page 49: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 49

© InSAT Company 2009-2012

--прибавляем к метке времени 1 час

TimeS=time.TimeAddHour(TimeS,1);

end;

end;

end;

if err==true then --если ошибка - выходим из цикла

server.Message("Ошибка получения данных");

break;

end;

--если метка последнего значения не изменилась

if SaveLastTime==LastTime and EndTime<time.TimeNow() then

LastTime=time.TimeAddHour(LastTime,61);

end;

until EndTime>=time.TimeNow();

server.Message("цикл считывания закончен" );

end Рассмотрим код построчно. В начале происходит объявление глобальных

переменных –код очевиден, поэтому не будем его пояснять. Функции UnPack и

ByteToInt16 будут вызываться из основного кода, поэтому их код мы рассмотрим позже.

Перейдем сразу к коду функции OnInit – этот код вызывается один раз при старте

OPC сервера.

function OnInit()

--считываем настройку количества считываемых записей при старте

NumRecStart=server.ReadSubDeviceExtProperty("NumRecStart" );

В данной строке, происходит считывание пользовательской настройки –

количество считываемых регистров при старте, которую мы назвали «NumRecStart». Ее

результат сохраняется в глобальную переменную NumRecStart.

local TimeNow=time.TimeNow(); --получаем текущее время

--получаем метку времени значения с которого начинаем чтение

LastTime=time.TimeAddHour(TimeNow,NumRecStart*(-1));

В этих строках мы получаем время, с которого нужно начать производить

считывание записей. Для работы со временем в OPC сервере есть специальные функции –

в разделе time. Для получения текущего времени используется функция time.TimeNow().

Функция time.TimeAddHour() предназначена для прибавления заданного количества

часов к переменной времени. Первый аргумент функции – время, второй – количество

прибавляемых часов. Если нужно вычесть время, то количество прибавляемых часов

должны быть отрицательными. Поэтому при вызове этой функции в нашем коде

переменная NumRecStart умножается на «минус один».

--проверяем наличие тегов в подустройстве

for i=1,10,1 do

local NameTag=NameTagStatic..(i);

--считываем атрибуты тега

local Err=server.GetAttributeTagByRelativeName(NameTag);

GetInput[i]=not Err; --проверяем существование тега

if Err==false then NoInput=false; end; --теги в подустройстве есть

end;

Возможна ситуация, когда некоторые из входов у пользователя не задействованы –

в этом случае опрашивать их тоже нецелесообразно. В этом случае пользователь может

просто удалить их из конфигурации.

Page 50: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 50

© InSAT Company 2009-2012

Данный код производит обработку такой ситуации. Функция

server.GetAttributeTagByRelativeName() предназначена для получения атрибутов тега –

имени, типа регистра, типа данных, номер регистра и т.д. Если функция возвращает nil

– значит тег не найден. При помощи цикла последовательно проверяются существование

всех входов Вход1-Вход10. Если тег существует, то в соответствующий индекс массива

GetInput записывается true, иначе – false. Глобальная переменная NoInput в начале

программы инициализирована значением true. Если в подустройстве найден хоть один

тег, то в переменную NoInput записывается – false.

Перейдем к функции OnBeforeReading.

function OnBeforeReading()

--нет ни одного тега в подустройстве - выходим

if NoInput==true then return; end;

Вначале функции мы проверяем состояние переменной NoInput – если оно равно

true, то в подустройстве нет ни одного тега и опрашивать нечего. В этом случае

происходит выход из функции – вызывается оператор return.

local StartTime; --время начала архива

local EndTime;--время конца архива

repeat

local SaveLastTime=LastTime; --сохраняем прошлую метку времени

local TimeStartTable={};

--разбиваем время начала на составляющие с учетом типа архива

TimeStartTable,StartTime=UnPack(LastTime);

EndTime=time.TimeAddHour(StartTime,62); --получаем время конца

--разбиваем время конца на составляющие с учетом типа архива

local TimeEndTable=UnPack(EndTime);

Данный код определяет время, с которого мы начнем считывать значения из

архива и время конца считывания. Согласно спецификации к прибору мы должны послать

ему дату и время в отдельных байтах трех регистров. Поэтому сначала нам необходимо

разобрать время на составляющие (год, месяц, день, час, минута, секунда) – эту задачу

выполняет функция UnPack().

Рассмотрим ее код:

function UnPack(time)

local Table=time.UnPackTime(time); --разбиваем время начала на составляющие

Table[5]=0; --задаем начало часа

Table[6]=0;

--формируем новое время

local NewTime=

time.PackTime(Table[1],Table[2],Table[3],Table[4],Table[5],Table[6]);

Table[1]=Table[1]-2000; --вычитаем 2000 из года

return Table,NewTime;

end;

В качестве аргумента, в функцию передается время, которое нужно разобрать. Для

разбора времени используется функция time.UnPackTime(). Данная функция возвращает

таблицу из 6 элементов (1 элемент – год, 2 – месяц, 3 – день, 4 – час, 5 – минута, 6 –

секунда). 5 и 6 элемент мы устанавливаем равным нулю. Дело в том что в системах

АСКУЭ, как правило, требуются значения на начало часа. Но поскольку OPC сервер может

Page 51: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 51

© InSAT Company 2009-2012

быть запущен в любое время, то необходимо произвести округление до начала часа –

установить часы и минуты равными нулю.

В дальнейшем коде, нам также нужно будет работать с округленным временем,

поэтому мы записываем округленное время в переменную NewTime – собираем время из

отдельных элементов при помощи функции time.PackTime().

Из первого элемента таблицы - года, необходимо вычесть 2000 – это требование

самого прибора.

Затем функция возвращает таблицу с элементами времени и новое, округленное,

время начала считывания архива.

В основном коде, к округленному значению мы прибавляем 62 часа

(максимальное количество считываемых записей) – это будет время конца. Это время мы

также разбираем на составляющие.

В итоге у нас получены две переменные-таблицы TimeStartTable и TimeEndTable, а

также переменная StartTime – метка начала.

local Send={}; --таблица отправляемых регистров

--упаковка байт в регистры

Send[1]=ByteToInt16(TimeStartTable[1],TimeStartTable[2]);

Send[2]=ByteToInt16(TimeStartTable[3],TimeStartTable[4]);

Send[3]=ByteToInt16(TimeStartTable[5],TimeStartTable[6]);

Send[4]=ByteToInt16(TimeEndTable[1],TimeEndTable[2]);

Send[5]=ByteToInt16(TimeEndTable[3],TimeEndTable[4]);

Send[6]=ByteToInt16(TimeEndTable[5],TimeEndTable[6]);

Как мы указывали ранее, прибор требует, чтобы время было передано в отдельных

байтах трех регистров. Функция ByteToInt16 собирает из двух чисел одно число Int16,

которое можно будет послать в прибор. Рассмотрим код функции:

function ByteToInt16(ByteH,ByteL)

local reg=bit.BitLshift(ByteH,8);

reg=bit.BitOr(reg,ByteL);

return reg;

end;

В качестве аргумента, функция получает два числа (байта). Первое число

смещается на 8 бит влево, при помощи функции «Побитовый сдвиг влево» -

bit.BitLshift() – так формируется старший байт. Младший байт при этом будет забит

нулями. Чтобы заполнить его битами второго числа выполняется операция «побитовое

ИЛИ» при помощи специальной функции bit.BitOr(). Сформированная переменная reg,

отправляется на выход функции.

В итоге, в основном коде у нас сформируется таблица Send, чьи элементы

заполнены числами содержащими дату начала и конца запроса архива.

local err=false; --объявление переменной ошибки

for i=1,10,1 do

--если данный номер тега есть в подустройстве опрашиваем его архив

if GetInput[i]==true then

Page 52: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 52

© InSAT Company 2009-2012

Send[7]=ByteToInt16(TypeArch,i); --тип архива и номер канала

err=modbus.WriteHoldingRegistersAsUInt16

(3,table.maxn(Send),true,"10325476",false,Send);

if err==true then break; end; --ошибка - выходим из цикла

Данный код выполняет запись времени в устройство. Поскольку входов у нас 10, то

необходимо произвести последовательный опрос всех тегов. Вначале проверяется

наличие входа в конфигурации. Если элемент таблицы GetInput с данным номером

содержит true – то запрос выполняется.

Элемент 7 таблицы Send, заполняется двумя значениями – типом архива (он

установлен как константа 1 – часовой), и номером канала – переменная i.

Теперь таблица Send полностью сформирована, и ее можно отправлять в

устройство. Для выполнения запроса записи, используется функция

Modbus.WriteHoldingRegistersAsUInt16(). В качестве аргументов в нее нужно передать

стартовый номер регистра для записи (в устройстве он равен 3), количество

передаваемых элементов (у нас их семь – по количеству элементов таблицы Send), также

задаем чередование байт, и передаем таблицу со значениями – таблицу Send.

Если операция не будет выполнена (например не будет связи с устройством), то

переменной err будет присвоено true. В этом случае вызывается оператор break – выход

из цикла.

server.Sleep(100); --ожидание 100 мс.

local Dest={}; --архив принятых значений

local TimeS=StartTime; --метка времени

--запрос чтения данных

err,Dest=modbus.ReadHoldingRegistersAsFloat(0x100,62,true,"32105476");

if err==true then break; end; --если ошибка - выходим им цикла

for j=1,table.maxn(Dest),1 do --разбор принятых значений

--если значение корректно, и метка времени не превысила текущее время

if Dest[j]>(-3590324220) and TimeS<=time.TimeNow() then

LastTime=TimeS; --сохраяем время как последнее считанное

--преобразуем в формат TimeStamp

local TimeStamp=time.TimeToTimeStamp(TimeS,0);

--записываем значение в HDA

server.WriteTagByRelativeNameToHda

(NameTagStatic..(i),Dest[j],OPC_QUALITY_GOOD,TimeStamp );

end;

TimeS=time.TimeAddHour(TimeS,1); --прибавляем к метке времени 1 час

end;

Согласно инструкции прибора, после отправки запроса и получения ответа, нужно

выждать 100 мс, это делается при помощи функции server.Sleep().

После выдержки времени можно приступать к опросу регистров с данными. Для

этого используется функция Modbus.ReadHoldingRegistersAsFloat(). В качестве аргументов

ей нужно передать стартовый адрес – 0x100 (256 в десятичной системе), количество

запрашиваемых элементов – 62, и чередование байт.

Если запрос не будет выполнен корректно, то переменной err будет присвоено true,

и будет вызвана команда выхода из цикла. Если же данные получены, то они будут

записаны в таблицу Dest.

Page 53: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 53

© InSAT Company 2009-2012

После того как данные получены, можно приступить к их анализу и записи в архив

тега. Для этого каждый элемент таблицы поочередно обрабатывается в цикле for.

Строчка

if Dest[j]>(-3590324220) and TimeS<=time.TimeNow() then

Предназначена для проверки правильности принятого числа. Согласно описанию

прибора, если в запрашиваемый момент времени значения в архиве нет, то в регистры

будет записано FF FF FF FF, что в переводе во Float соответствует числу -3590324220. То

есть если принятое число больше этого значения – то значение достоверное и его можно

обработать. Значение также не обрабатывается если время значения превысило текущее

время (мы обрабатываем значение, которого еще не существует).

После проверки на достоверность, в переменную LastTime сохраняется время

считываемого значения. Это необходимо, чтобы после завершения всего цикла

считывания у нас сохранилась последняя метка времени – именно с нее мы и начнем

считывание в следующем цикле.

При помощи функции time.TimeToTimeStamp() время преобразуется в формат

TimeStamp – в нему добавляются миллисекунды.

После того как значение и метка времени получены, и их можно записать в нужный

HDA тег, при помощи функции server.WriteTagByRelativeNameToHDA(). Затем, к метке

времени значения – к переменной TimeS прибавляется 1 час.

После этого следует код проверки.

if err==true then --если ошибка - выходим из цикла

server.Message("Ошибка получения данных");

break;

end;

--если метка последнего значения не изменилась

if SaveLastTime==LastTime and EndTime<time.TimeNow() then

LastTime=time.TimeAddHour(LastTime,61);

end;

until EndTime>=time.TimeNow();

Первая часть кода не нуждается в особых пояснениях – если при считывании

возникла ошибка (переменная err стала равна true), то в лог записывается сообщение и

происходит выход из цикла.

Вторая часть кода требует пояснения. Возможна ситуация, когда переменная с

последней меткой времени значения (переменная LastTime) после опроса не изменилась.

Такая ситуация может возникнуть, если мы начнем считывать архив за очень давний

промежуток времени – когда значений в архиве еще нет. В этом случае не изменится и

значение переменной EndTime, но цикл опроса завершается именно тогда, когда

значение этой переменной превысит текущее время.

Page 54: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 54

© InSAT Company 2009-2012

В таком случае, чтобы избежать «зацикливания» программы, необходимо

увеличить LastTime на 61 час – то есть пропустить пустой промежуток архива. Именно эту

функцию и реализует вторая часть кода.

Данный код упрощен – например, скрипт реализует только чтения часового

архива. Также может быть полезным ограничение количества получаемых записей –

чтобы сделать запросы ответа более короткими (что бывает полезным при сильных

помехах).

К OPC серверу прилагается конфигурация «Пульсар», в которой реализовано

чтение всех типов архивов, ограничение количества считываемых записей, а также ряд

дополнительных программных защит. Код скрипта конфигурации открыт, поэтому

читатель может изучить его самостоятельно.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/Пульсар (учебный).mbc

5.5 Чтение файлов по Modbus протоколу

Еще одним вариантом получения архивов по Modbus является считывание файлов

из устройства при помощи 20-функции (функция 0x14). Данная функция позволяет

считывать из файлов записи произвольной структуры. Каждый файл может содержать до

10 тысяч записей, количество файлов может составлять 65535. Количество получаемых за

один запрос данных – 245 байт.

Функция имеет следующую структуру запроса:

Обозначение поля Размер Диапазон значений

Адрес устройства 1 Byte 0x00 – 0xFF

Номер функции 1 Byte 0x14

Количество байт 1 Byte 0x07 – 0xF5

Запрос X, тип ссылки 1 Byte 0x06

Запрос X, номер файла 2 Byte 0x0001 – 0xFFFF

Запрос X, номер записи 2 Byte 0x0000 – 0x270F

Запрос X, длина записи 2 Byte N Byte

Запрос X+1, тип ссылки…

Page 55: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 55

© InSAT Company 2009-2012

CRC – контрольная сумма 2 Byte

Номер устройства – Modbus адрес устройства.

Номер функции – порядковый номер функции запроса (0x14).

Количество байт – суммарное количество байт всех элементов «ЗапросX» (тип

файла, номер записи, длина записи).

Следующие элементы описывают структуру выполняемого запроса чтения записи из

файла:

Тип ссылки – согласно спецификации всегда 6.

Номер файла – номер запрашиваемого файла.

Номер записи – номер интересующей записи из файла.

Длина записи – количество элементов (регистров) которые нужно считать из записи.

Далее следует описание запроса следующей записи, если таковые имеются.

CRC – контрольная сумма Modbus запроса.

Устройство посылает ответ следующей структуры:

Обозначение поля Размер Диапазон значений

Адрес устройства 1 Byte 0x01 – 0xFF

Номер функции 1 Byte 0x14

Длина ответа 1 Byte 0x07 – 0xF5

Запрос X, длина ответа 1 Byte 0x07 – 0xF5

Запрос X, тип ссылки 1 Byte 0x06

Запрос X, данные записи Nx2 Bytes

Запрос X+1, длина

ответа…

CRC – контрольная сумма 2 Byte

Адрес устройства и номер функции имеют такое же назначение, как и при запросе.

Page 56: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 56

© InSAT Company 2009-2012

Длина ответа – общая длина принятых элементов запроса (в байтах). Каждый

запрос содержит собственное поле с количеством байт в запросе.

Следующие элементы описывают полученные данные из записи файла:

Длина ответа – количество принятых байт в данной записи.

Тип ссылки – согласно спецификации всегда 6.

Данные записи – полученные из записей регистры данных. Каждый регистр

занимает 2 байта.

Подробнее прочитать про функцию 0x14, с примерами запросов и ответа, вы можете

в официальном стандарте Modbus, на странице 32-33:

http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf

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

номер записи и количество требуемых регистров из записи.

Для выполнения запроса чтения файлов по протоколу Modbus в MasterOPC есть

специальная функция - modbus.ReadFileFunction(). В качестве аргументов, функция

принимает 5 параметров:

1 параметр – количество получаемых из записи таблиц.

2 параметр – таблица с номерами считываемых файлов. Таблица должна содержать

столько номеров файлов, сколько задано количество получаемых таблиц.

3 параметр – таблица с номерами считываемых записей. Таблица должна содержать

столько номеров записей, сколько задано количество получаемых таблиц.

4 параметр – таблица количества принимаемых элементов для каждой получаемой

таблицы. Таблица должна содержать столько элементов, сколько задано количество

получаемых таблиц.

5 параметр – таблица таблиц масок. Таблица должна содержать столько таблиц

масок, сколько задано количество получаемых таблиц.

Функция возвращает от двух и более значений:

1 – код ошибки. При корректном ответе равен нулю.

2..N – принятые таблицы значений отформатированные по заданным маскам.

Количество принимаемых таблиц зависит от заданного пользователем значения (первый

входной аргумент функции).

Page 57: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 57

© InSAT Company 2009-2012

В качестве примера, рассмотрим пример запроса из справки по данной функции. В

приведенном ниже коде мы будем запрашивать из файла 1, две записи – 1 и 10, каждая

запись будет содержать по 5 элементов типа «2 –байтное знаковое целое» - Int16.

Приведем код полностью, а затем разберем его построчно.

Листинг 1.21 Чтение данных функцией ReadFileFunction (20-функция Modbus)

local err,dstlen --объявление переменных

local file = {1,1} --объявление и инициализация таблицы файлов

local rec = {1,10} --объявление и инициализация таблицы записей

local len = {5,5} --объявление и инициализация таблицы количества

принимаемых элементов

-- для каждой из принимаемых таблиц.

local dstdata = {} --объявление таблицы для принятия данных из записи 1

local dstdata2= {} --объявление таблицы для принятия данных из записи 10

local dstmsk1 = {"int16:5:10"} --маска первой таблицы (для записи 1). Тип

значения Int16,

--количество элементов – 5, чередование старшим байтов вперед

local dstmsk2 = {"int16:5:10"} -- маска второй таблицы (для записи 2).

Тип значения Int16,

--количество элементов – 5, чередование старшим байтов вперед

local dstmsk = {dstmsk1,dstmsk2} --создание таблицы таблиц с масками

--чтение архива из устройства

err,dstdata,dstdata2 = modbus.ReadFileFunction(2,file,rec,len,dstmsk);

--если ошибки нет

if err == 0 then

--то выводим в журнал сообщения с полученными значениями

server.Message("d1=",dstdata[1],",",dstdata[2],",",dstdata[3],

",",dstdata[4], ",",dstdata[5]);

server.Message("d2=",dstdata2[1],",",dstdata2[2],",",dstdata2[3],

",",dstdata2[4], ",",dstdata2[5]);

else

server.Message("error");

end

В первых девяти строках идет объявление и инициализация переменных.

Переменная file – таблица, которая содержит два элемента – две единицы, это номера

файлов. Поскольку мы считываем две записи из файла 1, то элемент содержит две

единицы. Переменная rec – таблица, которая содержит номера записей – 1 и 10.

Переменная len – таблица, которая содержит количество требуемых элементов из

каждой записи. Из каждой записи мы будем запрашивать по 5 элементов. Переменные

dstdata и dstdata2 – это таблицы, в которые мы будем записывать принимаемые

значения.

Переменные dstmsk1 и dstmsk2 – это таблицы, которые содержат маску

принимаемых данных из записи. Сейчас в обе маски записана строковая константа

"int16:5:10". Первый элемент строковой константы – тип принимаемого числа, в данном

случае – Int16. Второй элемент строки – количество элементов данного типа, в данном

случае 5. Третий элемент строки – чередование байт, в данном случае старшим байтом

вперед (для элементов Byte и String задавать чередование байт не нужно). По этой маске

Page 58: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 58

© InSAT Company 2009-2012

будут дешифрованы принятые из записи байты, и такие элементы будут содержаться в

выходной таблице (в данном случае в таблицах dstdata и dstdata2).

В нашем случае мы запрашивает 5 однотипных элементов, если бы нам требовалось

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

маски. Например, нам требуется получить от устройства данные в следующем порядке – 3

числа Int16, 2 числа Float, 4 числа Byte. В этом случае маска запроса будет выглядеть

следующим образом:

local dstmsk1 = {"int16:3:10", "Float:2:32107654","Byte:4"}

Далее объявляется переменная dstmsk – это таблица, содержащая в себе таблицы

масок преобразований. То есть в данную таблицу мы поместили две таблицы с масками

преобразования для каждой записи.

Далее в строке происходит непосредственное выполнение запроса на чтение записи

из файла.

err,dstdata,dstdata2 = modbus.ReadFileFunction(2,file,rec,len,dstmsk);

В функцию передаются аргументы: количество получаемых таблиц (количество

запрашиваемых записей), номера файлов, номера записей, длина записей, и маски

запроса – переменные которые мы предварительно инициализировали нужными

данными. Функция присваивает переменной err флаг ошибки, а переменным dstdata и

dstdata2 полученные от прибора и отформатированные по маске значения.

Затем, если ошибки нет (err равен нулю), то в лог журнала выводятся значения

элементов массива.

5.6 Получение архива станции управления Электон-5 по функции 0x14.

Мы рассмотрели пример получения данных по функции 0x14, теперь рассмотрим

пример опроса по данной функции реального устройства. В качестве примера рассмотрим

станцию управления электродвигателем (частотный преобразователь) ЭЛЕКТОН-05

фирмы «Электон».

Данное устройство применяется для управления насосами добычи нефти и

водозаборов, дымососов, промышленной вентиляции. Описание устройства можно найти

на сайте производителя:

http://www.elekton.ru/elekton05.shtml

Данный частотный преобразователь снабжен специальным управляющим

контроллером, который реализует следующие функции - плавный пуск и торможение

двигателя, отображение информации на дисплее, ПИД-регулятор, контроль состояния

внешних устройств подключенным к входам, а также реализует возможность обмена по

интерфейсам RS-232 и RS-485 для диспетчерского контроля и управления.

Page 59: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 59

© InSAT Company 2009-2012

Опрос устройства производится по протоколу Modbus RTU. Текущие значения

читаются функциями 0x03 и 0x04, для чтения архивов поддержана функция 0x14.

Подробное описание реализации Modbus протокола в данном устройстве можно скачать

со страницы загрузки:

http://www.elekton.ru/soft/elekton-09.35.zip

Для лучшего восприятия описываемого далее материала советуем скачать данный

документ, и ознакомится с ним.

Рассмотрим создание OPC конфигурация для чтения архива данного устройства.

Архив Электон-05 представляет собой набор файлов, количество файлов

регулируется – пользователь может установить его равным 512, 1536, 2560 или 3584.

Каждый файл состоит из записей, длина одной записи одинакова – 128 байт (64 регистра),

длина файла ограничена 512 байтами (256 регистров), таким образом, каждый файл

содержит 4 записи.

Электон-05 содержит пять типов записей, имеющих собственную структуру.

1 тип записи – периодическая запись текущих параметров, представляет собой

запись о состоянии параметров в определенный момент.

2 тип записи – запись о событии Пуск-Стоп и отключении напряжения.

3 тип записи – запись об изменении уставки.

4 тип записи – запись о событии изменении даты времени.

5 тип записи – запись о прочих событиях (блокировка, реверс, сброс счетчиков

наработки).

Записи записываются в файл по определенному событию или периодически. Все

записи записываются одним непрерывным массивом, то есть в одном файле могут

оказаться разные типы записей. Чтобы определить какого типа считанная запись, в ней

содержится номер-идентификатор (см. ниже). Запись циклическая – на место самой

старой записи, записывается новая.

Для примера мы будем считывать запись первого типа. Запись первого типа имеет

следующую структуру:

Page 60: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 60

© InSAT Company 2009-2012

Примечание. Данная таблица взята из документа «Приложение 2» официальной документации (ссылка выше). Таблица взята не полностью – поскольку пример учебный, мы ограничимся четырьмя параметрами – Выходная частота (регистр 12), Ток двигателя фаза А, В и С (регистры 13 – 15).

Рассмотрим таблицу. Регистры 0 и 1 – номер записи в хронологии (порядковый

номер записи), его мы использовать не будем. Регистр 2 – тип записи, как мы писали

ранее, записи всех типов записываются в файлы. Чтобы определить какого типа считанная

запись нужно проверить данный регистр – если он равен единице, то мы считали нужную

нам запись. Регистр 3 – версия программы, его мы использовать также не будем. Регистр

4 и 5 – время записи, с этой меткой времени нам и нужно записать значения в архив.

Время представлено в формате UnixTime – количество секунд с полуночи 1 января 1970

года. Остальные регистры – это считываемые параметры.

Создадим конфигурацию для OPC. Добавим узел, тип «COM». Добавим в него

устройство, тип Modbus, назовем его «Электон», период опроса установим равным 10

минутам.

В устройство добавим тег, тип – Server_Only, тип данных - float, тип доступа –

только чтение, включим HDA доступ, и отключим автоматическую запись.

Page 61: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 61

© InSAT Company 2009-2012

Выполним тиражирование данного тега – сделаем 4 копии, и поменяем у них

имена.

Скрипт мы расположим в устройстве – мы будем опрашивать прибор, разбирать

значения и раскладывать их по тегам при помощи функции

server.WriteTagByRelativeNameToHda(). Включим выполнение скрипта после чтения у

устройства и откроем редактор.

Код будем создавать в функции OnBeforeReading()

Page 62: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 62

© InSAT Company 2009-2012

Приведем весь код, необходимый для чтения всего архива устройства, а затем

разберем его.

Листинг 1.22 Чтения архива устройства «Электон-05»

local err,dstlen; --объявление переменных

local file={}; --объявление таблицы файлов

local rec={}; --объявление таблицы записей

local len={64}; --объявление и инициализация таблицы количества

принимаемых элементов

local dstdata = {}; --объявление таблицы для принятия данных из записи 1

--маска первой таблицы (для записи 1). Тип значения Int16

local dstmsk1 =

{"uint32:1:32107654","uint16:1:10","uint16:1:10","uint32:1:32107654",

"uint16:59:10"};

--количество элементов – 5, чередование старшим байтов вперед

local dstmsk = {dstmsk1}; --создание таблицы таблиц с масками

--цикл опроса всего архива

for i=1, 512, 1 do --цикл опроса по файлам

for j=0, 3, 1 do --цикл опроса по записям

file[1] = i; --номер файла равен номеру циклу

rec[1]=j*len[1]; --номер записи равен номеру подцикла умноженный на

длину

--записи

--чтение архива из устройства

err,dstdata = modbus.ReadFileFunction(1,file,rec,len,dstmsk);

if err==0 then

--ошибки нет

--проверим соответствие типа записи. Тип записи должен быть равен

единице

if dstdata[2]==1 then --второй элемент массива - тип записи

--тип соответствует можно записать значения в теги

local date_time=dsdata[4]; --значение времени хранится в элементе 4

local ts=time.TimeToTimeStamp(date_time,0); --преобразуем время в

TimeStamp

local Frequency=dstdata[11]/100; --частота хранится в элементе 11.

local Current_A=dstdata[12]/10; --ток в фазе А

local Current_B=dstdata[13]/10; --ток в фазе B

local Current_C=dstdata[14]/10; --ток в фазе C

Page 63: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 63

© InSAT Company 2009-2012

--записываем значения в теги

server.WriteTagByRelativeNameToHda("Частота",Frequency,OPC_QUALITY_GOOD,t

s);

server.WriteTagByRelativeNameToHda("Ток фазы А",Current_A,

OPC_QUALITY_GOOD,ts);

server.WriteTagByRelativeNameToHda("Ток фазы В",Current_B,

OPC_QUALITY_GOOD,ts);

server.WriteTagByRelativeNameToHda("Ток фазы С",Current_C,

OPC_QUALITY_GOOD,ts);

end;

end;

end;

end;

Первая часть кода в целом не требует подробного комментирования – по большей

части он знаком по предыдущему примеру. В строчке:

local dstmsk1 =

{"uint32:1:32107654","uint16:1:10","uint16:1:10","uint32:1:32107654",

"uint16:59:10"};

Мы инициализируем значение масками принимаемых элементов. Первый

принимаемый элемент - номер записи в хронологии; тип – беззнаковый целый, 4 байта, то

есть uint32. Второй и третий принимаемый элементы – тип записи и версия программы

соответственно; тип – беззнаковый целый, 2 байта, то есть uint16, два подряд идущих

элемента. Четвертый элемент – время; тип – беззнаковый целый, 4 байта, то есть uint32.

Остальные элементы – все имеют тип 2 байтового беззнакового целого, таких элементов в

записи 59.

При помощи двух циклов ( for i=1, 512, 1 do и for j=0, 3, 1 do) мы по очереди

опрашиваем все записи в устройстве.

Отдельно стоит пояснить строчку

rec[1]=j*len[1]; --номер записи равен номеру подцикла умноженный на длину

Номер записи определяется как номер подцикла опроса записей умноженный на

длину записи. Дело в том, что файл в контроллере представляет собой массив байт. В

запросе мы должны указать номер байта, с которого нужно начать считывание файла.

Таким образом, в первом цикле мы указываем номер регистра 0, и длину записи –

количество считываемых регистров (64 регистра или 128 байт), то есть считываем байты 0

– 127. В следующем цикле мы считываем из файла байты с номерами 128 – 255, и так

далее.

После этого вызывается функция:

err,dstdata = modbus.ReadFileFunction(1,file,rec,len,dstmsk);

как и в предыдущем примере вызывает запрос к устройству по функции 0x14, но в

данном случае возвращаемая таблица только одна.

Page 64: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 64

© InSAT Company 2009-2012

Затем после проверки на ошибку и на корректность записи (напомним, что нам

нужна запись только с идентификатором записи равным единице), можно осуществлять

запись архивного значения в тег. Сначала нам нужно получить время в формате

TimeStamp, для этого используются следующие строки:

local date_time=dsdata[4]; --значение времени хранится в элементе 4

local ts=time.TimeToTimeStamp(date_time,0); --преобразуем время в

TimeStamp

В первой строке мы присваиваем переменной date_time значение из четвертого

элемента массива (фактически этого можно не делать, а вставить данный элемент прямо

в функцию time.TimeToTimeStamp). Эту строчку мы поясним подробнее.

Прибор передает значение в формате UnixTime. Данный формат, представляет

собой 4 байтовое целое число, в котором содержится количество секунд с полуночи 1

января 1970 года. В нашем OPC сервере время представлено в таком же формате,

поэтому никакого дополнительного преобразования не требуется. Если в вашем приборе

значение представлено в другом формате (например, как в предыдущем примере –

разбито на несколько регистров, или в каком-либо другом виде), то вам может

потребоваться произвести преобразования, чтобы привести время к корректному типу.

После получения времени получаются значения интересующих нас параметров –

частоты, и токов в фазах (элементы 11 – 14).

local Frequency=dstdata[11]/100; --частота хранится в элементе 11. local

Current_A=dstdata[12]/10; --ток в фазе А

local Current_B=dstdata[13]/10; --ток в фазе B

local Current_C=dstdata[14]/10; --ток в фазе C

Частота делится на 100, а токи на 10 – так как эти параметры представлены в

приборе как целые числа со смещением запятой.

После этого значения записываются в соответствующие теги при помощи функции

server.WriteTagByRelativeNameToHda(). Первый аргумент – относительное имя тега,

второй – значение, третий – признак качества, четвертый – метка времени.

Поскольку пример учебный, то он упрощен. Например, количество файлов задано

статично равным 512, для реальной конфигурации лучше сделать это значение

регулируемым, используя дополнительные свойства.

Не реализован случай, когда не все записи заполнены данными. Для определения

количества файлов хронологии у Электоон-05, есть специальная расширенная Modbus

функция – 0x2B. Используя функцию OPC сервера server.SendAndReceiveDataByMask(),

которую мы рассматривали ранее, можно опрашивать устройство и по данной

расширенной функции.

Также может показаться не совсем корректным опрос всего архива устройства –

возможно, разработчик посчитает, что лучше сохранять номер последнего считанного

Page 65: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 65

© InSAT Company 2009-2012

файла, и при следующем опросе начинать считывать с него. В этом случае потребуется

сохранение номера последнего считанного файла, например, в текстовый файл на

компьютере. Для работы с файлами в MasterOPC также есть специальные функции.

Условием завершения опроса устройства может стать, например, ситуация когда

новая полученная метка времени имеет более ранее значение чем предыдущая –

поскольку используется кольцевой буфер, то это означает что мы начали считывать

старые записи. Такую проверку также несложно производить.

Все перечисленные улучшения читатель может попробовать сделать самостоятельно

– для закрепления изученного материала.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/Электон.mbc

6 Сопровождение разработанного кода

Итак, вы написали код на языке Lua реализующий определенный протокол.

Созданное устройство можно положить в библиотеку и импортировать в новые

конфигурации.

Но как быть, если после создания конфигурации в процессе работы обнаружится

ошибка (или неточность) в коде? Если конфигураций и устройств немного, то можно

внести изменения вручную, однако если планируется тиражировать разработанную

конфигурацию, то необходимо обеспечить возможность быстрого внесения изменений.

Для этого в MasterOPC есть специальные средства.

Для упрощения изменений кода, можно вынести код (или часть кода) из

конфигурации OPC сервера в отдельный файл, а затем исполнять код из этого файла.

Рассмотрим пример. Создадим конфигурацию, добавим в него узел типа Program, а

в него – Устройство.

Включим у устройства исполнение скрипта. Напишем небольшой код в секции

OnBeforeReading()

Листинг 1.23

function OnBeforeReading()

Page 66: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 66

© InSAT Company 2009-2012

local k=10;

local b=20;

server.Message("k+b=",k+b);

end

В данном коде мы объявляем две переменных, а затем выводим в журнал

сообщений сервера их сумму. Вынесем данный код в файл.

Создадим текстовый файл в папке ресурсов OPC сервера – в папке «Modules».

Примечание. В Windows XP путь будет c:\Documents and Settings\All Users\Application Data\Insat\MasterOPC Universal Modbus Server\MODULES\

В Windows 7 путь - C:\Users\All Users\Application Data\InSAT\MasterOPC Universal Modbus Server\ MODULES\

Назовем текстовый файл «Script.lua». Откроем его обычным блокнотом, вырежем

код из функции и вставим его в файл.

Сохраним файл.

Теперь сделаем, чтобы скрипт OPC сервера исполнял этот код. Для этого в Lua есть

специальная функция – dofile(). В качестве аргумента в функцию нужно передать путь к

текстовому файлу, в котором находится код.

Чтобы определить путь к папке ресурсов OPC сервера в дереве функций есть

специальные константы.

Константа GLOBALPROGRAMDATAFOLDER возвращает путь к папке ресурсов OPC

сервера. Константа LUA_MODULES возвращает путь к папке «Modules» в папке ресурсов –

Page 67: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 67

© InSAT Company 2009-2012

в этой папку рекомендуется размещать Lua-скрипты. Константа C_MODULES возвращает

путь к папке «CMODULES» в папке ресурсов.

Передадим в функцию dofile() путь к нашему файлу со скриптом.

Листинг 1.24 Исполнение кода из файла

-- функция,выполняющаяся перед чтением тегов

function OnBeforeReading()

dofile (LUA_MODULES.."\\Script.lua");

end

LUA_MODULES – это константа, которая возвращает путь к папке «Modules».

Оператор «..» - оператор конкатенации строк (сложения строк). С помощью данного

оператора мы получим полный путь до нашего файла со скриптом.

Теперь можно запустить режим исполнения и убедится, что скрипт функционирует

корректно – в журнал пишутся сообщения.

Изменим код в файле. Поскольку мы вызываем dofile() в теле функции

OnBeforeReading(), то OPC сервер можно не останавливать – OPC сервер загружает код из

файла каждый цикл опроса, поэтому после сохранения изменений сервер загрузит уже

исправленный код.

Посмотрим журнал. Результат изменился.

Page 68: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 68

© InSAT Company 2009-2012

Таким образом, мы изменили код скрипта, но при этом ничего не меняли в самой

конфигурации OPC сервера.

Может потребоваться, что часть кода должна исполняться в коде функции в OPC

сервере, а часть – в коде, исполняемом из файла. Например, для рассмотренного

примера «Контравт», в каждом теге канала используется разный номер регистра, но

остальной код остается таким же. В этом случае нужно объявить переменную, которая

будет хранить номер регистра, в код OPC сервера, а остальной код вынести в файл.

Для этого необходимо объявить переменную как глобальную (не указывать перед

ней оператор local). Например, создадим переменную z и инициализируем ее числом 15.

Листинг 1.25

function OnBeforeReading()

z=15;

dofile (LUA_MODULES.."\\Script.lua");

end

Теперь мы можем использовать эту переменную в коде, исполняемом из файла

«Script.lua». Например, вычтем значение переменной z из суммы k и b.

Запустим режим исполнения и убедимся в корректности результата.

Page 69: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 69

© InSAT Company 2009-2012

Возможна ситуация, когда код скрипта находится не только в теле одной функции

OnBeforeReading(), но и в функциях OnInit(), а также в пользовательских функциях. В этом

случае можно вынести в файл весь код скрипта.

Например, рассмотрим код:

Листинг 1.26

-- инициализация

function OnInit()

server.Message("Вызываем функцию OnInit()");

end

-- деинициализация

function OnClose()

server.Message("Вызываем функцию OnClose()");

end

-- функция,выполняющаяся перед чтением тегов

function OnBeforeReading()

server.Message("Вызываем функцию OnBeforeReading()");

end

-- функция,выполняющаяся после чтения тегов

function OnAfterReading()

server.Message("Вызываем функцию OnAfterReading()");

end

Каждая функция кода, генерирует сообщение в лог. Выделяем и вырезаем весь код

скрипта, и вставляем его в файл «Script.lua»

В редакторе скрипта OPC сервера оставляем только функцию выполнения кода из

файла:

Page 70: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 70

© InSAT Company 2009-2012

Запустим режим исполнения

Как мы видим, код исполняется из файла корректно – при старте была вызвана

функция OnInit(), затем стали вызываться функции OnBeforeReading() и OnAfterReading().

Примечание. В данном случае код из файла загружается в память один раз при старте, после чего начинает исполняться. Если в код скрипта были внесены изменения после запуска OPC сервера в режим исполнения, то код будет загружен снова только перезагрузки OPC сервера.

Таким образом, исполнение скрипта из файла, ускоряет внесение изменений в код.

Если у вас в конфигурации несколько десятков устройств содержат идентичный код,

который необходимо модифицировать, то будет достаточно изменить код в файле

скрипта, не меняя конфигурацию самих устройств.

Примечание. Конфигурация OPC сервера с полным кодом данного примера приложена к документации и находится в папке /OPC конфигурации/FromFile.mbc.

7 Заключение

В данной документации было разобрано несколько примеров по поддержке

различных протоколов – как расширений Modbus, так и оригинальных протоколов фирм-

производителей. Кроме того, OPC-сервер имеет множество дополнительных функций, не

описанных в данной документации – выполнение SQL-запросов, запись и считывание

файлов, запуск приложений.

OPC сервер также поддерживает возможность вызова dll библиотек, написанных на

C++ (пример с комментариями входит в комплект приложений к данной документации), -

это позволяет вынести часть кода из Lua скрипта, а также позволяет опрашивать

Page 71: Modbus Universal MasterOPC сервер Реализация …...©InSAT Company 2009-2012 Modbus Universal MasterOPC сервер Реализация собственных протоколов

Modbus Universal MasterOPC сервер. Реализация собственных протоколов Стр. 71

© InSAT Company 2009-2012

сторонние приложения - все это позволит расширить границы применений MasterOPC.

Описание остальных функций вы можете найти в справочной системе OPC сервера. Вы

также можете задавать свои вопросы по электронной почте в техническую поддержку

компании ИнСАТ – [email protected]

Надеемся, что данная документация оказалась вам полезной, и поможет вам в

решении различных задач.