From bbceb044c14f06fdeb544ba39c6a1a064d82ac80 Mon Sep 17 00:00:00 2001 From: "anklimov@gmail.com" Date: Sat, 11 Apr 2026 23:58:25 +0300 Subject: [PATCH] MultiAC pre-releas 3 --- lighthub/modules/out_multivent.cpp | 239 ++++++++++++++++++----------- lighthub/modules/out_multivent.h | 4 +- lighthub/modules/out_pid.h | 12 ++ lighthub/utils.cpp | 17 ++ lighthub/utils.h | 11 +- 5 files changed, 189 insertions(+), 94 deletions(-) diff --git a/lighthub/modules/out_multivent.cpp b/lighthub/modules/out_multivent.cpp index 93f2572..ac76be7 100644 --- a/lighthub/modules/out_multivent.cpp +++ b/lighthub/modules/out_multivent.cpp @@ -58,17 +58,18 @@ if (gatesObj) convert2float(poObj); int direction = DIRECT; - float kP=getFloatFromJson(pidObj,0,1.0); + float kP=getFloatFromJson(pidObj,"kP",0,1.0); if (kP<0) { kP=-kP; direction=REVERSE; } - float kI=getFloatFromJson(pidObj,1); - float kD=getFloatFromJson(pidObj,2); - float dT=getFloatFromJson(pidObj,3,5.0); + float kI=getFloatFromJson(pidObj,"kI",1,0.0); + float kD=getFloatFromJson(pidObj,"kD",2,0.0); + float dT=getFloatFromJson(pidObj,"dT",3,5.0); - pidObj->valueint = (long int) new PID (&valObj->valuefloat, &poObj->valuefloat, &setObj->valuefloat, kP, kI, kD, direction); + pidObj->valueint = (long int) new PID (&valObj->valuefloat, &poObj->valuefloat, &setObj->valuefloat, kP, kI, kD, P_ON_E,direction, + getFloatFromJson(i,"AlarmTime",4,120.0)*1000UL, getFloatFromJson(i,"AlarmValue",5,0.0)); //((PID*) pidObj->valueint)->SetMode (AUTOMATIC); ((PID*) pidObj->valueint)->SetSampleTime(dT*1000.0); @@ -114,7 +115,7 @@ return 1; int out_Multivent::isActive() { - debugSerial<<"VENT:active: "; + //debugSerial<<"VENT:active: "; if (gatesObj) { /* @@ -153,6 +154,8 @@ int out_Multivent::isActive() return 0; } +#define assign_if_positive(var, source) {int x=source; if (x>=0) {var=x;}} + int out_Multivent::Poll(short cause) { if (!acObj || !gatesObj) return 0; @@ -166,14 +169,15 @@ int out_Multivent::Poll(short cause) // metrics, collected from AC float acTemp = getFloatFromJson(acObj,"val",NAN); - int actualCmd = getIntFromJson (acObj,"mode"); + int acCmd = getIntFromJson (acObj,"mode"); // global params int boostTreshold = getIntFromJson (acObj,"boost",AC_BOOST_TRESHOLD); int actualMode = CMD_FAN; if (acTemp>30.0) actualMode = CMD_HEAT; else if (acTemp<15.0) actualMode = CMD_COOL; - + + if (isnan(acTemp) && acCmd) actualMode = acCmd; aJsonObject * i = gatesObj->child; int balance = 0; @@ -181,23 +185,36 @@ int out_Multivent::Poll(short cause) bool autoRequested = false; //At least 1 ch requested AUTO mode bool pidActive = false; bool pidComputed = false; + int lastACfan = -1; while (i) { if (i->name && *i->name) { - int cmd = getIntFromJson(i,"cmd"); + int cmd = getIntFromJson (i,"cmd"); float set = getFloatFromJson(i,"set"); float val = getFloatFromJson(i,"val"); int execCmd = 0; bool weakMode=false; // kind of modes when we activating PID only if AC in active mode switch (cmd) - { + { case CMD_HEATCOOL: { - if (set>val) execCmd = CMD_HEAT; - if (set val + { + case CMD_HEAT: + if (set>val) execCmd = CMD_HEAT; + if (setval+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT; + break; + default: + if (set>val+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT; + if (setvalueint && poObj && poObj->type == aJson_Float) + if (pidObj && pidObj->valueint && poObj && poObj->type == aJson_Float && valObj && valObj->type == aJson_Float) { - int pidAlarmTime = getIntFromJson(i,"pidAlarmTime",120000L); - if (isTimeOver(getIntFromJson(i,"valTS"),millisNZ(),pidAlarmTime)) //if value is fresh - use it, otherwise set val to NAN - setValToJson(i,"val",NAN); - PID * p = (PID *) pidObj->valueint; - - if ((execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true; + PID * p = (PID *) pidObj->valueint; + + /// Setup PID mode and direction based on real AC mode switch (actualMode) { //if air hot or cold - uses temp PID and block control by /fan case CMD_HEAT: @@ -244,43 +258,41 @@ int out_Multivent::Poll(short cause) { p->SetMode(MANUAL); debugSerial<name<name,true,true); + assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(getIntFromJson(i,"fan_in",0)).setSuffix(S_FAN),i->name,true)); } - } + } + + if (!p->isOutdated() && (execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true; + if (p->Compute()) - { + { debugSerial<itemArr->name<<"/"<name <GetIn()<GetSet()<GetOut() <<" P:"<GetKp()<<" I:"<GetKi()<<" D:"<GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M"); + if (p->isOutdated()) debugSerial << F(" "); debugSerial<valuefloat)) - { - poObj->valuefloat = getFloatFromJson(i,"pidAlarmValue",NAN); - debugSerial<valuefloat<valuefloat)) switch (execCmd) { case CMD_HEAT: - if (actualCmd==CMD_COOL) //close - fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true); + if (actualMode==CMD_COOL) //close + assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true)) else - fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true); + assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true)) balance+=poObj->valuefloat; pidComputed = true; break; case CMD_COOL: - if (actualCmd==CMD_HEAT) //close - fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true); + if (actualMode==CMD_HEAT) //close + assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true)) else - fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true,true); + assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true)); balance-=poObj->valuefloat; pidComputed = true; @@ -292,25 +304,25 @@ int out_Multivent::Poll(short cause) case CMD_HEAT: debugSerial<itemArr->name<<"/"<name<valuefloat <valuefloat).setSuffix(S_FAN),i->name,true,true); + if (cmd !=CMD_OFF || passiveMode) assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true)); break; case CMD_COOL: debugSerial<itemArr->name<<"/"<name<valuefloat <valuefloat).setSuffix(S_FAN),i->name,true,true); + if (cmd !=CMD_OFF || passiveMode) assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true)); break; case CMD_FAN: //no more hot or cold air ((PID *) pidObj->valueint)->SetMode(MANUAL); } - } } } else //PID not computed - maybe not in time, but we can use PID output as indicator of balance and boost if needed { if (p->GetMode() == AUTOMATIC && !isnan(poObj->valuefloat)) { - if (execCmd == CMD_HEAT) balance+=poObj->valuefloat; - else if (execCmd == CMD_COOL) balance-=poObj->valuefloat; + balance+=(p->GetDirection() == DIRECT) ? poObj->valuefloat : -poObj->valuefloat; + //if (execCmd == CMD_HEAT) balance+=poObj->valuefloat; + //else if (execCmd == CMD_COOL) balance-=poObj->valuefloat; } } } @@ -318,7 +330,8 @@ int out_Multivent::Poll(short cause) } i=i->next; }//while - if (pidComputed) + + if (pidComputed) //Active mode. PIDs in pasive mode do not triggered this flag { debugSerial<boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(AC_BOOST_LOW_TEMP).setSuffix(S_SET)); else { - if (abs(balance)0 || (lastACfan == 0 && (!activeMode || noFurtherModes))) sendACcmd(itemCmd().Percents255(lastACfan).setSuffix(S_FAN)); //if PID already set some fan value - send it to AC return 1; }; @@ -345,7 +363,7 @@ return 1; int out_Multivent::getChanType() { - return CH_THERMO; /////PWM + return CH_THERMO; } @@ -431,15 +449,28 @@ void out_Multivent::clearFlag (aJsonObject* zone, uint32_t flag) int out_Multivent::Ctrl(itemCmd cmd, char* subItem , bool toExecute, bool authorized) { -return fanCtrl(cmd,subItem, toExecute, false); + int result = fanCtrl(cmd,subItem, false); + + if (result>=0) + { + debugSerial << F("VENT: CTRL fan level: ")<setExt(millisNZ()); //setup validity interval setValToJson(acObj,"val",cmd.getFloat()); } - return 1; + return -1; case S_FAN: if (cmd.isValue()) { - debugSerial << F("VENT:")<valueint != CMD_OFF && cmdObj->valueint != CMD_HALT) break; cmd.Percents255(fanObj->valueint); cmd.setSuffix(S_FAN); @@ -625,7 +667,7 @@ while (i) case CMD_ON: break; case CMD_OFF: - if (getFlag(i,FLAG_FREEZED)) {debugSerial<valueint != CMD_OFF) setValToJson(i,"@precmd",cmdObj->valueint); //saving previous mode cmd.Percents255(0); cmd.setSuffix(S_FAN); @@ -655,11 +697,11 @@ while (i) case CMD_FREEZE: setFlag(i,FLAG_FREEZED); - return 1; + return -1; case CMD_UNFREEZE: clearFlag(i,FLAG_FREEZED); - return 1; + return -1; case CMD_COOL: case CMD_DRY: @@ -704,18 +746,23 @@ while (i) if (cmd.isValue()) { setValToJson(i,"set",cmd.getFloat()); - sendFlags |= FLAG_PARAMETERS; - } + sendFlags |= FLAG_PARAMETERS; + exitAfterSendingStatus = true; //if setpoint updated - send status immediately to update PID and sending status + } + else return -1; break; case S_VAL: if (cmd.isValue()) { - debugSerial<valueint) + { + ((PID *) pidObj->valueint)->SetVal(cmd.getFloat()); + debugSerial<name<valueint != CMD_OFF && cmdObj->valueint != -1) || passiveMode) { @@ -767,17 +815,14 @@ if (!totalV) return 0; int fanV=activeV/totalV; debugSerial << F("VENT: Total V:")<child; //Pass 2: re-distribute airflow @@ -815,7 +860,7 @@ while (i) i=i->next; } -return 1; +return fanV; } void out_Multivent::enablePid(aJsonObject* pidObj, int enable, int direction ) @@ -839,6 +884,7 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj) if (!acObj) return; int lastCmd = getIntFromJson(acObj,"@lastCmd"); int prevACcmd = getIntFromJson(acObj,"mode"); + int lastFan = getIntFromJson(acObj,"@lastFan",-1); if (!prevACcmd) { debugSerial<<"VENT: AC MODE received initially "< 0 && !isActiveNow) { @@ -943,6 +997,7 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj) break; case S_SET: if (!(supress & AC_SUPPRESS_SET) || lastSet != cmd.getFloat()) { + debugSerial<<"VENT: sendACcmd. set="; cmd.debugOut(); debugSerial< #include #include "itemCmd.h" +#include "aJson.h" #define OUTPUT_TRESHOLD 1.0 +class pidJson { + public: + pidJson(aJsonObject * _obj){obj=_obj;}; + + aJsonObject * getValObj(){return NULL;}; + aJsonObject * getSetObj(){return NULL;}; + aJsonObject * getPoObj(){return NULL;}; + PID * getPID(){return (obj) ? (PID*) obj->valueint: NULL;}; + aJsonObject * obj; +}; + class pidPersistent : public chPersistent { public: PID * pid; diff --git a/lighthub/utils.cpp b/lighthub/utils.cpp index f28bb5c..c123ff5 100644 --- a/lighthub/utils.cpp +++ b/lighthub/utils.cpp @@ -1061,6 +1061,14 @@ if (element && element->type == aJson_Boolean) return element->valuebool; return def; } + long getIntFromJson(aJsonObject * a, const char * name, int i,long def) + { +if (!a) return def; +if (a->type == aJson_Object) return getIntFromJson(a,name,def); +else if (a->type == aJson_Array) return getIntFromJson(a,i,def); +else return def; + } + float getFloatFromJson(aJsonObject * a, int i, float def) { aJsonObject * element = NULL; @@ -1086,6 +1094,15 @@ if (element && element->type == aJson_Int) return element->valueint; return def; } + float getFloatFromJson(aJsonObject * a, const char * name, int i, float def) +{ +aJsonObject * element = NULL; +if (!a) return def; +if (a->type == aJson_Object) return getFloatFromJson(a,name,def); +else if (a->type == aJson_Array) return getFloatFromJson(a,i,def); +else return def; +} + aJsonObject * getCreateObject(aJsonObject * a, int n) { if (!a) return NULL; diff --git a/lighthub/utils.h b/lighthub/utils.h index 2fccb24..d9be7f2 100644 --- a/lighthub/utils.h +++ b/lighthub/utils.h @@ -88,6 +88,8 @@ bool checkToken(char * token, char * data); long getIntFromJson(aJsonObject * a, const char * name, long def = 0); float getFloatFromJson(aJsonObject * a, int i, float def = 0.0); float getFloatFromJson(aJsonObject * a, const char * name, float def = 0.0); + long getIntFromJson(aJsonObject * a, const char * name, int i,long def); +float getFloatFromJson(aJsonObject * a, const char * name, int i, float def); @@ -147,7 +149,14 @@ if (a->type == aJson_Object) return NULL; } - + template + aJsonObject * getCreateObject(aJsonObject * a, const char * name, int n, Type def) +{ +if (!a) return NULL; +if (a->type == aJson_Object) return getCreateObject(a,name,def); +else if (a->type == aJson_Array) return getCreateObject(a,n,def); +else return NULL; +} template aJsonObject * setValToJson(aJsonObject * a, const char * name, Type val)