Аннотация.
Данная статья является продолжением моей предыдущей статьи и в ней рассмотрена внешняя компонента для обмена с внешними источниками данных посредством последовательного порта на примере однофазного электросчётчика Меркурий.
Эта работа может быть полезна там, где требуется точный учёт электроэнергии, анализ её потребления и произведённых с помощью неё полезных продуктов. Например оценка эффективности работы отдельных устройств (станков, станков с ЧПУ, инкубатора и т. д.), эффективность теплицы (затрачиваемая энергия на обогрев, полив) и многое другое.
Кратко про электросчётчики «Меркурий 206».
Данный счётчик весьма популярен, по нему несложно найти примеры подключения и примеры кода для считывания информации.
Счётчик подключается к считывающему устройству четырьмя проводами: линии A, B интерфейса RS-485, питание и «земля».
Скорость обмена данными 9600 бит/сек без контроля чётности.
Напряжение питания 5 вольт.
Теперь надо немного остановиться на протоколе обмена данного электросчётчика.
Протокол обмена со счётчиком довольно простой и в чём-то похож на Modbus RTU. Описание протокола можно найти здесь. Форматы запросов и ответов совпадают.
Название поля |
Условное обозначение |
Длина поля (байт) |
Примечание |
Сетевой адрес |
ADDR |
4 |
Серийный номер счётчика (передаётся старшим байтом вперёд) |
Команды |
CMD |
1 |
Двоичный код команды |
Данные |
|
0…17 |
Может отсутствовать (в зависимости от типа и назначения пакета) |
Контрольная сумма |
CRC16 |
2 |
2-х байтовый циклический избыточный код, вычисляемый по всем предшествующим байтам данного пакета (передаётся младшим байтом вперёд) |
CRC16 рассчитывается при помощи полинома 0xA001 и стартового значения 0xFFFF, точно так же, как в протоколе Modbus RTU.
Приведу пример функций расчёта контрольной суммы, которую использовал я в этом проекте:
int MODBUS_CRC16_merc_206 (unsigned char data[], int I){
// возвращает общую длинну полезных данных с контрольной суммой
// запись контрольной суммы data[i] младьший байт data [i+1] старший байт
unsigned short int crc = 0xFFFF;
for(int i=0; i<I; i++){
crc ^= data[i];
for(int j=0; j<8; j++){
if(crc & 1){
crc >>= 1;
crc ^= 0xA001;
}
else crc >>= 1;
}
}
for(int i =I; i<=(I+1); i++){
data[i]= crc;
crc=crc>>8;
}
return I+2;
}
Функция производит расчет контрольной суммы данных, находящихся с 1-й позиции массива по I и добавляет её в этот же массив за I (это необходимо учитывать при создании массива). Возвращает длину полезных данных, включая контрольную сумму.
В проекте использованы следующие команды для этого счётчика:
0x27 (чтение тарифов) в ответ получаем пакет из 16 байт данных по 4 байта на каждый тариф. При этом данные передаются в двоично-десятичном формате с точностью до 10 Вт.
Запрос:
00 00 04 D2 27 79 7B
где:
00 00 04 D2 - адрес счётчика
27 - команда
79 7В - CRC16
Ответ:
00 00 04 D2 27 00 02 27 50 00 02 27 50 00 02 27 50 00 02 27 50 A5 FB
где:
00 00 04 D2 - адрес счётчика
27 - команда
00 02 27 50 - тариф №1 (227,5 кВт х ч)
00 02 27 50 - тариф №2 (227,5 кВт х ч)
00 02 27 50 - тариф №3 (227,5 кВт х ч)
00 02 27 50 - тариф №4 (227,5 кВт х ч)
A5 FB - CRC16
0x63 (чтение U, I, P) получаем значения сетевого напряжения, тока и мощности потребления. Данные также передаются в двоично-десятичном формате.
Запрос:
00 00 04 D2 63 79 48
где:
00 00 04 D2 - адрес счётчика
63 - команда
79 48 - CRC16
Ответ:
00 00 04 D2 63 23 00 01 50 00 01 00 A5 FB
где:
00 00 04 D2 - адрес счётчика
63 - команда
23 00 - напряжение (230,0 В)
01 50 - ток нагрузки (1,5 А)
00 01 00 - потребляемая мощность (100Вт)
A5 FB - CRC16
0x81 (F, текущего тарифа и битовых флагов) из ответа данной команды нас интересуют только первые два байта, представляющие частоту в двоично-десятичном формате.
Запрос:
00 00 04 D2 81 F9 01
где:
00 00 04 D2 - адрес счётчика
81 - команда
F9 01 - CRC16
Ответ:
00 00 04 D2 81 50 50 3A 00 00 00 00 00 00 CC A4
где:
00 00 04 D2 - адрес счётчика
81 - команда
50 50 - частота (50,5 Гц)
3A - битовые флаги
00 00 00 00 00 00 - резерв
CC A4 - CRC16
В данном протоколе обмена отсутствует какой-либо префикс начала пакета данных, поэтому для приёма и декодирования сообщения необходимо перед отправкой запроса чистить буфер порта от мусора, в противном случае может возникнуть проблема с поиском начала массива данных.
После отправки запроса необходимо выждать определённое время, прежде чем читать из буфера ответ. Это связано с тем, что у счётчика есть таймаут, необходимый для приёма и обработки команды (запроса). Например, для скорости 9600 бод время таймаута равно 5 мс (по умолчанию).
Внешняя компонента.
Внешняя компонента выполнена по технологии Native API для 1С 8.3, с помощью С++ на базе стандартного шаблона. Компиляторы GCC, MVSC. Как указано ранее предназначена для сбора и документирования данных от однофазных счётчиков Меркурий 200, 201, 203 (кроме Меркурий 203.2TD), 206 Посредством интерфейсов RS-485, CAN.
Класс компоненты описан в файлах Mercury206Addin.h и Mercury206Addin.cpp
Содержит реализацию 5-и методов, позволяющих получать данные о израсходованной энергии, мгновенных значениях напряжения, тока, частоты и мощности на нагрузке.
В файлах Mercury206.h и Mercury206.cpp реализованы функции для работы со счётчиком.
Реализованные методы компоненты
Наименование команды |
Значение |
|
Англоязычное |
Русское |
|
Version |
ВерсияКомпоненты |
Получение информации о версии компоненты |
СonnectPort |
ПодключитьПорт |
Подключение последовательного порта |
TimeOut |
ТаймАут |
Установка значения задержки между запросом и чтением полученных данных от электросчётчика
|
Request |
Запрос |
Запрос данных от электросчётчика (совмещает в себе функцию чтения данных от счётчика)
|
DisablePort |
ОтключитьПорт |
Отключение последовательного порта
|
Подробное описание методов компоненты
ВерсияКомпоненты; возвращает строку с версией компоненты.
Пример использования:
Компонента.ВерсияКомпоненты;
Пример ответа функции
1.0.1 Linux x64
ПодключитьПорт (порт - строка, скорость порта - число); Подключает указанный порт, в случае успеха возвращает истину, в случае возникновения ошибки — ложь, и описание ошибки. Вызывается однократно при подключении порта.
Порт - строка в случае с Linux содержит путь к файлу порта (например "/dev/ttyUSB1").
Скорость порта - число может принимать значения 1200, 2400, 4800, 9600, 19200, 38400. В электросчётчике по умолчанию установлено 9600.
Пример использования для Linux:
РезультатПодключения = Компонента.ПодключитьПорт("/dev/ttyUSB1", 9600);
Пример использования для Windows:
РезультатПодключения = Компонента.ПодключитьПорт("COM4", 9600);
ТаймАут (задержка микросекунды — число); функция ничего не возвращает, необходима для записи значения временного интервала в микросекундах между отправкой команды электросчётчику и чтением ответа из буфера порта. Её применение обусловлено тем, что электросчётчик получив запрос тратит определённое время на его обработку. В связи с этим ответ приходит с определённой задержкой, и возможно появление ситуации, когда чтение буфера порта будет произведено до того как в нём появится ответ. В документации производителя указаны значения 20 миллисекунд (я устанавливал 50 мс). Вызывается однократно при подключении компоненты.
Пример использования:
Компонента.ТаймАут (50000);
Запрос (сетевой адрес счётчика - строка, номер команды - число); возвращает строку данных ответа от электросчётчика.
сетевой адрес счётчика - строка, последние 8 цифр серийного номера электросчётчика (указано в тех. документации).
номер команды — число от 1 до 3.
Описание команд
1 - получение значений израсходованной электроэнергии. (доступна только в relese версиях)
2 - получение мгновенных значений напряжения, тока, мощности на нагрузке.
3 - получение мгновенного значения частоты напряжения сети.
Пример использования:
РезультатЗапроса = Компонента.Запрос("12345",1);
Пример ответа функции запрос на команду №1:
/456.4/98735.3/2643.4/897.0!
информация по 4-м тарифам (израсходованная электроэнергия кВт) разделена дробной чертой, признак конца строки - восклицательный знак.
Пример ответа функции запрос на команду №2:
/223.3/5.4/1204.2!
/напряжение - Вольт /ток - Ампер /мощность на нагрузке - Ватт !
Пример ответа функции запрос на команду №3:
/51.7!
/ частота - Герц !
ОтключитьПорт(); Отключает уже подключенный порт, возвращает истину в случае успеха.
Пример использования:
Компонента.ОтключитьПорт();
Требования
Компонента тестировалась на версии платформы 1С 8.3.20.1789
Операционные системы Windows 7, Windows 10, Linux Mint, Ubuntu для 64 битных систем.
Подключение электросчётчиков
Подключение электросчётчиков необходимо производить в соответствии с рекомендациями производителя ссылка используя интерфейсы RS-485 или CAN.
При использовании преобразователей сторонних производителей необходимо следить за тем, что питание интерфейса счётчика не должно превышать +5В. Так же если питание с преобразователя не реализовано, то его необходимо подать с отдельного стабилизированного источника питания.
На стороне компьютера преобразователь подключается к RS-232, либо к USB с использованием соответствующих драйверов.
На одной линии RS-485, CAN могут находится несколько устройств. Максимальное количество устройств на линии необходимо уточнять из документации на используемые преобразователи RS-485.
Подключение файла компоненты
Подключение файла компоненты, открытие порта и настройка таймаута.
Функция ПодключитьКомпоненту(Компонента) Экспорт
ПутьКБиблиотеке="/home/del/libMercury206Addin.so";
Подключено = ПодключитьВнешнююКомпоненту(ПутьКБиблиотеке, "libextDLib", ТипВнешнейКомпоненты.Native);
Если НЕ Подключено Тогда
Сообщить ("Не удалось подключить компоненту");
Иначе
Сообщить ("Компонента подключена ");
Попытка
Компонента = новый ("AddIn.libextDLib.Mercury206");
Сообщить ("Создан объект компоненты");
Попытка
результат = Компонента.ПодключитьПорт("/dev/ttyUSB1", 9600);
Компонента.ТаймАут(50000);
Исключение
Сообщить ("Проблемы подключения порта");
КонецПопытки;
Исключение
Сообщить ("неудалось создать объект компоненты");
КонецПопытки;
КонецЕсли;
Возврат 1;
Конецфункции
Выполнение запроса к электросчётчику (с сетевым адресом 34) на получение данных по израсходованной электроэнергии.
Функция Получить(Компонента) Экспорт
результат = Компонента.Запрос("34", 1);
Сообщить (результат);
КонецФункции
Отключение порта после выполнения всех необходимых запросов.
Функция Отключить(Компонента) Экспорт
результат = Компонента.ОтключитьПорт();
Сообщить (результат);
КонецФункции