#ifndef MBUS_DISABLE #include "modules/out_modbus.h" #include "Arduino.h" #include "options.h" #include "utils.h" #include "Streaming.h" #include "item.h" #include #include "main.h" #include 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) uint16_t swap (uint16_t x) {return ((x & 0xff) << 8) | ((x & 0xff00) >> 8);} int str2regSize(char * str) { for(uint8_t i=0; iitemArg || (item->itemArg->type != aJson_Array) || aJson.getArraySize(item->itemArg)<2 || !modbusObj) { errorSerial<itemArg<itemArg->type != aJson_Array)<itemArg)<2)<itemArg, 1); if (templateIdObj->type != aJson_String || !templateIdObj->valuestring) { errorSerial<valuestring); if (! templateObj) { errorSerial<valuestring<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 (store->serialParam)); #elif defined (ARDUINO_ARCH_ESP8266) modbusSerial.begin(store->baud, static_cast (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"); return true; //store->addr=item->getArg(0); } int out_Modbus::Setup() { abstractOut::Setup(); if (!store) store= (mbPersistent *)item->setPersistent(new mbPersistent); if (!store) { errorSerial<timestamp=millisNZ(); if (getConfig()) { infoSerial<itemArr->name<itemArr->name); if (store) delete store; item->setPersistent(NULL); store = NULL; return 1; } 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<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 ")<name<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<valueint<valueint>= registerFrom) && (defMappingObj->valueint<=registerTo)) { mappedParam = findRegister(defMappingObj->valueint,defMappingObj->valueint-registerFrom,regType,registerFrom,registerTo,false,&submitRecurrentOut); executeWithoutCheck=true; debugSerial<<"MBUSD: recurrent check res: "<<"SRO:"<valueint<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<valuestring<type ==aJson_Object) { //Searching item param for nested mapping aJsonObject *itemParObj = aJson.getObjectItem(itemParametersObj,defMappingObj->valuestring); if (itemParObj) { //Retrive previous data aJsonObject *lastMeasured = aJson.getObjectItem(itemParObj,"@S"); if (lastMeasured && lastMeasured->type ==aJson_Int) { traceSerial<valueint<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<type==aJson_Array || nestedMapObj->type==aJson_Object)) mappedParam=mappedParam.doReverseMapping(nestedMapObj); traceSerial << F("MBUSD: NestedMapped:")<subtype & MB_VALUE_OUTDATED)) { executeWithoutCheck=true; submitRecurrentOut=true; lastMeasured->subtype|= MB_VALUE_OUTDATED; } } } } } break; } } } else traceSerial << F("MBUSD: Mapped:")<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) { bool storeLastValue = true; if (doExecution) { storeLastValue=false; // Check - if no action configured for object - not need to store last value - let requrent process do it aJsonObject * i = execObj->child; while (i && !storeLastValue) { if (i->name && *i->name && (*i->name != '@')) storeLastValue = true; i=i->next; } } aJsonObject * markObj = execObj; if (execObj->type == aJson_Array) { markObj = execObj->child; storeLastValue = true; } if (storeLastValue) { //Retrive previous data aJsonObject *lastMeasured = aJson.getObjectItem(markObj,"@S"); if (lastMeasured) { if (lastMeasured->type == aJson_Int) { if (lastMeasured->valueint == param) *submitParam=false; //supress repeating execution for same val else { lastMeasured->valueint=param; traceSerial<<"MBUS: Stored "<name<subtype&=~MB_VALUE_OUTDATED; } } } else //No container to store value yet { debugSerial<name<type==aJson_Int && (settedValue->valueint == param)) { traceSerial<subtype & MB_NEED_SEND)) settedValue->valueint=param; } } } } //if (submitRecurrentOut) *submitParam=true; //if requrrent check has submit smth - report it. return mappedParam; } paramObj=paramObj->next; } return itemCmd(); } 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<next; } } void out_Modbus::initLine() { //store->serialParam=(USARTClass::USARTModes) SERIAL_8N1; #if defined (__SAM3X8E__) modbusSerial.begin(store->baud, static_cast (store->serialParam)); #elif defined (ARDUINO_ARCH_ESP8266) modbusSerial.begin(store->baud, static_cast (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<getArg(0), modbusSerial); } int out_Modbus::sendModbus(char * paramName, aJsonObject * outValue) { if (!store) {errorSerial<parameters, paramName); if (!templateParamObj) {errorSerial<subtype = PAR_COIL; } if (regObj->type != aJson_Int) {errorSerial<type == aJson_Boolean) && prefetchObj->valuebool) { int modbusRegType = (outValue->subtype == PAR_COIL) ? MODBUS_COIL_REG_TYPE:MODBUS_HOLDING_REG_TYPE; debugSerial<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:")<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 = aJson.getObjectItem(markObj,"@S"); if (lastMeasured) { if (lastMeasured->type == aJson_Int) { traceSerial<valueint<< F(" Now:") << localBuffer<valueint != localBuffer) { debugSerial << F("MBUS:")<valueint == localBuffer) { debugSerial << F("MBUS:")<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<valueint<valueint<type == aJson_Int) && lastMeasured && (lastMeasured->type == aJson_Int)) lastMeasured->valueint = outValue->valueint; return ( res == 0); } 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 "<itemArr->name<<" "; 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<name<subtype & 3) != MB_SEND_ATTEMPTS) execObj->subtype++; errorSerial<<"Attempt: "<< (execObj->subtype & 3) <name<valueint= //execObj->subtype&=~ MB_NEED_SEND; execObj->subtype = 0; break; default: //param not found errorSerial<name<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 "<itemArr->name<<"/"<name<subtype = 0; } execObj=execObj->next; } } // if some polling configured if (store->pollingRegisters || store->pollingIrs || store->pollingCoils || store->poolingDiscreteIns) { traceSerial<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<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; }; int out_Modbus::getChanType() { return CH_MBUS; } 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<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<name<subtype =regType & 0xF; } /* Conflict with pre-fetching aJsonObject *polledValue = aJson.getObjectItem(markObj,"@S"); if (polledValue && outValue->type == aJson_Int) { traceSerial<<"MBUS: not Stored "<itemArr->name<<":"<name<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 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<