/* Copyright © 2017-2018 Andrey Klimov. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Homepage: http://lazyhome.ru GIT: https://github.com/anklimov/lighthub e-mail anklimov@gmail.com */ #include "options.h" #include "item.h" #include "aJSON.h" #include "utils.h" #include "textconst.h" #include "main.h" #include "bright.h" #include "itemCmd.h" //#include "SHA256.h" #ifdef _dmxout #include "dmx.h" #ifdef ADAFRUIT_LED #include #else #include "FastLED.h" #endif #endif #ifndef MODBUS_DISABLE #include #endif #include #include "modules/out_spiled.h" #include "modules/out_ac.h" #include "modules/out_motor.h" #include "modules/out_modbus.h" #include "modules/out_dmx.h" #include "modules/out_pwm.h" #include "modules/out_pid.h" #include "modules/out_multivent.h" #include "modules/out_uartbridge.h" #include "modules/out_relay.h" #include "modules/out_counter.h" #ifdef MERCURY_ENABLE #include "modules/out_mercury.h" #endif #ifdef ELEVATOR_ENABLE #include "modules/out_elevator.h" #endif #ifdef HUMIDIFIER_ENABLE #include "modules/out_humidifier.h" #endif short modbusBusy = 0; //bool isPendedModbusWrites = false; extern aJsonObject *pollingItem; extern PubSubClient mqttClient; extern int8_t ethernetIdleCount; extern int8_t configLocked; extern lan_status lanStatus; int retrieveCode(char **psubItem); int subitem2cmd(char *payload) { int cmd = 0; // Check for command if (payload) { if (strcmp_P(payload, ON_P) == 0) cmd = CMD_ON; else if (strcmp_P(payload, OFF_P) == 0) cmd = CMD_OFF; //else if (strcmp_P(payload, REST_P) == 0) cmd = CMD_RESTORE; //else if (strcmp_P(payload, TOGGLE_P) == 0) cmd = CMD_TOGGLE; else if (strcmp_P(payload, HALT_P) == 0) cmd = CMD_HALT; else if (strcmp_P(payload, XON_P) == 0) cmd = CMD_XON; //else if (strcmp_P(payload, XOFF_P) == 0) cmd = CMD_XOFF; else if (strcmp_P(payload, HEAT_P) == 0) cmd = CMD_HEAT; else if (strcmp_P(payload, COOL_P) == 0) cmd = CMD_COOL; else if (strcmp_P(payload, AUTO_P) == 0) cmd = CMD_AUTO; else if (strcmp_P(payload, FAN_ONLY_P) == 0) cmd = CMD_FAN; else if (strcmp_P(payload, DRY_P) == 0) cmd = CMD_DRY; //else if (strcmp_P(payload, HIGH_P) == 0) cmd = CMD_HIGH; //else if (strcmp_P(payload, MED_P) == 0) cmd = CMD_MED; //else if (strcmp_P(payload, LOW_P) == 0) cmd = CMD_LOW; } return cmd; } int txt2subItem(char *payload) { if (!payload || !strlen(payload)) return S_NOTFOUND; for(uint8_t i=1; ivalueint; itemType = replaceTypeToInt (aJson.getArrayItem(itemArr, I_TYPE)); itemArg = aJson.getArrayItem(itemArr, I_ARG); itemVal = aJson.getArrayItem(itemArr, I_VAL); itemExt = aJson.getArrayItem(itemArr, I_EXT); switch (itemType) { #ifndef PWM_DISABLE case CH_PWM: driver = new out_pwm (this); break; #endif #ifndef DMX_DISABLE case CH_RGBW: case CH_RGB: case CH_DIMMER: case CH_RGBWW: driver = new out_dmx (this); break; #endif #ifndef SPILED_DISABLE case CH_SPILED: driver = new out_SPILed (this); break; #endif #ifndef AC_DISABLE case CH_AC: driver = new out_AC (this); break; #endif #ifndef MOTOR_DISABLE case CH_MOTOR: driver = new out_Motor (this); break; #endif #ifndef MBUS_DISABLE case CH_MBUS: driver = new out_Modbus (this); break; #endif #ifndef PID_DISABLE case CH_PID: driver = new out_pid (this); break; #endif #ifndef RELAY_DISABLE case CH_RELAYX: driver = new out_relay (this); break; #endif #ifndef MULTIVENT_DISABLE case CH_MULTIVENT: driver = new out_Multivent (this); break; #endif #ifdef UARTBRIDGE_ENABLE case CH_UARTBRIDGE: driver = new out_UARTbridge (this); // debugSerial<name << F(" T:") << itemType << F(" =") << getArg() << endl; } } boolean Item::Setup() { if (driver) { if (driver->Status()) driver->Stop(); if (driver->Setup()) { if (getCmd()) setFlag(FLAG_COMMAND); if (itemVal) setFlag(FLAG_PARAMETERS); } return true; } else return false; } void Item::Stop() { if (driver) { driver->Stop(); } return; } Item::~Item() { if (driver) { delete driver; } } Item::Item(char *name) //Constructor { char * pDefaultSubItem = defaultSubItem; driver = NULL; defaultSubItem[0] =0; defaultSuffixCode = 0; if (name && items) { char* sub; if (sub=strchr(name,'/')) { char buf [MQTT_SUBJECT_LENGTH+1]; short i; for(i=0;(name[i] && (name[i]!='/') && (ivalueint & CMD_MASK; else return -1; } void Item::setCmd(uint8_t cmdValue) { aJsonObject *itemCmd = aJson.getArrayItem(itemArr, I_CMD); if (itemCmd && (itemCmd->type == aJson_Int || itemCmd->type == aJson_NULL)) { itemCmd->type = aJson_Int; itemCmd->valueint = cmdValue & CMD_MASK | itemCmd->valueint & (FLAG_MASK); // Preserve special bits debugSerial<type == aJson_Int)) { return (uint32_t) itemCmd->valueint & flag & FLAG_MASK; } return 0; } void Item::setFlag (uint32_t flag) { aJsonObject *itemCmd = aJson.getArrayItem(itemArr, I_CMD); if (itemCmd && (itemCmd->type == aJson_Int || itemCmd->type == aJson_NULL)) { itemCmd->type = aJson_Int; itemCmd->valueint |= flag & FLAG_MASK; // Preserve CMD bits // debugSerial<type == aJson_Int || itemCmd->type == aJson_NULL)) { itemCmd->valueint &= CMD_MASK | ~(flag & FLAG_MASK); // Preserve CMD bits // debugSerial<type == aJson_Int){ if (!n) return itemArg->valueint; else return 0;//-1; } if ((itemArg->type == aJson_Array) && ( n < aJson.getArraySize(itemArg))) return aJson.getArrayItem(itemArg, n)->valueint; else return 0;//-2; } float Item::getFloatArg(short n) //Return arg float or first array element if Arg is array { if (!itemArg) return 0.0;//-1; if ((itemArg->type == aJson_Array) && ( n < aJson.getArraySize(itemArg))) { aJsonObject * obj = aJson.getArrayItem(itemArg, n); if (obj && obj->type == aJson_Int) return static_cast (obj->valueint); if (obj && obj->type == aJson_Float) return obj->valuefloat; return 0.0; } else if (!n) { if (itemArg->type == aJson_Int) return static_cast(itemArg->valueint); else if (itemArg->type == aJson_Float) return itemArg->valuefloat; } return 0.0; } short Item::getArgCount() { if (!itemArg) return 0; if (itemArg->type == aJson_Int) return 1; if (itemArg->type == aJson_Array) return aJson.getArraySize(itemArg); else return 0; } /* int Item::getVal(short n) //Return Val from Value array { if (!itemVal) return -1; else if (itemVal->type==aJson_Array) { aJsonObject *t = aJson.getArrayItem(itemVal,n); if (t) return t->valueint; else return -3; } else return -2; } */ long int Item::getVal() //Return Val if val is int or first elem of Value array { if (!itemVal) return 0;//-1; if (itemVal->type == aJson_Int) return itemVal->valueint; else if (itemVal->type == aJson_Array) { aJsonObject *t = aJson.getArrayItem(itemVal, 0); if (t) return t->valueint; else return 0;//-3; } else return 0;//-2; } uint8_t Item::getSubtype() { if (!itemVal) return 0;//-1; if (itemVal->type == aJson_Int || itemVal->type == aJson_Float || itemVal->type == aJson_NULL) return itemVal->subtype; else if (itemVal->type == aJson_Array) { aJsonObject *t = aJson.getArrayItem(itemVal, 0); if (t) return t->subtype; else return 0;//-3; } else return 0;//-2; } /* void Item::setVal(short n, int par) // Only store if VAL is array defined in config to avoid waste of RAM { if (!itemVal || itemVal->type!=aJson_Array) return; debugSerial<type == aJson_Int) return (chPersistent *) itemExt->child; else return NULL; } chPersistent * Item::setPersistent(chPersistent * par) { if (!itemExt) { for (int i = aJson.getArraySize(itemArr); i <= 4; i++) aJson.addItemToArray(itemArr, itemExt = aJson.createNull());//Item((long int)0)); //itemExt = aJson.getArrayItem(itemArr, I_EXT); }; if(!itemExt ) return NULL; if(itemExt->type == aJson_NULL) itemExt->type=aJson_Int; else if(itemExt->type != aJson_Int ) return NULL; itemExt->valueint = 0; itemExt->child = (aJsonObject *) par; // debugSerial<type == aJson_Array)); } // If retrieving subitem code ok - return it // parameter will point on the rest truncated part of subitem // or pointer to NULL of whole string converted to subitem code int retrieveCode(char **psubItem) { int suffixCode; char* suffix; //debugSerial<getChanType(); switch (_itemType) { case CH_COUNTER: return 0; case CH_THERMO: return 80; default: return 255; } } // myhome/dev/item/subItem int Item::Ctrl(char * payload, char * subItem) { if (!payload) return 0; int fr = freeRam(); if (fr < minimalMemory) { errorSerial<type!=aJson_Array) return false; // Iterate across array of names aJsonObject *i = itemArr->child; configLocked++; while (i) { if (i->type == aJson_String) { //debugSerial<< i->valuestring<valuestring); if (nextItem && nextItem->type == aJson_Array) //nextItem is correct item { Item it(nextItem); if (cmd && it.isValid()) it.Ctrl(*cmd,subItem,false,authorized); //Execute (non recursive) //Retrieve itemType aJsonObject * itemtype = aJson.getArrayItem(nextItem,0); if (itemtype && itemtype->type == aJson_Int && itemtype->valueint == CH_GROUP) { //is Group aJsonObject * itemSubArray = aJson.getArrayItem(nextItem,1); short res = digGroup(itemSubArray,cmd,subItem,authorized); if (!cmd && res) { configLocked--; return true; //Not execution, just activity check. If any channel is active - return true } } else // Normal channel if (!cmd && it.isValid() && it.isActive()) { configLocked--; return true; //Not execution, just activity check. If any channel is active - return true } } } i = i->next; } //while configLocked--; return false; } int Item::isScheduled() { aJsonObject *timestampObj = aJson.getArrayItem(itemArr, I_TIMESTAMP); if (timestampObj && (timestampObj->subtype > 0)) { return timestampObj->subtype; } return 0; } int Item::scheduleOppositeCommand(itemCmd cmd,bool isActiveNow,bool authorized) { itemCmd nextCmd=cmd; switch (cmd.getCmd()){ case CMD_XON: if (isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_XOFF); break; case CMD_XOFF: if (!isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_XON); break; case CMD_ON: if (isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_OFF); break; case CMD_OFF: if (!isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_ON); break; case CMD_ENABLE: if (!getFlag(FLAG_DISABLED) && !isScheduled()) return 0; nextCmd.Cmd(CMD_DISABLE); break; case CMD_DISABLE: if (getFlag(FLAG_DISABLED) && !isScheduled()) return 0; nextCmd.Cmd(CMD_ENABLE); break; case CMD_FREEZE: if (getFlag(FLAG_FREEZED) && !isScheduled()) return 0; nextCmd.Cmd(CMD_UNFREEZE); break; case CMD_UNFREEZE: if (!getFlag(FLAG_FREEZED) && !isScheduled()) return 0; nextCmd.Cmd(CMD_FREEZE); break; case CMD_HALT: if (!isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_RESTORE); break; case CMD_RESTORE: if (isActiveNow && !isScheduled()) return 0; nextCmd.Cmd(CMD_HALT); break; case CMD_TOGGLE: nextCmd.Cmd(CMD_TOGGLE); break; default: return 0; } debugSerial<0) && (timestampObj->type == aJson_Int || timestampObj->type == aJson_NULL || timestampObj->type == aJson_Reserved)) { timestampObj->valueint = millis()+cmd.getInt(); timestampObj->type = (authorized?aJson_Reserved:aJson_Int); timestampObj->subtype=(cmd.getCmd() & 0xF); debugSerial<valueint<subtype=0; debugSerial<0); debugSerial<0 && !cmd.getInt()) {cmd.Cmd(CMD_OFF);status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE;} if (chActive==0 && cmd.getInt()) {cmd.Cmd(CMD_ON);status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE;} // continue processing as SET case S_SET: stored.loadItemDef(this); // if previous color was in RGB notation but new value is HSV - discard previous val and change type; if ((stored.getArgType() == ST_RGB || stored.getArgType() == ST_RGBW) && (cmd.getArgType() == ST_HSV255)) stored.setArgType(cmd.getArgType()); if (itemType == CH_GROUP && cmd.isColor()) stored.setArgType(ST_HSV255);//Extend storage for group channel //Convert value to most approptiate type for channel stored.assignFrom(cmd,getChanType()); stored.debugOut(); if ((scale100 || SCALE_VOLUME_100) && (cmd.getArgType()==ST_HSV255 || cmd.getArgType()==ST_PERCENTS255 || cmd.getArgType()==ST_INT32 || cmd.getArgType()==ST_UINT32)) stored.scale100(); cmd=stored; status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; break; case S_VAL: break; case S_SAT: stored.loadItemDef(this); if (stored.setS(cmd.getS())) { cmd=stored; cmd.setSuffix(S_SET); status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; } else invalidArgument=true; break; case S_HUE: stored.loadItemDef(this); if (stored.setH(cmd.getH())) { cmd=stored; cmd.setSuffix(S_SET); status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; } else invalidArgument=true; break; case S_TEMP: stored.loadItemDef(this); stored.setColorTemp(cmd.getColorTemp()); cmd=stored; status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; } } break; case CMD_TOGGLE: if (suffixCode != S_CTRL) { chActive=(isActive()>0); toExecute=true; if (chActive) cmd.Cmd(CMD_OFF); else { // cmd.loadItemDef(this); /// cmd.Cmd(CMD_ON); } status2Send |=FLAG_COMMAND | FLAG_SEND_IMMEDIATE; } else { if (getFlag(FLAG_DISABLED)) clearFlag(FLAG_DISABLED); else setFlag(FLAG_DISABLED); status2Send |= FLAG_FLAGS | FLAG_SEND_IMMEDIATE; res=1; } break; case CMD_DN: case CMD_UP: { itemCmd fallbackCmd=cmd; long step=0; if (cmd.isValue()) step=cmd.getTens_raw(); if (!step) step=DEFAULT_INC_STEP; if (cmd.getCmd() == CMD_DN) step=-step; cmd.loadItemDef(this); cmd.Cmd(CMD_VOID); // Converting to SET value command switch (suffixCode) { case S_NOTFOUND: toExecute=true; case S_SET: { long limit = limitSetValue(); //if (limit && suffixCode==S_NOTFOUND) limit = 100; if (cmd.incrementPercents(step,limit)) { status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; } else {cmd=fallbackCmd;invalidArgument=true;} } break; case S_HUE: if (cmd.incrementH(step)) { status2Send |= FLAG_PARAMETERS | FLAG_SEND_DEFFERED; cmd.setSuffix(S_SET); } else {cmd=fallbackCmd;invalidArgument=true; errorSerial << F("Invalid arg")<type == aJson_Array && operation) { chActive=(isActive()>0); digGroup(itemArg,&cmd,subItem,authorized); if ((suffixCode==S_CMD) && cmd.isValue()) { scheduleOppositeCommand(originalCmd,chActive,authorized); scheduledOppositeCommand = true; } } res=1; // Post-processing of group command - converting HALT,REST,XON,XOFF to conventional ON/OFF for status switch (cmd.getCmd()) { int t; case CMD_RESTORE: // individual for group members switch (t = getCmd()) { case CMD_HALT: //previous command was HALT ? ///if ((suffixCode==S_CMD) && cmd.isValue() && (!chActive || isScheduled())) scheduleOppositeCommand(cmd,authorized); debugSerial << F("CTRL: Restored from:") << t << endl; cmd.loadItemDef(this); cmd.Cmd(CMD_ON); //turning on status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE; break; default: return 3; } status2Send |= FLAG_COMMAND; break; case CMD_XOFF: // individual for group members switch (t = getCmd()) { case CMD_XON: //previous command was CMD_XON ? ///if ((suffixCode==S_CMD) && cmd.isValue() && (chActive || isScheduled())) scheduleOppositeCommand(cmd,authorized); debugSerial << F("CTRL: Turned off from:") << t << endl; cmd.Cmd(CMD_OFF); //turning Off status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE; break; default: debugSerial << F("CTRL: XOFF skipped. Prev cmd:") << t <0); ///if ((suffixCode==S_CMD) && cmd.isValue() && (!chActive || isScheduled())) scheduleOppositeCommand(cmd,authorized); if (!chActive) //if channel was'nt active before CMD_XON { cmd.loadItemDef(this); cmd.Cmd(CMD_ON); command2Set=CMD_XON; status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE; } else { debugSerial<0); ///if ((suffixCode==S_CMD) && cmd.isValue() && (chActive || isScheduled())) scheduleOppositeCommand(cmd,authorized); if (chActive) //if channel was active before CMD_HALT /// HERE bug - if cmd == On but 0 = active { cmd.Cmd(CMD_OFF); command2Set=CMD_HALT; status2Send |= FLAG_COMMAND | FLAG_SEND_IMMEDIATE; } else { debugSerial<0); toExecute=chActive || toExecute; // Execute if active debugSerial<isAllowed(cmd)) && (!getFlag(FLAG_FREEZED))) { if (driver) //New style modular code { // UPDATE internal variables if (status2Send) cmd.saveItem(this,status2Send); res = driver->Ctrl(cmd, subItem, toExecute,authorized); if (driver->getChanType() == CH_THERMO) status2Send |= FLAG_SEND_IMMEDIATE; //if (res==-1) status2Send=0; ///////not working if (status2Send & FLAG_FLAGS) res =1; //ENABLE & DISABLE processed by core } else { switch (itemType) { /// rest of Legacy monolite core code (to be refactored ) BEGIN /// case CH_RELAY: if (cmd.isCommand()) { short iaddr=getArg(); short icmd =cmd.getCmd(); if (!authorized && isProtectedPin(iaddr)) {errorSerial<0) { pinMode(iaddr, OUTPUT); switch (icmd){ case CMD_AUTO: case CMD_COOL: case CMD_ON: case CMD_DRY: case CMD_FAN: case CMD_XON: digitalWrite(iaddr, k = (inverse) ? LOW : HIGH); break; case CMD_OFF: case CMD_HALT: case CMD_XOFF: digitalWrite(iaddr, k = (inverse) ? HIGH : LOW); } /* if (inverse) digitalWrite(iaddr, k = ((icmd == CMD_ON || icmd == CMD_AUTO) ? LOW : HIGH)); else digitalWrite(iaddr, k = ((icmd == CMD_ON || icmd == CMD_AUTO) ? HIGH : LOW)); */ debugSerial<name); tStore.tempX100=cmd.getFloat()*100.; //Save measurement tStore.timestamp16=millisNZ(8) & 0xFFFF; //And timestamp //debugSerial<=0) { cmd.setPercents(INIT_VOLUME); status2Send |= FLAG_PARAMETERS | FLAG_SEND_IMMEDIATE; }; res=modbusDimmerSet(cmd); } break; case CH_VC: // UPDATE internal variables if (status2Send) cmd.saveItem(this,status2Send); if (toExecute && !(chActive && cmd.getCmd()==CMD_ON && !cmd.isValue())) res=VacomSetFan(cmd); break; case CH_VCTEMP: // UPDATE internal variables if (status2Send) cmd.saveItem(this,status2Send); if (toExecute && !(chActive && cmd.getCmd()==CMD_ON && !cmd.isValue())) res=VacomSetHeat(cmd); break; #endif /// rest of Legacy monolite core code (to be refactored ) END /// } //switch } //else (nodriver) //update command for HALT & XON and send MQTT status if (command2Set) setCmd(command2Set | FLAG_COMMAND); if (operation) SendStatus(status2Send); } //alowed cmd else { errorSerial<name<isActive(); if (active >= 0) { printActiveStatus(active); return active; } } // No driver - check command if (itemType != CH_GROUP) // Simple check last command first switch (cmd) { case CMD_ON: case CMD_XON: case CMD_AUTO: case CMD_HEAT: case CMD_COOL: printActiveStatus(true); return 1; case CMD_OFF: case CMD_HALT: case -1: ///// No last command printActiveStatus(false); return 0; } // Last time was not a command but parameters set. Looking inside st.loadItem(this); switch (itemType) { case CH_GROUP: //make recursive calculation - is it some active in group if (itemArg->type == aJson_Array) { debugSerial<subtype & 0xF; bool authorized = (timestampObj->type==aJson_Reserved); int32_t remain = (uint32_t) timestampObj->valueint - (uint32_t)millis(); if (cmd) { itemCmd st(ST_UINT32,cmd); st.Int(remain); //if (!(remain % 1000)) if (cause == POLLING_1S) { SendStatusImmediate(st,FLAG_SEND_DELAYED); debugSerial<< remain/1000 << F(" sec remaining") << endl; } if (remain <0 && abs(remain)< 0xFFFFFFFFUL/2) { int fr = freeRam(); SendStatusImmediate(st,FLAG_SEND_DELAYED); if (fr < minimalMemory) { errorSerial<subtype=0; Ctrl(itemCmd(ST_VOID,cmd),NULL,true,authorized); //timestampObj->subtype=0; //// } } } switch (cause) { case POLLING_SLOW: // Legacy polling switch (itemType) { #ifndef MODBUS_DISABLE case CH_MODBUS: checkModbusDimmer(); sendDelayedStatus(); return true; break; case CH_VC: checkFM(); sendDelayedStatus(); return true; break; #endif default: sendDelayedStatus(); } } if (driver && driver->Status()) { return driver->Poll(cause); } return false; } void Item::sendDelayedStatus() { long int flags = getFlag(FLAG_COMMAND | FLAG_PARAMETERS); if (flags && lanStatus==OPERATION) { SendStatus(flags);//(FLAG_COMMAND | FLAG_PARAMETERS); clearFlag(FLAG_COMMAND | FLAG_PARAMETERS); } } int Item::SendStatus(int sendFlags) { if (sendFlags & FLAG_SEND_IMMEDIATE) sendFlags &= ~ (FLAG_SEND_IMMEDIATE | FLAG_SEND_DEFFERED); if ((sendFlags & FLAG_SEND_DEFFERED) || freeRam()<150 || (!isNotRetainingStatus() )) { setFlag(sendFlags & (FLAG_COMMAND | FLAG_PARAMETERS | FLAG_FLAGS)); debugSerial<name, sizeof(addrstr)-1); if (sendFlags & FLAG_PARAMETERS && st.getCmd() != CMD_OFF && st.getCmd() != CMD_HALT && // send only for OH bus supported types (st.getArgType() == ST_PERCENTS255 || st.getArgType() == ST_HSV255 || st.getArgType() == ST_FLOAT_CELSIUS)) { st.toString(valstr, sizeof(valstr), FLAG_PARAMETERS,true); mqttClient.publish(addrstr, valstr, true); debugSerial<")<")<name, sizeof(addrstr)-1); if (subItem) { strncat(addrstr, "/", sizeof(addrstr)-1); strncat(addrstr, subItem, sizeof(addrstr)-1); } strncat(addrstr, "/", sizeof(addrstr)-1); // if (sendFlags & FLAG_SEND_DELAYED) // strncat_P(addrstr, DEL_P, sizeof(addrstr)-1); // else strncat_P(addrstr, SET_P, sizeof(addrstr)-1); if (sendFlags & FLAG_SEND_DELAYED) strncat_P(addrstr, suffix_P[S_DELAYED], sizeof(addrstr)-1); else strncat_P(addrstr, suffix_P[S_SET], sizeof(addrstr)-1); // Preparing parameters payload ////////// switch (st.getArgType()) { case ST_RGB: case ST_RGBW: //valstr[0]='#'; st.Cmd(CMD_RGB); st.toString(valstr, sizeof(valstr), FLAG_PARAMETERS|FLAG_COMMAND); break; default: st.toString(valstr, sizeof(valstr), (sendFlags & FLAG_SEND_DELAYED)?FLAG_COMMAND|FLAG_PARAMETERS:FLAG_PARAMETERS,(SCALE_VOLUME_100)); } debugSerial<")<name, sizeof(addrstr)-1); if (subItem) { strncat(addrstr, "/", sizeof(addrstr)-1); strncat(addrstr, subItem, sizeof(addrstr)-1); } strncat(addrstr, "/", sizeof(addrstr)-1); // strncat_P(addrstr, CMD_P, sizeof(addrstr)-1); strncat_P(addrstr, suffix_P[S_CMD], sizeof(addrstr)-1); debugSerial<")<name, sizeof(addrstr)-1); if (subItem) { strncat(addrstr, "/", sizeof(addrstr)-1); strncat(addrstr, subItem, sizeof(addrstr)-1); } strncat(addrstr, "/", sizeof(addrstr)-1); //strncat_P(addrstr, CTRL_P, sizeof(addrstr)-1); strncat_P(addrstr, suffix_P[S_CTRL], sizeof(addrstr)-1); debugSerial<")<getChanType(); return itemType; } // Setup FLAG_SEND_RETRY flag to repeat unsucsessfull modbus tranzaction after release line void Item::mb_fail(int result) { debugSerial<name<Ctrl(val); } #ifndef MODBUS_DISABLE else switch (itemType) { case CH_MODBUS: if (modbusBusy) return M_BUSY; clearFlag(FLAG_SEND_RETRY); // Clean retry flag debugSerial<name<name<4 [22:20:33] Write task has completed successfully [22:20:33] <= Response: 0A 06 07 D0 00 04 89 FF [22:20:32] => Poll: 0A 06 07 D0 00 04 89 FF 100% 2003-> 10000 [22:24:05] Write task has completed successfully [22:24:05] <= Response: 0A 06 07 D2 27 10 33 C0 [22:24:05] => Poll: 0A 06 07 D2 27 10 33 C0 ON 2001->1 [22:24:50] Write task has completed successfully [22:24:50] <= Response: 0A 06 07 D0 00 01 49 FC [22:24:50] => Poll: 0A 06 07 D0 00 01 49 FC OFF 2001->0 [22:25:35] Write task has completed successfully [22:25:35] <= Response: 0A 06 07 D0 00 00 88 3C [22:25:34] => Poll: 0A 06 07 D0 00 00 88 3C POLL 2101x10 [22:27:29] <= Response: 0A 03 14 00 23 00 00 27 10 13 88 0B 9C 00 32 00 F8 00 F2 06 FA 01 3F AD D0 [22:27:29] => poll: 0A 03 08 34 00 0A 87 18 */ #ifndef MODBUS_DISABLE extern ModbusMaster node; int Item::modbusDimmerSet(itemCmd st) { int value=st.getPercents(); int cmd=st.getCmd(); switch (cmd){ case CMD_OFF: case CMD_HALT: value=0; break; } short numpar=0; if ((itemArg->type == aJson_Array) && ((numpar = aJson.getArraySize(itemArg)) >= 2)) { int _addr = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_ADDR)->valueint; int _reg = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_REG)->valueint; int _mask = -1; if (numpar >= (MODBUS_CMD_ARG_MASK+1)) _mask = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_MASK)->valueint; int _maxval = 0x3f; if (numpar >= (MODBUS_CMD_ARG_MAX_SCALE+1)) _maxval = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_MAX_SCALE)->valueint; int _regType = MODBUS_HOLDING_REG_TYPE; if (numpar >= (MODBUS_CMD_ARG_REG_TYPE+1)) _regType = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_REG_TYPE)->valueint; if (_maxval) return modbusDimmerSet(_addr, _reg, _regType, _mask, map(value, 0, 100, 0, _maxval)); else return modbusDimmerSet(_addr, _reg, _regType, _mask, value); } return 0; } int Item::VacomSetFan(itemCmd st) { int val=st.getPercents(); int cmd=st.getCmd(); if (st.isCommand()) switch (st.getSuffix()){ case S_CMD: case S_NOTFOUND: break; default: return -1; } switch (cmd){ case CMD_OFF: case CMD_HALT: // produvka here val=0; break; } uint8_t result; int addr = getArg(); debugSerial<type != aJson_String) return 0; Item it(itemArg->valuestring); if (it.isValid() && it.itemType == CH_VC) { addr=it.getArg(); } else return 0; debugSerial<> 8; value <<=8; value |= t; } switch (_regType) { case MODBUS_HOLDING_REG_TYPE: result = node.writeSingleRegister(_reg, value); break; case MODBUS_COIL_REG_TYPE: result = node.writeSingleCoil(_reg, value); break; default: debugSerial<name, sizeof(addrstr) - 1); strncat(addrstr, "_stat", sizeof(addrstr) - 1); // aJson.addStringToObject(out,"type", "rect"); modbusSerial.begin(MODBUS_FM_BAUD, MODBUS_FM_PARAM); node.begin(getArg(), modbusSerial); debugSerial << F("MB: polling FM ") << itemArr->name<< endl; delay(50); result = node.readHoldingRegisters(2101 - 1, 10); // do something with data if read is successful if (result == node.ku8MBSuccess) { debugSerial<type == aJson_Array) { aJsonObject *airGateObj = aJson.getArrayItem(itemArg, 1); if (airGateObj && airGateObj->type == aJson_String) { int val = 100; Item item(airGateObj->valuestring); if (item.isValid()) // item.Ctrl(0, 1, &val); item.Ctrl(itemCmd().Percents(val)); } } } else debugSerial << F("MB: polling ") << itemArr->name<< F(" error=") << _HEX(result) << endl; if (node.getResponseBuffer(0) & 8) //Active fault { debugSerial << F("MB: polling FM fault ") << itemArr->name<< endl; delay(50); result = node.readHoldingRegisters(2111 - 1, 1); if (result == node.ku8MBSuccess) aJson.addNumberToObject(out, "flt", (long int) node.getResponseBuffer(0)); modbusBusy=0; //resumeModbus(); if (isActive()>0) Off(); //Shut down /// modbusBusy=1; } else aJson.addNumberToObject(out, "flt", (long int)0); debugSerial << F("MB: polling PI ") << itemArr->name<< endl; delay(50); result = node.readHoldingRegisters(20 - 1, 4); // do something with data if read is successful if (result == node.ku8MBSuccess) { debugSerial << F("MB: PI Val :"); for (j = 0; j < 4; j++) { data = node.getResponseBuffer(j); debugSerial << data << F("-"); } debugSerial << endl; int set = node.getResponseBuffer(0); float ftemp, fset = set * a + b; if (set) aJson.addNumberToObject(out, "set", fset); aJson.addNumberToObject(out, "t", ftemp = (int) node.getResponseBuffer(1) * a + b); // aJson.addNumberToObject(out,"d", (int) node.getResponseBuffer(2)*a+b); int16_t pwr = node.getResponseBuffer(3); if (pwr > 0) aJson.addNumberToObject(out, "pwr", pwr / 10.); else aJson.addNumberToObject(out, "pwr", (long int) 0); if (ftemp > FM_OVERHEAT_CELSIUS && set) { if (mqttClient.connected() && !ethernetIdleCount) mqttClient.publish("/alarm/ovrht", itemArr->name); Off(); //Shut down } } else debugSerial << F("MB: polling PI ") << itemArr->name<< F(" error=") << _HEX(result) << endl; outch = aJson.print(out); if (mqttClient.connected() && !ethernetIdleCount) mqttClient.publish(addrstr, outch); free(outch); aJson.deleteItem(out); modbusBusy = 0; //resumeModbus(); return 1; } int Item::checkModbusDimmer() { if (modbusBusy) return -1; //if (checkModbusRetry()) return -2; short numpar = 0; if ((itemArg->type != aJson_Array) || ((numpar = aJson.getArraySize(itemArg)) < 2)) { debugSerial<= (MODBUS_CMD_ARG_REG_TYPE+1)) _regType = aJson.getArrayItem(itemArg, MODBUS_CMD_ARG_REG_TYPE)->valueint; int data; //node.setSlave(addr); modbusSerial.begin(MODBUS_SERIAL_BAUD, MODBUS_SERIAL_PARAM); node.begin(addr, modbusSerial); debugSerial << F("MB: polling dimmer ") << itemArr->name<< endl; switch (_regType) { case MODBUS_HOLDING_REG_TYPE: result = node.readHoldingRegisters(reg, 1); break; case MODBUS_COIL_REG_TYPE: result = node.readCoils(reg, 1); break; case MODBUS_DISCRETE_REG_TYPE: result = node.readDiscreteInputs(reg, 1); break; case MODBUS_INPUT_REG_TYPE: result = node.readInputRegisters(reg, 1); break; default: debugSerial<name << F(" Val: ") << _HEX(data) << endl; checkModbusDimmer(data); // Looking 1 step ahead for modbus item, which uses same register Item nextItem(pollingItem->next); if (pollingItem && nextItem.isValid() && nextItem.itemType == CH_MODBUS && nextItem.getArg(0) == addr && nextItem.getArg(1) == reg) { nextItem.checkModbusDimmer(data); pollingItem = pollingItem->next; if (!pollingItem) pollingItem = items->child; } } else debugSerial << F("MB: polling ") << itemArr->name<< F(" error=") << _HEX(result) << endl; modbusBusy = 0; //resumeModbus(); return 1; } int Item::checkModbusDimmer(int data) { if (getFlag(FLAG_SEND_RETRY)) return 0; //Active send transaction short mask = getArg(2); itemCmd st; if (mask < 0) return 0; st.loadItem(this); short maxVal = getArg(3); if (maxVal<=0) maxVal = 0x3f; int d = data; if (mask == 1) d >>= 8; if (mask == 0 || mask == 1) d &= 0xff; if (maxVal) d = map(d, 0, maxVal, 0, 255); int cmd = getCmd(); //debugSerial<