Files
lighthub/lighthub/modules/out_mercury.cpp
Andrey 6019aa41bb MBUS refactoring,
AC Haier tolerance for comm errors(CRC check),
MERCURY - not blocking MBUS if failed (delay added if login fail)
Kernel fixes (flags, Locks)
Multivent to multichannel virtual AC converted (interim)
PID library forked and changed
2025-01-22 02:18:25 +03:00

481 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//Спецификация протокола тут
//https://ftp.owen.ru/CoDeSys3/04_Library/05_3.5.11.5/02_Libraries/02_vendor_protocols/merkuriy-sistema-komand-sogl-1-2021-02-02.pdf
// Использованы фрагменты кода отсюда: https://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/arduino-mega-merkurii-2032t-rs485
#ifdef MERCURY_ENABLE
#include "modules/out_mercury.h"
#include "Arduino.h"
#include "options.h"
#include "utils.h"
#include "Streaming.h"
#include "item.h"
#include <ModbusMaster.h>
#include "main.h"
#include <HardwareSerial.h>
extern aJsonObject *modbusObj;
extern ModbusMaster node;
extern short modbusBusy;
extern void modbusIdle(void) ;
extern uint32_t mbusSlenceTimer;
bool out_Mercury::getConfig()
{
if (!store || !item || !item->itemArg || (item->itemArg->type != aJson_Array) || aJson.getArraySize(item->itemArg)<6)
{
errorSerial<<F("Mercury: config failed:")<<(bool)store<<F(",")<<(bool)item<<F(",")<<(bool)item->itemArg<<F(",")<<(item->itemArg->type != aJson_Array)<<F(",")<< (aJson.getArraySize(item->itemArg)<6)<<endl;
return false;
}
aJsonObject * delayObj=aJson.getArrayItem(item->itemArg, 5);
if (delayObj) pollingInterval = delayObj->valueint;
else pollingInterval = 10000;
return true;
}
void out_Mercury::initLine(bool full)
{
int baud;
serialParamType serialParam;
aJsonObject * serialParamObj=aJson.getArrayItem(item->itemArg, 2);
if (serialParamObj && serialParamObj->type == aJson_String) serialParam = str2SerialParam(serialParamObj->valuestring);
else serialParam = SERIAL_8N1;
aJsonObject * baudObj=aJson.getArrayItem(item->itemArg, 1);
if (baudObj && baudObj->type == aJson_Int && baudObj->valueint) baud = baudObj->valueint;
else baud = 9600;
#if defined (__SAM3X8E__)
modbusSerial.begin(baud, static_cast <USARTClass::USARTModes> (serialParam));
#elif defined (ARDUINO_ARCH_ESP8266)
modbusSerial.begin(baud, static_cast <SerialConfig>(serialParam));
#elif defined (ESP32)
if (full) modbusSerial.begin(baud, (serialParam),MODBUS_UART_RX_PIN,MODBUS_UART_TX_PIN);
else modbusSerial.updateBaudRate (baud); //Some terrible error in ESP32 core with uart reinit
#else
modbusSerial.begin(baud, (serialParam));
#endif
//debugSerial<<F("Mercury: ")<< baud << F(" <")<< serialParam<< F("> addr:")<<item->getArg(0)<<endl;
node.begin(item->getArg(0), modbusSerial);
}
int out_Mercury::Setup()
{
abstractOut::Setup();
if (!store) store= (mercuryPersistent *)item->setPersistent(new mercuryPersistent);
if (!store)
{ errorSerial<<F("Mercury: Out of memory")<<endl;
return 0;}
store->timestamp=millisNZ();
if (getConfig())
{
infoSerial<<F("Mercury: config loaded ")<< item->itemArr->name<<endl;
setStatus(CST_INITIALIZED);
store->lastSuccessTS = 0;
initLine(true);
return 1;
}
else
{ errorSerial<<F("Mercury: config error")<<endl;
setStatus(CST_FAILED);
return 0;
}
}
int out_Mercury::Stop()
{
debugSerial.println("Mercury: De-Init");
disconnectMercury();
delete store;
item->setPersistent(NULL);
setStatus(CST_UNKNOWN);
store = NULL;
return 1;
}
short out_Mercury::connectMercury()
{
if (!item || !item->itemArg || modbusBusy) return RET_INTERROR;
uint8_t buffer[10];
uint8_t ret;
modbusBusy=1;
initLine();
buffer[1]=1; //Login
aJsonObject * levelObj=aJson.getArrayItem(item->itemArg, 3);
if (levelObj && levelObj->type == aJson_Int) buffer[2] = levelObj->valueint;
else errorSerial<<F("Mercury: wrong accesslevel")<<endl;
aJsonObject * passObj=aJson.getArrayItem(item->itemArg, 4);
if (passObj && passObj->type == aJson_String && (strlen (passObj->valuestring) == 6)) memcpy((char*) buffer+3,passObj->valuestring,6);
else if (passObj && passObj->type == aJson_Array && (aJson.getArraySize (passObj) == 6))
{
//debugSerial<<F("Mercury: Pass :");
for(short i=0;i<6;i++)
{
aJsonObject * digitObj=aJson.getArrayItem(passObj,i);
if (digitObj && digitObj->type==aJson_Int)
{
buffer[3+i]=digitObj->valueint;
//debugSerial<< digitObj->valueint<< F(",");
}
}
debugSerial<<endl;
}
else errorSerial<<F("Mercury: passwort must be ether String[6] or Array[6]")<<endl;
debugSerial<<F("Mercury: Connect ")<< item->itemArr->name<<F(" level:")<< buffer[2] ;//<< F(" pass:")<< passObj->valuestring;
ret=node.ModbusRawTransaction(buffer,9,4);
modbusBusy=0;
if (!ret) ret = buffer[1];
debugSerial<<F(" res:")<< ret << endl;
mbusSlenceTimer = millisNZ();
return ret;
}
short out_Mercury::disconnectMercury()
{
if (!item || !item->itemArg || modbusBusy) return RET_INTERROR;
uint8_t buffer[4];
uint8_t ret;
modbusBusy=1;
initLine();
buffer[1]=2; //Close
ret=node.ModbusRawTransaction(buffer,2,4);
modbusBusy=0;
if (!ret) ret = buffer[1];
debugSerial<<F("Mercury: Disconnect ")<< item->itemArr->name<<F(" res:")<< ret << endl;
mbusSlenceTimer = millisNZ();
return ret;
}
///////////////////// команды//////////////////////////////////////////////////////
//byte testConnect[] = { 0x00, 0x00 }; //тест связи
//byte Sn[] = { 0x00, 0x08, 0x05 }; //запрос сетевого адреса
//byte Open[] = { 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; //открыть канал с паролеи
//byte Close[] = { 0x00, 0x02 }; // закрыть канал (а то ждать 4 минуты)
//byte activPower[] = { 0x00, 0x05, 0x00, 0x00 }; //суммарная энергия прямая + обратная + активная + реактивная
//byte ind[] = { 0x00, 0x03, 0x01, 0x01, 0x00, 0x01, 0x00, 0x1F, 0x00, 0x1F, 0x00 }; //маска индикации работает только для автоматического режима
//byte Angle[] = { 0x00, 0x08, 0x16, 0x51 }; // углы между фазами 1и2, 1и3, 2и3
////////////////////////подпрограмма чтения показаний счётчика////////////////////////////////////
#define M_BEGIN_YESTERDAY 0xD0 // показания на начало вчерашних суток
#define M_BEGIN_TODAY 0x21 // показания на начало сегодняшних суток
#define M_NOW 0x00 // показания на сейчас
//2.5.17 Запросы на чтение массивов регистров накопленной энергии
short out_Mercury::getCounters(byte when, String topic, int divisor)
{
if (!item || !item->itemArg || modbusBusy) return RET_INTERROR;
modbusBusy=1;
initLine();
uint8_t response[19]{}; //1+4+4+4+4+2
response[1]=0x05;
response[2]=when; // показания на начало вчерашних суток 0xD0
uint8_t ret=node.ModbusRawTransaction(response, 4 , sizeof(response));
if (!ret)
{
unsigned long r = 0;
r |= (unsigned long)response[2]<<24;
r |= (unsigned long)response[1]<<16;
r |= (unsigned long)response[4]<<8;
r |= (unsigned long)response[3];
publishTopic(item->itemArr->name, (float) r/1000,(topic+"/Active").c_str());
//Обратную (5-8) пропускаем
r = 0;
r |= (unsigned long)response[10]<<24;
r |= (unsigned long)response[9]<<16;
r |= (unsigned long)response[12]<<8;
r |= (unsigned long)response[11];
publishTopic(item->itemArr->name, (float) r/1000,(topic+"/Reactive").c_str());
//Обратную (13-16) пропускаем
};
debugSerial<<F("Mercury: getCounters ")<< item->itemArr->name << F(" res:")<< ret << endl;
modbusBusy=0;
mbusSlenceTimer = millisNZ();
return ret;
}
/////////////////////////углы, напряжения токи по фазам//////////////////////////////////////////////////
#define VOLTAGE 0x11 //0x11 напряжения пофазно, 100делитель
#define CURRENT 0x21 //0x21 токи пофазно
#define ANGLES 0x51 //0x51 углы между напряжениями фаз, 100делитель
#define M_COSF 0x31
#define M_ANGLE_DEF 0x30
#define M_PWR 0x00
short out_Mercury::getCurrentVal12(byte param, String topic, int divisor)
{
if (!item || !item->itemArg || modbusBusy) return RET_INTERROR;
modbusBusy=1;
initLine();
uint8_t response[12]{};
response[1]=0x08; //2.6 Запросы на чтение параметров
response[2]=0x16; //2.6.16 Чтение вспомогательных параметров: мгновенной активной,реактивной, полной мощности,
// фазных и линейных, напряжений,тока, коэффициента мощности,частоты, небаланса
// Ответ 12 (9) двоичных байт. Два старших разряда старшего байта указывают положение вектора полной мощности и
// должны маскироваться (см. п. 1.5)
response[3]=param;
uint8_t ret=node.ModbusRawTransaction(response, 4 , sizeof(response));
if (!ret)
{
long r = 0;
r |= (long)response[1]<<16;
r |= (long)response[3]<<8;
r |= (long)response[2];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/A").c_str());
r = 0;
r |= (long)response[4]<<16;
r |= (long)response[6]<<8;
r |= (long)response[5];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/B").c_str());
r=0;
r |= (long)response[7]<<16;
r |= (long)response[9]<<8;
r |= (long)response[8];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/C").c_str());
}
debugSerial<<F("Mercury: getCurrentVal12 ")<< item->itemArr->name << F(" res:")<< ret << endl;
modbusBusy=0;
mbusSlenceTimer = millisNZ();
return ret;
}
//cosSN(); //0x31 углы между напряжениями фазы и током COSf
/////////////////////////COSf по фазам//////////////////////////////////////////////////
short out_Mercury::getCurrentVal15(byte param, String topic, int divisor)
{
if (!item || !item->itemArg || modbusBusy) return RET_INTERROR;
modbusBusy=1;
initLine();
uint8_t response[15]{};
response[1]=0x08;
response[2]=0x16;
response[3]=param;
uint8_t ret=node.ModbusRawTransaction(response, 4 , sizeof(response));
if (!ret)
{
long r = 0;
r |= (long)response[1]<<16;
r |= (long)response[3]<<8;
r |= (long)response[2];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/sum").c_str());
r = 0;
r |= (long)response[4]<<16;
r |= (long)response[6]<<8;
r |= (long)response[5];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/A").c_str());
r = 0;
r |= (long)response[7]<<16;
r |= (long)response[9]<<8;
r |= (long)response[8];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/B").c_str());
r=0;
r |= (long)response[10]<<16;
r |= (long)response[12]<<8;
r |= (long)response[11];
publishTopic(item->itemArr->name, (float) r/divisor,(topic+"/C").c_str());
}
debugSerial<<F("Mercury: getCurrentVal15 ")<< item->itemArr->name << F(" res:")<< ret << endl;
modbusBusy=0;
mbusSlenceTimer = millisNZ();
return ret;
}
int out_Mercury::Poll(short cause)
{
//bool lineInitialized = false;
if (cause==POLLING_SLOW) return 0;
if (modbusBusy || ( mbusSlenceTimer && !isTimeOver(mbusSlenceTimer,millis(),100))) return 0;
if (Status() == CST_FAILED) return 0;
if (!getConfig()) return 0;
switch (Status())
{
case CST_INITIALIZED:
if (isTimeOver(store->timestamp,millis(),10000UL)) setStatus(M_CONNECTING);
break;
case M_CONNECTING:
switch (connectMercury())
{
case RET_SUCCESS: setStatus(M_CONNECTED);
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(CST_FAILED);
break;
default:
store->timestamp = millisNZ();
setStatus(CST_INITIALIZED);
}
break;
case M_CONNECTED:
if (isTimeOver(store->lastSuccessTS,millis(),240000UL)) setStatus(CST_INITIALIZED);
if (isTimeOver(store->timestamp,millis(),pollingInterval)) setStatus(M_POLLING1);
break;
case M_POLLING1:
switch (getCurrentVal12(VOLTAGE,"/voltage",100))
{
case RET_SUCCESS: setStatus(M_POLLING2);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
case M_POLLING2:
switch (getCurrentVal12(CURRENT,"/current",1000))
{
case RET_SUCCESS: setStatus(M_POLLING3);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
case M_POLLING3:
switch (getCurrentVal12(ANGLES,"/angles",100))
{
case RET_SUCCESS: setStatus(M_POLLING4);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
case M_POLLING4:
switch (getCounters(M_NOW,"/counters",1000))
{
case RET_SUCCESS: setStatus(M_POLLING5);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
case M_POLLING5:
switch (getCurrentVal12(M_PWR+1,"/power",100))
{
case RET_SUCCESS: setStatus(M_POLLING6);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
case M_POLLING6:
switch (getCurrentVal12(M_COSF,"/cosf",1000))
{
case RET_SUCCESS: setStatus(M_CONNECTED);
store->timestamp=millisNZ();
store->lastSuccessTS=millisNZ();
break;
case RET_NOT_CONNECTED: setStatus(M_CONNECTING);
break;
default:
setStatus(CST_INITIALIZED);
}
break;
}
return pollingInterval;
};
int out_Mercury::getChanType()
{
return CH_MBUS;
}
//!Control unified item
int out_Mercury::Ctrl(itemCmd cmd, char* subItem, bool toExecute,bool authorized)
{
if (!store) return -1;
int suffixCode = cmd.getSuffix();
int res = -1;
return res;
}
#endif