Files
lighthub/lighthub/modules/out_modbus.cpp

1199 lines
56 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.

#ifndef MBUS_DISABLE
#include "modules/out_modbus.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) ;
uint32_t mbusSlenceTimer = 0;
struct reg_t
{
const char verb[4];
const uint8_t id;
};
/*
struct serial_t
{
const char verb[4];
const serialParamType mode;
};
*/
#define PAR_I16 1
#define PAR_I32 2
#define PAR_U16 3
#define PAR_U32 4
#define PAR_I8H 5
#define PAR_I8L 6
#define PAR_U8H 7
#define PAR_U8L 8
#define PAR_TENS 9
#define PAR_100 10
#define PAR_COIL 11
const reg_t regSize_P[] PROGMEM =
{
{ "i16", (uint8_t) PAR_I16 },
{ "i32", (uint8_t) PAR_I32 },
{ "u16", (uint8_t) PAR_U16 },
{ "u32", (uint8_t) PAR_U32 },
{ "i8h", (uint8_t) PAR_I8H },
{ "i8l", (uint8_t) PAR_I8L },
{ "u8h", (uint8_t) PAR_U8H },
{ "u8l", (uint8_t) PAR_U8L },
{ "x10", (uint8_t) PAR_TENS },
{ "100", (uint8_t) PAR_100 }
// { "bit", (uint8_t) PAR_COIL }
} ;
#define regSizeNum sizeof(regSize_P)/sizeof(reg_t)
/**
* @brief Меняет порядок байтов в 16-битном числе.
* @param x Входное число.
* @return Число с изменённым порядком байтов.
*/
uint16_t swap (uint16_t x) {return ((x & 0xff) << 8) | ((x & 0xff00) >> 8);}
/**
* @brief Преобразует строку в тип регистра.
* @param str Строка с типом регистра.
* @return Код типа регистра.
*/
int str2regSize(char * str)
{
for(uint8_t i=0; i<regSizeNum && str;i++)
if (strcmp_P(str, regSize_P[i].verb) == 0)
return pgm_read_byte_near(&regSize_P[i].id);
return (int) PAR_I16;
}
//TODO irs etc
/**
* @brief Получает имя параметра по номеру регистра.
* @param parameters JSON-объект с параметрами.
* @param regnum Номер регистра.
* @return Имя параметра или NULL.
*/
char * getParamNameByReg(aJsonObject * parameters, int regnum)
{
if (!parameters) return NULL;
aJsonObject * i = parameters->child;
while (i)
{
aJsonObject * regObj = aJson.getObjectItem(i, "reg");
if (regObj && regObj->type == aJson_Int && regObj->valueint == regnum)
{
debugSerial<<F("MBUS: ")<<i->name<<F(" added by num ")<<regnum<<endl;
return i->name;
}
i=i->next;
}
return NULL;
}
/**
* @brief Проверяет наличие действия в JSON-объекте.
* @param execObj JSON-объект.
* @return true, если действие найдено, иначе false.
*/
bool haveAction(aJsonObject * execObj)
{
aJsonObject * j = execObj->child;
switch (execObj->type)
{
case aJson_Object:
while (j)
{
if (j->name && *j->name && (*j->name != '@')) return true;
j=j->next;
}
break;
case aJson_Array:
while (j)
{
if (haveAction(j)) return true;
j=j->next;
}
}
return false;
}
/**
* @brief Загружает и применяет конфигурацию Modbus.
* @return true, если конфигурация успешно загружена, иначе false.
*/
bool out_Modbus::getConfig()
{
// Retrieve and store template values from global modbus settings
if (!store || !item || !item->itemArg || (item->itemArg->type != aJson_Array) || aJson.getArraySize(item->itemArg)<2 || !modbusObj)
{
errorSerial<<F("MBUS: config failed:")<<(bool)store<<F(",")<<(bool)item<<F(",")<<(bool)item->itemArg<<F(",")<<(item->itemArg->type != aJson_Array)<<F(",")<< (aJson.getArraySize(item->itemArg)<2)<<endl;
return false;
}
aJsonObject * templateIdObj = aJson.getArrayItem(item->itemArg, 1);
if (templateIdObj->type != aJson_String || !templateIdObj->valuestring)
{
errorSerial<<F("MBUS: Invalid template.")<<endl;
return false;
}
aJsonObject * templateObj = aJson.getObjectItem(modbusObj, templateIdObj->valuestring);
if (! templateObj)
{
errorSerial<<F("MBUS: Modbus template not found: ")<<templateIdObj->valuestring<<endl;
return false;
}
aJsonObject * serialParamObj=aJson.getObjectItem(templateObj, "serial");
if (serialParamObj && serialParamObj->type == aJson_String) store->serialParam = str2SerialParam(serialParamObj->valuestring);
else store->serialParam = SERIAL_8N1;
aJsonObject * baudObj=aJson.getObjectItem(templateObj, "baud");
if (baudObj && baudObj->type == aJson_Int && baudObj->valueint) store->baud = baudObj->valueint;
else store->baud = 9600;
#if defined (__SAM3X8E__)
modbusSerial.begin(store->baud, static_cast <USARTClass::USARTModes> (store->serialParam));
#elif defined (ARDUINO_ARCH_ESP8266)
modbusSerial.begin(store->baud, static_cast <SerialConfig>(store->serialParam));
#elif defined (ARDUINO_ARCH_ESP32)
modbusSerial.begin(store->baud, (store->serialParam),MODBUS_UART_RX_PIN,MODBUS_UART_TX_PIN);
#else
modbusSerial.begin(store->baud, (store->serialParam));
#endif
aJsonObject * pollObj=aJson.getObjectItem(templateObj, "poll");
if (pollObj && pollObj->type == aJson_Object)
{
store->pollingRegisters=aJson.getObjectItem(pollObj, "regs");
store->pollingIrs=aJson.getObjectItem(pollObj, "irs");
store->pollingCoils=aJson.getObjectItem(pollObj, "coils");
store->poolingDiscreteIns=aJson.getObjectItem(pollObj, "dins");
aJsonObject * delayObj= aJson.getObjectItem(pollObj, "delay");
if (delayObj) store->pollingInterval = delayObj->valueint;
else store->pollingInterval = 1000;
}
else {store->pollingRegisters=NULL;store->pollingInterval = 1000;store->pollingIrs=NULL;}
store->parameters=aJson.getObjectItem(templateObj, "par");
// initializing @S where needed
if (store->parameters)
{
// Creating for parameters where prefetch required
debugSerial<<F("Adding prefetch regs:")<<endl;
aJsonObject * i = store->parameters->child;
while (i)
{
aJsonObject * prefetchObj = aJson.getObjectItem(i, "prefetch");
if (prefetchObj && prefetchObj->type == aJson_Boolean && prefetchObj->valuebool)
{
createLastMeasured(i->name);
}
i=i->next;
}
debugSerial<<F("Adding referred regs:")<<endl;
i = store->parameters->child;
// Creating for parameters used in references from another parameters
while (i)
{
aJsonObject * mapObj = aJson.getObjectItem(i, "map");
if (mapObj)
{
aJsonObject * defObj = aJson.getObjectItem(mapObj, "def");
if (defObj && defObj->type == aJson_Int)
{
createLastMeasured(getParamNameByReg(store->parameters,defObj->valueint));
}
if (defObj && defObj->type == aJson_String)
{
createLastMeasured(defObj->valuestring);
}
}
i=i->next;
}
debugSerial<<F("Adding regs with actions:")<<endl;
// Check - if action configured for object and create
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj)
{
i = itemParametersObj->child;
while (i)
{
if (haveAction(i)) createLastMeasured(i);
i=i->next;
}
}
}
return true;
//store->addr=item->getArg(0);
}
/**
* @brief Создаёт поле для хранения последнего измеренного значения по имени параметра.
* @param name Имя параметра.
* @return true, если успешно, иначе false.
*/
int out_Modbus::createLastMeasured(char * name)
{
if (!name) return false;
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return createLastMeasured(aJson.getObjectItem(itemParametersObj,name));
}
/**
* @brief Создаёт поле для хранения последнего измеренного значения по JSON-объекту.
* @param execObj JSON-объект параметра.
* @return true, если успешно, иначе false.
*/
int out_Modbus::createLastMeasured(aJsonObject * execObj)
{
if (!execObj) return false;
aJsonObject * markObj = execObj;
if (execObj->type == aJson_Array)
{
markObj = execObj->child;
//storeLastValue = true;
}
if (!markObj) return false;
aJsonObject *lastMeasured = aJson.getObjectItem(markObj,"@S");
if (lastMeasured) return false;
debugSerial<<F("MBUS: Add @S: ")<<execObj->name<<endl;
aJson.addNumberToObject(markObj, "@S", (long) 0);
lastMeasured = aJson.getObjectItem(markObj,"@S");
if (!lastMeasured) return false;
lastMeasured->subtype |= MB_VALUE_OUTDATED;
return true;
}
/**
* @brief Получает объект последнего измеренного значения по имени параметра.
* @param name Имя параметра.
* @return JSON-объект или NULL.
*/
aJsonObject * out_Modbus::getLastMeasured(char * name)
{
if (!name) return NULL;
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return getLastMeasured (aJson.getObjectItem(itemParametersObj,name));
}
/**
* @brief Получает объект последнего измеренного значения по JSON-объекту.
* @param execObj JSON-объект параметра.
* @return JSON-объект или NULL.
*/
aJsonObject * out_Modbus::getLastMeasured(aJsonObject * execObj)
{
if (!execObj) return NULL;
if (execObj->type == aJson_Array) execObj = execObj->child;
aJsonObject *lastMeasured = aJson.getObjectItem(execObj,"@S");
if (lastMeasured && lastMeasured->type == aJson_Int) return lastMeasured;
return NULL;
}
/**
* @brief Инициализирует канал Modbus и загружает конфигурацию.
* @return 1 при успехе, 0 при ошибке.
*/
int out_Modbus::Setup()
{
abstractOut::Setup();
if (!store) store= (mbPersistent *)item->setPersistent(new mbPersistent);
if (!store)
{ errorSerial<<F("MBUS: Out of memory")<<endl;
return 0;}
store->timestamp=millisNZ();
if (getConfig())
{
infoSerial<<F("MBUS: config loaded ")<< item->itemArr->name<<endl;
setStatus(CST_INITIALIZED);
return 1;
}
else
{ errorSerial<<F("MBUS: config error")<<endl;
setStatus(CST_FAILED);
Stop();
return 0;
}
}
/**
* @brief Останавливает работу канала Modbus и освобождает ресурсы.
* @return 1 при успехе.
*/
int out_Modbus::Stop()
{
debugSerial.print("MBUS: De-Init ");
debugSerial.println(item->itemArr->name);
if (store) delete store;
item->setPersistent(NULL);
store = NULL;
return 1;
}
/**
* @brief Читает данные из Modbus-устройства.
* @param reg Номер регистра.
* @param regType Тип регистра.
* @param count Количество регистров.
* @return true, если чтение успешно, иначе false.
*/
bool readModbus(uint16_t reg, int regType, int count)
{
uint8_t result;
switch (regType) {
case MODBUS_HOLDING_REG_TYPE:
result = node.readHoldingRegisters(reg, count);
break;
case MODBUS_COIL_REG_TYPE:
result = node.readCoils(reg, count);
break;
case MODBUS_DISCRETE_REG_TYPE:
result = node.readDiscreteInputs(reg, count);
break;
case MODBUS_INPUT_REG_TYPE:
result = node.readInputRegisters(reg, count);
break;
default:
debugSerial<<F("MBUS: Not supported reg type\n");
return false;
}
mbusSlenceTimer = millisNZ();
if (result != node.ku8MBSuccess) errorSerial<<F("MBUS: Polling error ")<<_HEX(result)<<endl;
return (result == node.ku8MBSuccess);
}
/**
* @brief Находит и обрабатывает регистр Modbus, выполняет сопоставление и действия.
* @param registerNum Номер регистра.
* @param posInBuffer Позиция в буфере ответа.
* @param regType Тип регистра.
* @param registerFrom Начальный регистр диапазона.
* @param registerTo Конечный регистр диапазона.
* @param doExecution Выполнять ли действие.
* @param submitParam Флаг для подавления повторных действий.
* @return Команда itemCmd с результатом.
*/
itemCmd out_Modbus::findRegister(uint16_t registerNum, uint16_t posInBuffer, uint8_t regType, uint16_t registerFrom, uint16_t registerTo, bool doExecution, bool * submitParam)
{
aJsonObject * paramObj = store->parameters->child;
bool tmpSubmitParam;
if (!submitParam) submitParam=&tmpSubmitParam;
*submitParam=true;
//bool is8bit = false;
while (paramObj)
{
int8_t parType = PAR_I16;
aJsonObject *regObj=NULL;
switch (regType) {
case MODBUS_HOLDING_REG_TYPE:
regObj = aJson.getObjectItem(paramObj, "reg");
break;
case MODBUS_INPUT_REG_TYPE:
regObj = aJson.getObjectItem(paramObj, "ir");
break;
case MODBUS_COIL_REG_TYPE:
regObj = aJson.getObjectItem(paramObj, "coil");
parType = PAR_COIL;
break;
case MODBUS_DISCRETE_REG_TYPE:
regObj = aJson.getObjectItem(paramObj, "din");
parType = PAR_COIL;
}
if (regObj && regObj->valueint ==registerNum)
{
aJsonObject *idObj = aJson.getObjectItem(paramObj, "id");
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
uint16_t data = node.getResponseBuffer(posInBuffer);
bool executeWithoutCheck=false; //Afler recurrent check, all dublicatess and suppressing checked by recurrent
bool submitRecurrentOut = false; //false if recurrent check find duplicates
char buf[16];
uint32_t param =0;
itemCmd mappedParam;
aJsonObject *typeObj = aJson.getObjectItem(paramObj, "type");
aJsonObject *mapObj = aJson.getObjectItem(paramObj, "map");
if (typeObj && typeObj->type == aJson_String) parType=str2regSize(typeObj->valuestring);
switch(parType) {
case PAR_I16:
//isSigned=true;
param=data;
mappedParam.Int((int32_t)(int16_t)data);
break;
case PAR_U16:
param=data;
mappedParam.Int((uint32_t)data);
break;
case PAR_I32:
//isSigned=true;
param = swap(data ) | swap(node.getResponseBuffer(posInBuffer+1)<<16);
mappedParam.Int((int32_t)param);
break;
case PAR_U32:
param = swap(data )| swap (node.getResponseBuffer(posInBuffer+1)<<16 );
mappedParam.Int((uint32_t)param);
break;
case PAR_U8L:
//is8bit=true;
param = data & 0xFF;
mappedParam.Int((uint32_t)param);
break;
case PAR_U8H:
//is8bit=true;
param = (data & 0xFF00) >> 8;
mappedParam.Int((uint32_t)param);
break;
case PAR_TENS:
param=data;
mappedParam.Tens((int16_t) data);
break;
case PAR_100:
param=data;
mappedParam.Tens_raw((int16_t) data * (TENS_BASE/100));
mappedParam.Float((int32_t) (int16_t) data/100.);
break;
case PAR_COIL:
param = (node.getResponseBuffer(posInBuffer/16) >> (posInBuffer % 16)) & 1;
mappedParam.Int((uint32_t)param);
}
traceSerial << F("MBUSD: got ")<<mappedParam.toString(buf,sizeof(buf))<< F(" from type ")<<parType<<F(":")<<paramObj->name<<endl;
if (mapObj && (mapObj->type==aJson_Array || mapObj->type==aJson_Object))
{
mappedParam=mappedParam.doReverseMapping(mapObj);
if (!mappedParam.isCommand() && !mappedParam.isValue()) //Not mapped
{
aJsonObject *defMappingObj;
defMappingObj = aJson.getObjectItem(mapObj, "def");
if (defMappingObj)
{
switch (defMappingObj->type)
{
case aJson_Int: //register/coil/.. number
traceSerial<<F("MBUSD: Searching reg#")<<defMappingObj->valueint<<endl;
if ((defMappingObj->valueint>= registerFrom) && (defMappingObj->valueint<=registerTo))
{
mappedParam = findRegister(defMappingObj->valueint,defMappingObj->valueint-registerFrom,regType,registerFrom,registerTo,false,&submitRecurrentOut);
executeWithoutCheck=true;
traceSerial<<"MBUSD: recurrent check res: "<<"SRO:"<<submitRecurrentOut<<endl;
}
else
{
debugSerial<<F("def reg# ")<<defMappingObj->valueint<<F(" out of range buffer, fetching")<<endl;
////to prevent CORRUPTION if using same buffer
uint16_t localBuffer;
node.setResponseBuffer(&localBuffer,1);
if (readModbus(defMappingObj->valueint,regType,1))
{
mappedParam = findRegister(defMappingObj->valueint,0,regType,defMappingObj->valueint,defMappingObj->valueint,false,&submitRecurrentOut);
executeWithoutCheck=true;
}
node.setDefaultResponseBuffer();
}
break;
case aJson_String: // parameter name
traceSerial<<F("Searching reg: ")<<defMappingObj->valuestring<<endl;
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
//Searching item param for nested mapping
//Retrive previous data
aJsonObject *lastMeasured = getLastMeasured(defMappingObj->valuestring);
if (lastMeasured)
{
traceSerial<<F("LastKnown value: ")<<lastMeasured->valueint<<endl;
//Searching template param for nested mapping
aJsonObject * templateParObj = aJson.getObjectItem(store->parameters,defMappingObj->valuestring);
if (templateParObj)
{
int8_t nestedParType = PAR_I16;
aJsonObject * nestedTypeObj = aJson.getObjectItem(templateParObj, "type");
if (nestedTypeObj && nestedTypeObj->type == aJson_String) parType=str2regSize(nestedTypeObj->valuestring);
switch(nestedParType) {
case PAR_I16:
case PAR_I32:
mappedParam.Int((int32_t)lastMeasured->valueint);
break;
case PAR_U32:
case PAR_U16:
case PAR_U8L:
case PAR_U8H:
case PAR_COIL:
mappedParam.Int((uint32_t)lastMeasured->valueint);
break;
case PAR_TENS:
mappedParam.Tens((int16_t) data);
break;
case PAR_100:
mappedParam.Tens_raw((int16_t) lastMeasured->valueint * (TENS_BASE/100));
mappedParam.Float((int32_t) (int16_t) lastMeasured->valueint/100.);
break;
default: errorSerial<<F("Invalid regtype")<<endl;
}
aJsonObject * nestedMapObj = aJson.getObjectItem(templateParObj, "map");
if (nestedMapObj && (nestedMapObj->type==aJson_Array || nestedMapObj->type==aJson_Object)) mappedParam=mappedParam.doReverseMapping(nestedMapObj);
traceSerial << F("MBUSD: NestedMapped:")<<mappedParam.toString(buf,sizeof(buf))<<endl;
if (!(lastMeasured->subtype & MB_VALUE_OUTDATED))
{
executeWithoutCheck=true;
submitRecurrentOut=true;
lastMeasured->subtype|= MB_VALUE_OUTDATED;
}
}
} //nested have lastMeasured
}
break;
}
}
}
else
traceSerial << F("MBUSD: Mapped:")<<mappedParam.toString(buf,sizeof(buf))<<endl;
} //mapping
if (doExecution && idObj && idObj->type==aJson_Int)
switch (idObj->valueint)
{
case S_CMD:
mappedParam.saveItem(item,FLAG_COMMAND);
break;
case S_SET:
mappedParam.saveItem(item,FLAG_PARAMETERS);
}
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
aJsonObject *execObj = aJson.getObjectItem(itemParametersObj,paramObj->name);
if (execObj)
{
// if (!doExecution || haveAction(execObj)) //if no action in execObj - do not save last value to avoid confuse further recurrent check
// {
//Retrive previous data
aJsonObject *lastMeasured = getLastMeasured(execObj);
if (lastMeasured)
{
if (lastMeasured->valueint == param)
{
//if recurrent call but value was readed before
if (!doExecution && !(lastMeasured->subtype & MB_VALUE_OUTDATED))
{
*submitParam=true; //never used
lastMeasured->subtype|=MB_VALUE_OUTDATED;
return mappedParam;
}
*submitParam=false; //supress repeating execution for same val
}
else
{
lastMeasured->valueint=param;
traceSerial<<"MBUS: Stored "<<param<<" to @S of "<<paramObj->name<<endl;
lastMeasured->subtype&=~MB_VALUE_OUTDATED;
}
}
// }
if (executeWithoutCheck)
{
if (doExecution && (submitRecurrentOut || *submitParam))
{
//debugSerial<<F("MBUS: exec ");mappedParam.debugOut();
executeCommand(execObj, -1, mappedParam);
*submitParam=true; //if requrrent check has submit smth - report it.
}
return mappedParam;
}
if (*submitParam && doExecution)
{
// Compare with last submitted val (if @V NOT marked as NULL in config)
aJsonObject * markObj = execObj;
if (execObj->type == aJson_Array)
{
markObj = execObj->child;
//storeLastValue = true;
}
aJsonObject *settedValue = aJson.getObjectItem(markObj,"@V");
if (settedValue && settedValue->type==aJson_Int && (settedValue->valueint == param))
{
traceSerial<<F("MBUSD: Ignored - equal with setted val")<<endl;
*submitParam=false;
}
else
{
if (doExecution)
{
//debugSerial<<F("MBUS: exec ");mappedParam.debugOut();
executeCommand(execObj, -1, mappedParam);
}
// if param updated by device and no new value queued to send - update @V to avoid "Ignored - equal with setted val"
if (settedValue && !(execObj->subtype & MB_NEED_SEND))
settedValue->valueint=param;
}
} //to be executed
} //ExecObj
} //item Parameters
return mappedParam;
} //reg == regNum
paramObj=paramObj->next;
} //while
return itemCmd();
}
/**
* @brief Опрос Modbus-устройства по списку регистров.
* @param reg JSON-объект с регистрами.
* @param regType Тип регистра.
*/
void out_Modbus::pollModbus(aJsonObject * reg, int regType)
{
if (!reg) return;
reg=reg->child;
while (reg)
{
switch (reg->type)
{
case aJson_Int:
{
int registerNum = reg->valueint;
if (readModbus(registerNum,regType,1))
{
findRegister(registerNum,0,regType,registerNum,registerNum);
}
}
break;
case aJson_Array:
if (aJson.getArraySize(reg)==2)
{
int registerFrom=aJson.getArrayItem(reg, 0)->valueint;
int registerTo=aJson.getArrayItem(reg, 1)->valueint;
if (readModbus(registerFrom,regType,registerTo-registerFrom+1))
{ traceSerial<<endl;
for(int i=registerFrom;i<=registerTo;i++)
{
findRegister(i,i-registerFrom,regType,registerFrom,registerTo);
}
}
}
}
reg = reg->next;
}
}
/**
* @brief Инициализирует линию связи Modbus.
*/
void out_Modbus::initLine()
{
//store->serialParam=(USARTClass::USARTModes) SERIAL_8N1;
#if defined (__SAM3X8E__)
modbusSerial.begin(store->baud, static_cast <USARTClass::USARTModes> (store->serialParam));
#elif defined (ARDUINO_ARCH_ESP8266)
modbusSerial.begin(store->baud, static_cast <SerialConfig>(store->serialParam));
#elif defined (ESP32)
//modbusSerial.begin(store->baud, store->serialParam);
//delay(100);
modbusSerial.updateBaudRate (store->baud); //Some terrible error in ESP32 core with uart reinit
#else
modbusSerial.begin(store->baud, (store->serialParam));
#endif
//debugSerial<< store->baud << F("---")<< store->serialParam<<endl;
node.begin(item->getArg(0), modbusSerial);
}
/**
* @brief Отправляет значение в Modbus-устройство.
* @param paramName Имя параметра.
* @param outValue JSON-объект с отправляемым значением.
* @return 0 при успехе, отрицательное значение при ошибке.
*/
int out_Modbus::sendModbus(char * paramName, aJsonObject * outValue)
{
if (!store) {errorSerial<<F(" internal send error - no store")<<endl; return -1;}
aJsonObject * templateParamObj = aJson.getObjectItem(store->parameters, paramName);
if (!templateParamObj) {errorSerial<<F(" internal send error - no template")<<endl; return -1;}
aJsonObject * regObj = aJson.getObjectItem(templateParamObj, "reg");
if (!regObj)
{
regObj = aJson.getObjectItem(templateParamObj, "coil");
if (!regObj) {errorSerial<<F(" internal send error - no reg/coil")<<endl; return -2;}
else outValue->subtype = PAR_COIL;
}
if (regObj->type != aJson_Int) {errorSerial<<F(" Reg/coil must be int")<<endl; return -2;}
aJsonObject * prefetchObj = aJson.getObjectItem(templateParamObj, "prefetch");
aJsonObject *lastMeasured = NULL;
int res = -1;
if (prefetchObj && (prefetchObj->type == aJson_Boolean) && prefetchObj->valuebool)
{
int modbusRegType = (outValue->subtype == PAR_COIL) ? MODBUS_COIL_REG_TYPE:MODBUS_HOLDING_REG_TYPE;
debugSerial<<F(" prefetch ")<<paramName<<F(" #") <<regObj->valueint << " type:" << modbusRegType << " ";
/// to prevent CORRUPTIOIN if using same buffer
uint16_t localBuffer;
node.setResponseBuffer(&localBuffer,1);
bool successRead = readModbus(regObj->valueint,modbusRegType,1);
mbusSlenceTimer = millisNZ();
if (successRead)
{
#ifdef PREFETCH_EXEC_IMMEDIALELLY
// option to execute if changed immediatelly
bool submited = false;
findRegister(regObj->valueint,0,modbusRegType,regObj->valueint,regObj->valueint,true,&submited);
node.setDefaultResponseBuffer();
if (submited)
{
debugSerial << F("MBUS:")<<paramName<< (" val changed. Write cancelled")<<endl;
return -3;
}
else debugSerial << F("MBUS:")<<paramName<< F(" val not changed. Continue")<<endl;
#else
// Option to skip writing if change, let next polling take of change
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
aJsonObject *execObj = aJson.getObjectItem(itemParametersObj,paramName);
if (execObj)
{
//aJsonObject * markObj = execObj;
//if (execObj->type == aJson_Array) markObj = execObj->child;
//Retrive previous data
lastMeasured = getLastMeasured(execObj);// aJson.getObjectItem(markObj,"@S");
if (lastMeasured)
{
//if (lastMeasured->type == aJson_Int)
// {
traceSerial<<F(" Last:")<<lastMeasured->valueint<< F(" Now:") << localBuffer<<endl;
if (lastMeasured->valueint != localBuffer)
{
debugSerial << F("MBUS:")<<paramName<< F(" val changed.")<<endl;
node.setDefaultResponseBuffer();
return -3;
}
else
{
if (outValue->valueint == localBuffer)
{
debugSerial << F("MBUS:")<<paramName<< F("=")<<localBuffer<<F(": equal targert.")<<endl;
node.setDefaultResponseBuffer();
return -4;
}
debugSerial << F("MBUS:")<<paramName<< F(" val not changed. Continue")<<endl;
}
//}
}
}
}
#endif
}
node.setDefaultResponseBuffer();
}
switch(outValue->subtype) {
case PAR_U16:
case PAR_I16:
case PAR_TENS:
case PAR_100:
res = node.writeSingleRegister(regObj->valueint,outValue->valueint);
break;
break;
case PAR_I32:
case PAR_U32:
res = node.writeSingleRegister(regObj->valueint,swap(outValue->valueint & 0xFFFF));
res += node.writeSingleRegister(regObj->valueint+1,swap(outValue->valueint >> 16)) ;
break;
case PAR_U8L:
case PAR_I8L:
res = node.writeSingleRegister(regObj->valueint,outValue->valueint & 0xFF);
break;
case PAR_U8H:
case PAR_I8H:
res = node.writeSingleRegister(regObj->valueint,(outValue->valueint & 0xFFFF)>> 8);
break;
case PAR_COIL:
res = node.writeSingleCoil (regObj->valueint,outValue->valueint);
break;
}
mbusSlenceTimer = millisNZ();
debugSerial<<F("MBUS res: ")<<res<<F(" ")<<paramName<<" reg:"<<regObj->valueint<<F(" val:")<<outValue->valueint<<endl;
//If wrote - suppress action on poll
if ((res ==0) && (outValue->type == aJson_Int) && lastMeasured && (lastMeasured->type == aJson_Int)) lastMeasured->valueint = outValue->valueint;
return ( res == 0);
}
/**
* @brief Осуществляет опрос и отправку команд Modbus.
* @param cause Причина вызова (например, медленный опрос).
* @return Интервал следующего опроса.
*/
int out_Modbus::Poll(short cause)
{
if (cause==POLLING_SLOW) return 0;
bool lineInitialized = false;
if (modbusBusy || (Status() != CST_INITIALIZED) || ( mbusSlenceTimer && !isTimeOver(mbusSlenceTimer,millis(),200))) return 0;
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
aJsonObject *execObj = itemParametersObj->child;
bool onceSendOk=false;
while (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)) && !onceSendOk)
{
if ((execObj->subtype & MB_NEED_SEND) && !(execObj->subtype & MB_SEND_ERROR))
{
aJsonObject * markObj = execObj;
if (execObj->type == aJson_Array) markObj = execObj->child;
aJsonObject *outValue = aJson.getObjectItem(markObj,"@V");
if (outValue)
{
modbusBusy=1;
if (!lineInitialized)
{
lineInitialized=true;
initLine();
}
int sendRes;
int savedValue;
bool needResend;
do
{
savedValue = outValue->valueint;
debugSerial<<"MBUS: SEND "<<item->itemArr->name<<"/"<<execObj->name<<"="<<outValue->valueint<<endl;
sendRes = sendModbus(execObj->name,outValue);
needResend = (savedValue != outValue->valueint);
while(needResend && mbusSlenceTimer && !isTimeOver(mbusSlenceTimer,millis(),200)) modbusIdle();
}
while (needResend); //repeat sending if target value changed while we're waited for mbus responce
switch (sendRes)
{
case 1: //success
case -4: //equal tatget
//execObj->subtype&=~ MB_NEED_SEND;
execObj->subtype = 0;
onceSendOk=true;
///return 1; //relax
break;
case 0: //fault
execObj->subtype |= MB_SEND_ERROR;
errorSerial<<F("MBUS: ")<<item->itemArr->name<<"/"<<execObj->name<<F(" send error. ");
if ((execObj->subtype & 3) != MB_SEND_ATTEMPTS) execObj->subtype++;
errorSerial<<F("MBUS: ")<<item->itemArr->name<<"/"<<execObj->name<<" Attempt: "<< (execObj->subtype & 3) <<endl;
break;
case -3:
errorSerial<<F("MBUS: param ")<<item->itemArr->name<<"/"<<execObj->name<<F(" sending cancelled")<<endl;
//outValue->valueint=
//execObj->subtype&=~ MB_NEED_SEND;
execObj->subtype = 0;
break;
default: //param not found
errorSerial<<F("MBUS: param ")<<item->itemArr->name<<"/"<<execObj->name<<F(" not found")<<endl;
execObj->subtype&=~ MB_NEED_SEND;
}
}
}
execObj=execObj->next;
}
modbusBusy=0;
}
if (isTimeOver(store->timestamp,millis(),store->pollingInterval) && ( !mbusSlenceTimer || isTimeOver(mbusSlenceTimer,millis(),200)))
{
// Clean_up FLAG_SEND_ERROR flag
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
aJsonObject *execObj = itemParametersObj->child;
while (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)))
{
if (execObj->subtype & MB_SEND_ERROR) execObj->subtype&=~ MB_SEND_ERROR;
if ((execObj->subtype & 0x3) >= MB_SEND_ATTEMPTS)
{
//execObj->subtype&=~ MB_NEED_SEND;
//Clean ERROR, NEED, Attempts
errorSerial<<"MBUS: send failed "<<item->itemArr->name<<"/"<<execObj->name<<endl;
execObj->subtype = 0;
}
execObj=execObj->next;
}
}
// if some polling configured
if (store->pollingRegisters || store->pollingIrs || store->pollingCoils || store->poolingDiscreteIns)
{
traceSerial<<F("MBUSD: Poll ")<< item->itemArr->name << endl;
modbusBusy=1;
if (!lineInitialized)
{
lineInitialized=true;
initLine();
}
pollModbus(store->pollingRegisters,MODBUS_HOLDING_REG_TYPE);
pollModbus(store->pollingIrs,MODBUS_INPUT_REG_TYPE);
pollModbus(store->pollingCoils,MODBUS_COIL_REG_TYPE);
pollModbus(store->poolingDiscreteIns ,MODBUS_DISCRETE_REG_TYPE);
traceSerial<<F("MBUSD: endPoll ")<< item->itemArr->name << endl;
//Non blocking waiting to release line
uint32_t time = millis();
while (!isTimeOver(time,millis(),50))
modbusIdle();
modbusBusy =0;
}
store->timestamp=millisNZ();
}
return store->pollingInterval;
};
/**
* @brief Возвращает тип канала.
* @return CH_MBUS.
*/
int out_Modbus::getChanType()
{
return CH_MBUS;
}
/**
* @brief Отправляет команду itemCmd в Modbus по шаблону параметра.
* @param templateParamObj JSON-объект шаблона параметра.
* @param cmd Команда itemCmd.
* @return 1 при успехе, 0 при ошибке.
*/
int out_Modbus::sendItemCmd(aJsonObject *templateParamObj, itemCmd cmd)
{
if (templateParamObj)
{
int suffixCode = cmd.getSuffix();
//bool isCommand = cmd.isCommand();
if (!suffixCode) return 0;
char *suffixStr =templateParamObj->name;
// We have find template for suffix or suffixCode
itemCmd cmdValue = itemCmd(ST_VOID,CMD_VOID);
long Value = 0;
int8_t regType = PAR_I16;
aJsonObject * typeObj = aJson.getObjectItem(templateParamObj, "type");
aJsonObject * mapObj = aJson.getObjectItem(templateParamObj, "map");
if (typeObj && typeObj->type == aJson_String) regType=str2regSize(typeObj->valuestring);
switch(regType) {
case PAR_I16:
case PAR_U16:
case PAR_I32:
case PAR_U32:
case PAR_U8L:
case PAR_U8H:
case PAR_I8H:
case PAR_I8L:
cmdValue=cmd.doMapping(mapObj);
if (!cmdValue.isValue())return 0;
Value=cmdValue.getInt();
break;
case PAR_TENS:
cmdValue=cmd.doMapping(mapObj);
if (!cmdValue.isValue())return 0;
Value=cmdValue.getTens();
break;
case PAR_100:
cmdValue=cmd.doMapping(mapObj);
if (!cmdValue.isValue())return 0;
Value=cmdValue.getTens_raw()*(100/TENS_BASE);
}
traceSerial<<F("MBUSD: suffix:")<<suffixStr<< F(" Val: ")<<Value<<endl;
aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
if (itemParametersObj && itemParametersObj->type ==aJson_Object)
{
aJsonObject *execObj = aJson.getObjectItem(itemParametersObj,suffixStr);
if (execObj && ((execObj->type == aJson_Object) || (execObj->type == aJson_Array)))
{
aJsonObject * markObj = execObj;
if (execObj->type == aJson_Array) markObj = execObj->child;
//Schedule update
execObj->subtype |= MB_NEED_SEND;
aJsonObject *outValue = aJson.getObjectItem(markObj,"@V");
if (outValue) // Existant. Preserve original @type
{
outValue->valueint=Value;
outValue->subtype =regType & 0xF;
}
else //No container to store value yet
// If no @V in config - creating with INT type - normal behavior - no supress in-to-out
{
debugSerial<<F("Add @V: ")<<execObj->name<<endl;
aJson.addNumberToObject(markObj, "@V", Value);
outValue = aJson.getObjectItem(markObj,"@V");
if (outValue) outValue->subtype =regType & 0xF;
}
/* Conflict with pre-fetching
aJsonObject *polledValue = aJson.getObjectItem(markObj,"@S");
if (polledValue && outValue->type == aJson_Int)
{
traceSerial<<"MBUS: not Stored "<<Value<<" to @S of "<<item->itemArr->name<<":"<<templateParamObj->name<<endl;
polledValue->valueint=Value; //to pevent suppressing to change back to previously polled value if this occurs before next polling
polledValue->subtype&=~MB_VALUE_OUTDATED;
}
*/
}
}
return 1;
}
else return 0;
}
//!Control unified Modbus item
// Priority of selection sub-items control to:
// 1. if defined standard suffix Code inside cmd
// 2. custom textual subItem
// 3. non-standard numeric suffix Code equal param id
/**
* @brief Унифицированное управление Modbus-каналом.
* @param cmd Команда itemCmd.
* @param subItem Имя подэлемента.
* @param toExecute Выполнять ли команду.
* @param authorized Авторизовано ли выполнение.
* @return Результат выполнения.
*/
int out_Modbus::Ctrl(itemCmd cmd, char* subItem, bool toExecute,bool authorized)
{
if (!store) return -1;
int suffixCode = cmd.getSuffix();
aJsonObject *templateParamObj = NULL;
int res = -1;
// trying to find parameter in template with name == subItem (NB!! standard suffixes dint working here)
if (subItem && strlen (subItem) && store && store->parameters)
{
templateParamObj = aJson.getObjectItem(store->parameters, subItem);
res= sendItemCmd(templateParamObj,cmd);
}
else
// No subitem, trying to find suffix with root item - (Trying to find template parameter where id == suffixCode)
{
if (store && store->parameters) templateParamObj = store->parameters->child;
bool suffixFinded = false;
while (templateParamObj)
{
aJsonObject *idObj = aJson.getObjectItem(templateParamObj, "id");
if (idObj && idObj->type==aJson_Int && idObj->valueint == suffixCode)
{
res= sendItemCmd(templateParamObj,cmd);
suffixFinded = true;
}
templateParamObj=templateParamObj->next;
}
if (!suffixFinded) errorSerial<<F("MBUS: No template for ")<<subItem<<F(" or suffix ")<<suffixCode<<endl;
}
return res;
}
#endif