Сбор показаний с счетчиков "Меркурий 200-203"

Первым делом оказалось, что счетчики золотые, на сайте кроме паспортов и примитивного описания ничего более нет.
Это и понятно, НПК-Инкотекс взял за основу пакет Modbus RTU (убрал из него все логичное и полезное — решив использовать фиксированные поля, без кода ошибок и т.д.,) расширив поле ID до 4-х байт, оставшееся было наречено «собственным протоколом». Что позволяет продавать (в единственном лице) конвертеры из «собственного протокола» в нормальный Modbus.
Первым делом был скачан с офсайта конфигуратор:

Подключен сам счетчик (USB-485), и подсмотрены сами пакеты:


Оказалось, что конфигуратор совершал 7 запросов, а именно:
— 0x28 (Чтение идентификационных данных счетчика)
— 0x2F (Чтение псевдосерийного номера)
— 0x29 (Чтение напряжения на литиевой батарее)
— 0x2C (Чтение времени последнего включения напряжения)
— 0x2B (Чтение времени последнего отключения напряжения)
и неизвестные 0x66-65
Далее было раздобыто описание «протокола» и стало совсем все понятно.
В запросе ID это первые четыре байта (0x000D1F или 859973), байт команды и два байта CRC.
В ответе так же ID, команда и набор параметров (длина представляет магическую цифру, определяется самим параметром, может быть от 0 до 17 байт).
С пакетом вроде все понятно, осталось написать программу )
Собрал платку на STM32F107VC (CAN с USB был желателен), 4-е MAX485 на столько же USART-UART портов, + выводы DE/RE
Написал структуры, простите за названия:
/* по порядку портов */
#define MOD0 0x00
#define MOD1 0x01
#define MOD2 0x02
#define MOD3 0x03
#define CAN 0x04
/* биты окончания отправки */
#define END_MOD0 0x01
#define END_MOD1 0x02
#define END_MOD2 0x04
#define END_MOD3 0x08
#define END_CAN 0x10
/* главная структура, логика опроса */
typedef struct sInterface
{
uint8_t ModEnab; // флаг начала цикла опроса
uint8_t ModDev0; // сколько всего устройств опрашивается, линия 1
uint8_t ModDev1; // сколько всего устройств опрашивается, линия 2
uint8_t ModDev2; // сколько всего устройств опрашивается, линия 3
uint8_t ModDev3; // сколько всего устройств опрашивается, линия 4
uint8_t CANDev; // CAN, на перспективу )
uint8_t CurDevMod0; // текущий номер устройства, линия 1
uint8_t CurDevMod1; // текущий номер устройства, линия 1
uint8_t CurDevMod2; // текущий номер устройства, линия 1
uint8_t CurDevMod3; // текущий номер устройства, линия 1
uint8_t CurRegDevMod0; // текущий номер регистра, линия 1
uint8_t CurRegDevMod1; // текущий номер регистра, линия 1
uint8_t CurRegDevMod2; // текущий номер регистра, линия 1
uint8_t CurRegDevMod3; // текущий номер регистра, линия 1
uint8_t AllRegDev0; // всего параметров (за сколько раз опрашивается одно устройство), линия 1
uint8_t AllRegDev1; // всего параметров (за сколько раз опрашивается одно устройство), линия 1
uint8_t AllRegDev2; // всего параметров (за сколько раз опрашивается одно устройство), линия 1
uint8_t AllRegDev3; // всего параметров (за сколько раз опрашивается одно устройство), линия 1
uint8_t ModEndFlag; // флаг окончания опроса
}sInterface;
/* указываются ID и тип счетчика */
typedef struct
{
uint32_t ID;
uint8_t Type;
}sDeviceModbus;
/* для отправки-приема данных */
typedef struct
{
uint8_t EnabFlag;
uint8_t TX_Buf[30];
uint8_t RX_Buf[110];
uint8_t RX_cou_byte;
uint8_t TX_cou_byte;
uint8_t RxBusy;
uint32_t ID;
uint8_t SelBaud;
uint16_t BaudRate[4];
uint8_t LengPar;
uint8_t TypeDevice;
uint8_t Option;
} sModbus_Struct;
/* линии ведь 4 */
extern sModbus_Struct ModDataStruct[4];
extern sDeviceModbus DevModLin0[240];
extern sDeviceModbus DevModLin1[240];
extern sDeviceModbus DevModLin2[240];
extern sDeviceModbus DevModLin3[240];
extern sInterface Interface;
Далее описал сам счетчик:
/* просто идентификатор */
#define DEVICE_MERC_200 70
/* полученные данные дальше отправляются на сервер, их там надо еще разобрать */
/* не пожалел 5 байт, это должно облегчить задачу */
const char Dev_string_ME200[] = {"ME200"};
const char Dev_string_ME203[] = {"ME203"};
/* сами параметры, char-ы так же вставляются в пакет, помогая распознавать параметр */
#define MERC200_LENG 10
const char DevTable_Merc200[MERC200_LENG][3]=
{
{'G','A', 0x20},
{'I','C', 0x21},
{'C','W', 0x26},
{'V','A', 0x27},
{'U','D', 0x28},
{'L','U', 0x2B},
{'L','Y', 0x2C},
{'H','T', 0x2E},
{'P','I', 0x2F},
{'M','S', 0x32}
};
#define MERC203_LENG 10
const char DevTable_Merc203[MERC203_LENG][3]=
{
{'G','A', 0x21},
{'I','C', 0x21},
{'C','W', 0x26},
{'V','A', 0x27},
{'U','D', 0x28},
{'L','U', 0x2B},
{'L','Y', 0x2C},
{'H','T', 0x2E},
{'P','I', 0x2F},
{'M','S', 0x32}
};
/* осталось самое сложное*/
void vModBusControl(void *pvParameters)
{
uint8_t i;
uint8_t Line;
sDeviceModbus * PDevModLine;
uint8_t * PModDev;
uint8_t *PcurRegDevMod;
uint8_t *PcurDevMod;
uint8_t * PAllRegDev;
uint8_t PEnd_MOD;
for( ;; )
{
if(Interface.ModEnab == ON)
{
Line = NULL;
/* until cycle is completed*/
while(Interface.ModEndFlag != (END_MOD0 | END_MOD1 | END_MOD2 | END_MOD3 | END_CAN))
{
/* в моем случае было важно не допустиить превышения размера пакета */
/* тогда можно отправлять пакет в середине цикла */
/* if size net Pack > MAX_SIZE_NET_PACK */
if(NetParam.NetBufCounter > MAX_SIZE_NET_PACK)
{
/* then pause request interface, and send network packet */
StructConveer.Net |= CONV_NET_SEND;
while(StructConveer.Net & CONV_NET_SEND){ vTaskDelay(10); };
}
ClearModTXRXbuf();
getLine:
/* Get Line */
switch(Line)
{
case MOD0:
PDevModLine = &DevModLin0[Interface.CurDevMod0];
PModDev = &Interface.ModDev0;
PcurRegDevMod = &Interface.CurRegDevMod0;
PAllRegDev = &Interface.AllRegDev0;
PcurDevMod = &Interface.CurDevMod0;
PEnd_MOD = END_MOD0;
break;
case MOD1:
PDevModLine = &DevModLin1[Interface.CurDevMod1];
PModDev = &Interface.ModDev1;
PcurRegDevMod = &Interface.CurRegDevMod1;
PAllRegDev = &Interface.AllRegDev1;
PcurDevMod = &Interface.CurDevMod1;
PEnd_MOD = END_MOD1;
break;
case MOD2:
PDevModLine = &DevModLin2[Interface.CurDevMod2];
PModDev = &Interface.ModDev2;
PcurRegDevMod = &Interface.CurRegDevMod2;
PAllRegDev = &Interface.AllRegDev2;
PcurDevMod = &Interface.CurDevMod2;
PEnd_MOD = END_MOD2;
break;
case MOD3:
PDevModLine = &DevModLin3[Interface.CurDevMod3];
PModDev = &Interface.ModDev3;
PcurRegDevMod = &Interface.CurRegDevMod3;
PAllRegDev = &Interface.AllRegDev3;
PcurDevMod = &Interface.CurDevMod3;
PEnd_MOD = END_MOD3;
break;
case CAN:
PEnd_MOD = END_CAN;
break;
}
/* Line */
/* if Line is use */
if(*PModDev != 0)
{
if(PDevModLine->ID != 0)
{
if(*PcurDevMod < *PModDev)
{
ReadyPackModbus((sModbus_Struct*)&ModDataStruct[Line], PDevModLine, &ModDataStruct[Line].LengPar, *PcurRegDevMod, PAllRegDev);
}
}
/* one approach */
if(*PAllRegDev == 1)
{
if(*PcurDevMod >= *PModDev)
{
Interface.ModEndFlag |= PEnd_MOD;
}
else
{
(*PcurDevMod) ++;
}
}
/* some approaches reading */
else
{
/* Mercurii & misk */
if(*PcurRegDevMod >= *PAllRegDev)
{
*PcurRegDevMod = 0;
if(*PcurDevMod >= *PModDev)
{
Interface.ModEndFlag |= PEnd_MOD;
}
else
{
(*PcurDevMod)++;
}
}
else
{
(*PcurRegDevMod)++;
}
}
}
else
{
Interface.ModEndFlag |= PEnd_MOD;
}
i= NULL;
/* start send request */
if((ModDataStruct[0].LengPar != 0)&&(!(Interface.ModEndFlag & END_MOD0)))
{
i |= 0x01;
}
if((ModDataStruct[1].LengPar != 0)&&(!(Interface.ModEndFlag & END_MOD1)))
{
i |= 0x02;
}
if((ModDataStruct[2].LengPar != 0)&&(!(Interface.ModEndFlag & END_MOD2)))
{
i |= 0x04;
}
if((ModDataStruct[3].LengPar != 0)&&(!(Interface.ModEndFlag & END_MOD3)))
{
i |= 0x08;
}
if(Line != CAN)
{
/* GOTO - а-та-та*/
Line ++;
goto getLine;
}
Modbus_Transmit_Select(i);
vTaskDelay(200);
Line = NULL;
if(ModDataStruct[0].RX_cou_byte != 0)
{
InsertingRetDatatoPacket((sModbus_Struct*)&ModDataStruct[0], 0);
ModDataStruct[0].RX_cou_byte = 0;
}
if(ModDataStruct[1].RX_cou_byte != 0)
{
InsertingRetDatatoPacket((sModbus_Struct*)&ModDataStruct[1], 1);
ModDataStruct[1].RX_cou_byte = 0;
}
if(ModDataStruct[2].RX_cou_byte != 0)
{
ModDataStruct[2].RX_cou_byte = 0;
}
if(ModDataStruct[3].RX_cou_byte != 0)
{
ModDataStruct[3].RX_cou_byte = 0;
}
}
/* здесь можно отправить собравшийся пакет */
ClearModTXRXbuf();
/* очистка всего */
Interface.CurDevMod0 =0;
Interface.CurDevMod1 =0;
Interface.CurDevMod2 =0;
Interface.CurDevMod3 =0;
Interface.CurRegDevMod0 =0;
Interface.CurRegDevMod1 =0;
Interface.CurRegDevMod2 =0;
Interface.CurRegDevMod3 =0;
Interface.ModEnab =0;
Interface.ModEndFlag = NULL;
InstallAlarm();
}
vTaskDelay(200);
}
}
для работы с USART DMA пока не стал писать, все в прерывании:
/* линия 1, остальные так же */
void MODBUS_1_USART_IntHand(void)
{
if(MODBUS_1_USART->SR & USART_SR_RXNE)
{
MODBUS_1_USART->SR &=~ USART_SR_RXNE;
ModDataStruct[0].RX_Buf[ModDataStruct[0].RX_cou_byte] = MODBUS_1_USART->DR;
ModDataStruct[0].RX_cou_byte ++;
ModDataStruct[0].RxBusy = 1;
}
if(MODBUS_1_USART->SR & USART_SR_TC)
{
MODBUS_1_USART->SR &=~ USART_SR_TC;
if(ModDataStruct[0].TX_cou_byte < ModDataStruct[0].LengPar)
{
MODBUS_1_USART->DR = ModDataStruct[0].TX_Buf[ModDataStruct[0].TX_cou_byte];
ModDataStruct[0].TX_cou_byte++;
}
else
{
Modbus_Receive_Select(END_MOD0);
}
}
}
Так готовится запрос:
void ReadyPackModbus(sModbus_Struct *buf, sDeviceModbus * insert, uint8_t *len, char Parameter, uint8_t * AllRegDev)
{
uint8_t i=0;
uint16_t crc;
buf->ID = insert->ID;
switch(insert->Type)
{
case DEVICE_MERC_200:
buf->TypeDevice = DEVICE_MERC_200;
buf->TX_Buf[i++] = ((insert->ID & 0xFF000000)>>24);
buf->TX_Buf[i++] = ((insert->ID & 0x00FF0000)>>16);
buf->TX_Buf[i++] = ((insert->ID & 0x0000FF00)>>8);
buf->TX_Buf[i++] = insert->ID & 0x000000FF;
buf->TX_Buf[i++] = DevTable_Merc200[Parameter][2];
buf->Option = Parameter;
*AllRegDev = MERC200_LENG;
*len = 5;
break;
}
crc = getCRC((uint8_t*)&buf->TX_Buf[0], *len);
buf->TX_Buf[i++] = crc & 0x00FF;
buf->TX_Buf[i++] = (crc & 0xFF00)>>8;
*len = i;
}
А так «набивается» пакет:
void InsertingRetDatatoPacket(sModbus_Struct* Data, uint8_t Line)
{
uint8_t i, i2;
uint8_t Len;
char TypeDevString[5];
char TempString[10];
switch(Data->TypeDevice)
{
case DEVICE_MERC_200:
/* 18 байт, с запасом для всех параметров */
Len = 9;
strcpy((char*)&TypeDevString, (char*)&Dev_string_ME200);
break;
}
/* Number Interface Line */
ConvIntToAscii((uint8_t)Line, CAPT_BYTE1, (char*)&TempString, 0);
NetParam.NetBuf[NetParam.NetBufCounter++] = '-';
NetParam.NetBuf[NetParam.NetBufCounter++] = TempString[0];
NetParam.NetBuf[NetParam.NetBufCounter++] = TempString[1];
/* Type Device */
NetParam.NetBuf[NetParam.NetBufCounter++] = '#';
NetParam.NetBuf[NetParam.NetBufCounter++] = TypeDevString[0];
NetParam.NetBuf[NetParam.NetBufCounter++] = TypeDevString[1];
NetParam.NetBuf[NetParam.NetBufCounter++] = TypeDevString[2];
NetParam.NetBuf[NetParam.NetBufCounter++] = TypeDevString[3];
NetParam.NetBuf[NetParam.NetBufCounter++] = TypeDevString[4];
NetParam.NetBuf[NetParam.NetBufCounter ++] = '#';
/* ID Device (4 bytes) */
ConvIntToAscii((uint32_t)Data->ID, CAPT_BYTE4, (char*)&TempString, 0);
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[0];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[1];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[2];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[3];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[4];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[5];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[6];
NetParam.NetBuf[NetParam.NetBufCounter ++] = TempString[7];
NetParam.NetBuf[NetParam.NetBufCounter++] = '-';
i2 = 3;
switch(Data->TypeDevice)
{
case DEVICE_MERC_200 :
NetParam.NetBuf[NetParam.NetBufCounter++] = DevTable_Merc200[Data->Option][0];
NetParam.NetBuf[NetParam.NetBufCounter++] = DevTable_Merc200[Data->Option][1];
i2 = 0;
break;
case DEVICE_MERC_203 : break;
}
for(i=0;i<Len*2;i++)
{
ConvIntToAscii(Data->RX_Buf[i2], CAPT_BYTE1, (char*)&TempString, 0);
NetParam.NetBuf[NetParam.NetBufCounter++] = TempString[0];
NetParam.NetBuf[NetParam.NetBufCounter++] = TempString[1];
i2 ++;
}
}
В таком виде пакет отсылается на сервер:

- +3
- 02 сентября 2014, 17:09
- khomin
- 1
Файлы в топике:
Меркурий.zip
Что позволяет продавать (в единственном лице) конвертеры из «собственного протокола» в нормальный Modbus.Справедливости ради, Меркурий продаёт универсальные адаптеры USB — RS-485 (Меркурий 221). Так же как и не скрывает, что счётчики работают и с другими адаптерами 485й шины. Вот счётчики с CAN-шиной наоборот, к CAN-шине отношения не имеют. Там, по сути, тот же RS-485, только уровнями сигнала отличаются, на практике достаточно поменять проводки местами и CAN-счётчик можно опрашивать по RS-485 сети, дома так и сделал. А вот протокол у них свой, но то что за основу взят модбас — очень удобно, к счётчикам на одну линию можно вешать другие модбас-устройства, не опасаясь конфликтов. Я, правда, с трехфазными Меркурий 230 имел дело. Классная штука. Считает большое количество параметров, в т.ч. активную/реактивную/полную/коэффициент мощности по каждой фазе, обновляя раз в секунду. Дома такой использую в качестве трехканального однофазного счетчика, можно смотреть потребление разных приборов.
Протокол в своё время выпросил у разработчика, скинули без особых проблем, только поинтересовались, нафига он мне.
У них есть ещё фишка передачи данных по сети питания.
Пытались использовать их счётчики для центролизованного учёта потребления узлов связи через с2000-интернет. Но практика показала что это бессмысленно. Поскольку все кто потом должен проверять показания замеряли расход за месяц умножали его на количество узлов и и месяцев в году и так прописывали в контракте с электросетевой компанией.
В. Общем такие штуки наверно удобны в для жеков в новых томах. А в свЗи оказались лишними.
Пытались использовать их счётчики для центролизованного учёта потребления узлов связи через с2000-интернет. Но практика показала что это бессмысленно. Поскольку все кто потом должен проверять показания замеряли расход за месяц умножали его на количество узлов и и месяцев в году и так прописывали в контракте с электросетевой компанией.
В. Общем такие штуки наверно удобны в для жеков в новых томах. А в свЗи оказались лишними.
Еще у них похоже проблемы в понимании протоколов, например в описании на «Меркурий 230-AR» интерфейс указан — «CAN», в каком месте он «CAN» мне не понятно )
В добавок пакет не совместим с другими счетчиками, если собрать линию из 200-203-206 и 230, работать оно 100% не будет (проверено), ровно как если использовать 200 и другие устройства (с интерфейсом Modbus), ибо пакет не стандартный, косяки и проблемы обязательно будут, чего в коммерческом учете не хотелось бы )
В добавок пакет не совместим с другими счетчиками, если собрать линию из 200-203-206 и 230, работать оно 100% не будет (проверено), ровно как если использовать 200 и другие устройства (с интерфейсом Modbus), ибо пакет не стандартный, косяки и проблемы обязательно будут, чего в коммерческом учете не хотелось бы )
Добрый день!
Правильно ли я догадываюсь, что
а) Речь идет об обычном бытовом электросчетчике, который обслуживает, в том числе, и мою квартиру.
Вот таком:

б) У него есть незапломбированный и незапаролированный на уровне софта разъем через который к нему можно подключиться и считать как минимум показания, а как максимум — прочие параметры — текущий ток, мощность и т.п.
Правильно ли я догадываюсь, что
а) Речь идет об обычном бытовом электросчетчике, который обслуживает, в том числе, и мою квартиру.
Вот таком:

б) У него есть незапломбированный и незапаролированный на уровне софта разъем через который к нему можно подключиться и считать как минимум показания, а как максимум — прочие параметры — текущий ток, мощность и т.п.
- denis_vishniakov
- 12 сентября 2014, 19:12
- ↓
Бли-и-и-ин… А уж обрадовался — счас замонстрячу онлайн монитор энергопотребления… нет в мире совершенства…
- denis_vishniakov
- 13 сентября 2014, 17:09
- ↑
- ↓
поставьте второй счетчик )
в разрыв между первым и проводкой )
И можно будет мониторить )
Потребляют они вроде немного…
в разрыв между первым и проводкой )
И можно будет мониторить )
Потребляют они вроде немного…
Не, если что-то свое ставить в разрыв, то это можно что-нить типа бесконтактного датчика тока в корпусе на DIN-рейку. Отдельный счетчик слишком большой и слишком дорогой… А основное удовольствие — не ходить на лестничную клетку, чтобы снять показания — теряется, потому что кто его знает, синхронно они будут считать или нет.
Учитывая, что момент переключения между тарифами точно не синхронизируешь…
Учитывая, что момент переключения между тарифами точно не синхронизируешь…
- denis_vishniakov
- 13 сентября 2014, 19:03
- ↑
- ↓
Комментарии (15)
RSS свернуть / развернуть