Multi AC pre-release 2

This commit is contained in:
2026-04-05 20:57:16 +03:00
parent 00969f88a4
commit b5ef7ff221
2 changed files with 189 additions and 93 deletions

View File

@@ -52,7 +52,7 @@ if (gatesObj)
{ {
aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0); aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0);
convert2float(setObj); convert2float(setObj);
aJsonObject * valObj = getCreateObject(i,"val",(float) 20.0); aJsonObject * valObj = getCreateObject(i,"val",(float) NAN);
convert2float(valObj); convert2float(valObj);
aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0); aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0);
convert2float(poObj); convert2float(poObj);
@@ -169,7 +169,7 @@ int out_Multivent::Poll(short cause)
int actualCmd = getIntFromJson (acObj,"mode"); int actualCmd = getIntFromJson (acObj,"mode");
// global params // global params
int boostTreshold = getIntFromJson (acObj,"boost"); int boostTreshold = getIntFromJson (acObj,"boost",AC_BOOST_TRESHOLD);
int actualMode = CMD_FAN; int actualMode = CMD_FAN;
if (acTemp>30.0) actualMode = CMD_HEAT; if (acTemp>30.0) actualMode = CMD_HEAT;
else if (acTemp<15.0) actualMode = CMD_COOL; else if (acTemp<15.0) actualMode = CMD_COOL;
@@ -185,9 +185,9 @@ int out_Multivent::Poll(short cause)
{ {
if (i->name && *i->name) if (i->name && *i->name)
{ {
int cmd = getIntFromJson(i,"cmd"); int cmd = getIntFromJson(i,"cmd");
int set = getIntFromJson(i,"set"); float set = getFloatFromJson(i,"set");
int val = getIntFromJson(i,"val"); float val = getFloatFromJson(i,"val");
int execCmd = 0; int execCmd = 0;
bool weakMode=false; // kind of modes when we activating PID only if AC in active mode bool weakMode=false; // kind of modes when we activating PID only if AC in active mode
@@ -219,30 +219,34 @@ int out_Multivent::Poll(short cause)
bool passiveMode = getIntFromJson(i,"@pasv",0); bool passiveMode = getIntFromJson(i,"@pasv",0);
aJsonObject * pidObj = aJson.getObjectItem(i, "pid"); aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
aJsonObject * poObj = aJson.getObjectItem(i,"po"); aJsonObject * poObj = aJson.getObjectItem(i, "po");
if (pidObj && pidObj->valueint && poObj && poObj->type == aJson_Float) if (pidObj && pidObj->valueint && poObj && poObj->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; PID * p = (PID *) pidObj->valueint;
if ((execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true;
switch (actualMode) if ((execCmd == CMD_HEAT || execCmd == CMD_COOL) && p->GetMode() == AUTOMATIC) pidActive = true;
{ //if air hot or cold - uses temp PID and block control by /fan switch (actualMode)
case CMD_HEAT: { //if air hot or cold - uses temp PID and block control by /fan
if (weakMode || passiveMode) p->SetMode(AUTOMATIC); case CMD_HEAT:
p->SetControllerDirection(DIRECT); if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
break; p->SetControllerDirection(DIRECT);
case CMD_COOL: break;
if (weakMode || passiveMode) p->SetMode(AUTOMATIC); case CMD_COOL:
p->SetControllerDirection(REVERSE); if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
break; p->SetControllerDirection(REVERSE);
default: break;
if ((passiveMode || weakMode || execCmd ==CMD_OFF) && p->GetMode() == AUTOMATIC) default:
{ if ((passiveMode || weakMode || execCmd ==CMD_OFF) && p->GetMode() == AUTOMATIC)
p->SetMode(MANUAL); {
debugSerial<<F("VENT: PID set to MANUAL due no HEAT/COOL. zone:")<<i->name<<endl; p->SetMode(MANUAL);
fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true); debugSerial<<F("VENT: PID set to MANUAL due no HEAT/COOL. zone:")<<i->name<<endl;
} fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true,true);
} }
}
if (p->Compute()) if (p->Compute())
{ {
{ {
@@ -252,7 +256,13 @@ int out_Multivent::Poll(short cause)
<<" P:"<<p->GetKp()<<" I:"<<p->GetKi()<<" D:"<<p->GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M"); <<" P:"<<p->GetKp()<<" I:"<<p->GetKi()<<" D:"<<p->GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M");
debugSerial<<endl; debugSerial<<endl;
if (isnan(poObj->valuefloat))
{
poObj->valuefloat = getFloatFromJson(i,"pidAlarmValue",NAN);
debugSerial<<F("VENT: PID output is NAN, using default:")<<poObj->valuefloat<<endl;
}
if (!isnan(poObj->valuefloat))
switch (execCmd) switch (execCmd)
{ {
case CMD_HEAT: case CMD_HEAT:
@@ -297,7 +307,7 @@ int out_Multivent::Poll(short cause)
} }
else //PID not computed - maybe not in time, but we can use PID output as indicator of balance and boost if needed 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) if (p->GetMode() == AUTOMATIC && !isnan(poObj->valuefloat))
{ {
if (execCmd == CMD_HEAT) balance+=poObj->valuefloat; if (execCmd == CMD_HEAT) balance+=poObj->valuefloat;
else if (execCmd == CMD_COOL) balance-=poObj->valuefloat; else if (execCmd == CMD_COOL) balance-=poObj->valuefloat;
@@ -312,11 +322,11 @@ int out_Multivent::Poll(short cause)
{ {
debugSerial<<F("VENT: Chan balance=")<<balance<<F(" treshold:")<<boostTreshold<<endl; debugSerial<<F("VENT: Chan balance=")<<balance<<F(" treshold:")<<boostTreshold<<endl;
if (balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_HEAT).Int(30).setSuffix(S_SET)); if (balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_HEAT).Int(AC_BOOST_HIGH_TEMP).setSuffix(S_SET));
else if (-balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(18).setSuffix(S_SET)); else if (-balance>boostTreshold) setBoost(itemCmd().Cmd(CMD_COOL).Int(AC_BOOST_LOW_TEMP).setSuffix(S_SET));
else else
{ {
pidActive = false; if (abs(balance)<AC_BOOST_DEADBAND) pidActive = false;
} }
} }
@@ -435,6 +445,34 @@ if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command fi
int turnbyfan = getIntFromJson(acObj,"turnbyfan",0); int turnbyfan = getIntFromJson(acObj,"turnbyfan",0);
if (cmd.getSuffix()==S_FAN)
{
switch (cmd.getCmd())
{
case CMD_HIGH:
cmd.Percents255(255);
cmd.Cmd(0);
break;
case CMD_MED:
cmd.Percents255(128);
cmd.Cmd(0);
break;
case CMD_LOW:
cmd.Percents255(64);
cmd.Cmd(0);
break;
// case CMD_OFF:
// cmd.Percents255(0);
// cmd.Cmd(0);
// break;
default:
if (cmd.isValue()) cmd.Percents255(cmd.getInt()); // convert to integer
} //switch cmd
}
if (!subItem) // feedback from shared AC if (!subItem) // feedback from shared AC
{ {
switch (suffixCode) switch (suffixCode)
@@ -451,7 +489,8 @@ switch (suffixCode)
case S_FAN: case S_FAN:
if (cmd.isValue()) if (cmd.isValue())
{ {
debugSerial << F("VENT:")<<F("AC fan: ")<< cmd.getCmd()<<endl; debugSerial << F("VENT:")<<F("AC fan: ")<< cmd.getInt()<<endl;
checkACfan(cmd.getInt());
setValToJson(acObj,"fan",cmd.getInt()); setValToJson(acObj,"fan",cmd.getInt());
} }
return 1; return 1;
@@ -459,7 +498,8 @@ switch (suffixCode)
case S_SET: case S_SET:
if (cmd.isValue()) if (cmd.isValue())
{ {
debugSerial << F("VENT:")<<F("AC set: ")<< cmd.getCmd()<<endl; debugSerial << F("VENT:")<<F("AC set: ")<< cmd.getFloat()<<endl;
checkACset(cmd.getFloat());
setValToJson(acObj,"set",cmd.getFloat()); setValToJson(acObj,"set",cmd.getFloat());
} }
return 1; return 1;
@@ -468,6 +508,7 @@ switch (suffixCode)
if (cmd.isCommand()) if (cmd.isCommand())
{ {
debugSerial << F("VENT:")<<F("AC mode: ")<< cmd.getCmd()<<endl; debugSerial << F("VENT:")<<F("AC mode: ")<< cmd.getCmd()<<endl;
checkACcmd(cmd.getCmd());
setValToJson(acObj,"mode",cmd.getCmd()); setValToJson(acObj,"mode",cmd.getCmd());
} }
return 1; return 1;
@@ -485,34 +526,6 @@ switch (suffixCode)
aJsonObject * i = NULL; aJsonObject * i = NULL;
if (cmd.getSuffix()==S_FAN)
{
switch (cmd.getCmd())
{
case CMD_HIGH:
cmd.Percents255(255);
cmd.Cmd(0);
break;
case CMD_MED:
cmd.Percents255(128);
cmd.Cmd(0);
break;
case CMD_LOW:
cmd.Percents255(10);
cmd.Cmd(0);
break;
// case CMD_OFF:
// cmd.Percents255(0);
// cmd.Cmd(0);
// break;
default:
if (cmd.isValue()) cmd.Percents255(cmd.getInt()); // convert to integer
} //switch cmd
}
if (gatesObj) i = gatesObj->child; // Pass 1 - calculate summ air value, max value etc if (gatesObj) i = gatesObj->child; // Pass 1 - calculate summ air value, max value etc
int activeV = 0; int activeV = 0;
@@ -553,12 +566,6 @@ while (i)
debugSerial<<F("VENT: FAN control disabled by PID")<<endl; debugSerial<<F("VENT: FAN control disabled by PID")<<endl;
return 1; return 1;
} }
// on boost something requested - remove
//if (!force && getFlag(acObj,FLAG_ACTION_NEEDED))
// {
// debugSerial<<F("VENT: FAN control disabled in boost mode")<<endl;
// return 1;
// }
if (cmd.getInt()) if (cmd.getInt())
{ {
@@ -567,8 +574,6 @@ while (i)
cmd.Cmd(turnbyfan); cmd.Cmd(turnbyfan);
debugSerial<<"VENT: generating cmd by fan: "<<turnbyfan<<endl; debugSerial<<"VENT: generating cmd by fan: "<<turnbyfan<<endl;
} }
//fanObj->valueint = cmd.getInt();
//sendFlags |= FLAG_PARAMETERS;
} }
else else
{ {
@@ -576,12 +581,7 @@ while (i)
{ {
cmd.Cmd(CMD_OFF); cmd.Cmd(CMD_OFF);
debugSerial<<"VENT: Turning OFF by fan"<<endl; debugSerial<<"VENT: Turning OFF by fan"<<endl;
//setValToJson(i,"@precmd",cmdObj->valueint);
//cmdObj->valueint = CMD_OFF;
//sendFlags |= FLAG_COMMAND;
} }
//fanObj->valueint = 0;
//sendFlags |= FLAG_PARAMETERS;
} }
fanObj->valueint = cmd.getInt(); // fanObj->valueint = cmd.getInt(); //
if (!passiveMode) sendFlags |= FLAG_PARAMETERS; // if (!passiveMode) sendFlags |= FLAG_PARAMETERS; //
@@ -713,6 +713,7 @@ while (i)
{ {
debugSerial<<F("VENT: value ")<<cmd.getFloat()<<endl; debugSerial<<F("VENT: value ")<<cmd.getFloat()<<endl;
setValToJson(i,"val",cmd.getFloat()); setValToJson(i,"val",cmd.getFloat());
setValToJson(i,"valTS",millisNZ()); //setup validity interval
} }
return 1; return 1;
break; break;
@@ -767,7 +768,8 @@ int fanV=activeV/totalV;
debugSerial << F("VENT: Total V:")<<totalV<<F(" active V:")<<activeV/255<< F(" fan%:")<<fanV<< F(" Max req:")<<maxRequestedV/255 <<F(" from ")<<maxV<<F(" m3")<< endl; debugSerial << F("VENT: Total V:")<<totalV<<F(" active V:")<<activeV/255<< F(" fan%:")<<fanV<< F(" Max req:")<<maxRequestedV/255 <<F(" from ")<<maxV<<F(" m3")<< endl;
//executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd((fanV)?CMD_ON:CMD_OFF)); //executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd((fanV)?CMD_ON:CMD_OFF));
executeCommand(acObj,-1,itemCmd().Percents255(fanV).setSuffix(S_FAN)); //executeCommand(acObj,-1,itemCmd().Percents255(fanV).setSuffix(S_FAN));
sendACcmd(itemCmd().Percents255(fanV).setSuffix(S_FAN));
/* /*
if (fanV) if (fanV)
executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd(CMD_ON)); executeCommand(aJson.getObjectItem(gatesObj, ""),-1,itemCmd().Percents255(fanV).Cmd(CMD_ON));
@@ -831,13 +833,66 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj)
return ((pidObj && pidObj->valueint) && (((PID *) pidObj->valueint)->GetMode() ==AUTOMATIC)); return ((pidObj && pidObj->valueint) && (((PID *) pidObj->valueint)->GetMode() ==AUTOMATIC));
} }
void out_Multivent::checkACcmd (int acCmd)
{
if (!acObj) return;
int lastCmd = getIntFromJson(acObj,"@lastCmd");
int prevACcmd = getIntFromJson(acObj,"mode");
if (!prevACcmd) {
debugSerial<<"VENT: AC MODE received initially "<<acCmd<<endl;
setValToJson(acObj,"@lastCmd",acCmd);
return;
}
if (lastCmd && (acCmd != lastCmd)) {
debugSerial<<"VENT: AC MODE changed manually from "<<lastCmd<<" to "<<acCmd<<endl;
setValToJson(acObj,"@lastCmd",acCmd);
setValToJson(acObj,"@lastFan",-1);
return;}
}
void out_Multivent::checkACfan (int acFan)
{
if (!acObj) return;
int prevACfan = getIntFromJson(acObj,"fan");
if (acFan != prevACfan)
{
debugSerial<<"VENT: AC FAN changed, delete lastfan"<<endl;
setValToJson(acObj,"@lastFan",-1);
return;
}
}
void out_Multivent::checkACset (int acSet)
{
if (!acObj) return;
int lastSet = getIntFromJson(acObj,"@lastSet",-1);
//int prevACset = getIntFromJson(acObj,"set");
if (lastSet == -1) {
debugSerial<<"VENT: AC SET received initially "<<acSet<<endl;
setValToJson(acObj,"@lastSet",acSet);
return;
}
if (lastSet && (acSet != lastSet)) {
debugSerial<<"VENT: AC SET changed manually from "<<lastSet<<" to "<<acSet<<endl;
setValToJson(acObj,"@lastSet",acSet);
return;}
}
int out_Multivent::sendACcmd (itemCmd cmd) int out_Multivent::sendACcmd (itemCmd cmd)
{ {
if (!acObj) return 0; if (!acObj) return 0;
int lastCmd = getIntFromJson(acObj,"@lastCmd"); int supress = getIntFromJson(acObj,"supress",0xF);
int acCmd = getIntFromJson(acObj,"mode");
int overrideCmd = getIntFromJson(acObj,"overridecmd"); //int acCmd = getIntFromJson(acObj,"mode");
if (overrideCmd && cmd.getCmd() != CMD_OFF) cmd.Cmd(overrideCmd); //if override cmd exist - use it instead of requested. But allow to turn off by original cmd
//if (lastCmd && (acCmd != lastCmd)) { //if (lastCmd && (acCmd != lastCmd)) {
// //debugSerial<<"VENT: AC MODE changed manually to "<<item->getCmd()<<endl; // //debugSerial<<"VENT: AC MODE changed manually to "<<item->getCmd()<<endl;
@@ -845,28 +900,58 @@ bool out_Multivent::pidEnabled(aJsonObject* pidObj)
if (cmd.isCommand()) if (cmd.isCommand())
{ {
if (cmd.getCmd() == lastCmd ) { int lastCmd = getIntFromJson(acObj,"@lastCmd");
int overrideCmd = getIntFromJson(acObj,"overridecmd");
if (overrideCmd && cmd.getCmd() != CMD_OFF) cmd.Cmd(overrideCmd); //if override cmd exist - use it instead of requested. But allow to turn off by original cmd
if (cmd.getCmd() == lastCmd && (supress & AC_SUPPRESS_CMD))
{
//debugSerial<<"VENT: AC MODE already "<<lastCmd<<endl; //debugSerial<<"VENT: AC MODE already "<<lastCmd<<endl;
} }
else else
{ {
executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0)); executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0));
setValToJson(acObj,"@lastCmd",cmd.getCmd()); setValToJson(acObj,"@lastCmd",cmd.getCmd());
setValToJson(acObj,"@lastFan",-1); //reset last fan to avoid suppression after mode change
} }
} }
if (cmd.isValue()) if (cmd.isValue())
{ {
executeCommand(acObj,-1,cmd.Cmd(0)); int lastFan = getIntFromJson(acObj,"@lastFan",-1);
int lastSet = getFloatFromJson(acObj,"@lastSet");
int prevACcmd = getIntFromJson(acObj,"mode",-1);
bool isActiveNow = (prevACcmd != CMD_OFF && prevACcmd != -1) || (lastFan>0);
switch (cmd.getSuffix()) switch (cmd.getSuffix())
{ {
case S_FAN: case S_FAN:
setValToJson(acObj,"@lastFan",cmd.getCmd()); if (!(supress & AC_SUPPRESS_FAN) || lastFan != cmd.getInt()) {
executeCommand(acObj,-1,cmd.Cmd(0));
}
setValToJson(acObj,"@lastFan",cmd.getInt());
if (cmd.getInt() == 0 && isActiveNow)
{
setValToJson(acObj,"@lastCmd",CMD_OFF);
setValToJson(acObj,"@preCmd",prevACcmd);
}
else if (cmd.getInt() > 0 && !isActiveNow)
{
int preCmd = getIntFromJson(acObj,"@preCmd",CMD_FAN);
setValToJson(acObj,"@lastCmd",preCmd);
}
break; break;
case S_SET: case S_SET:
setValToJson(acObj,"@lastSet",cmd.getCmd()); if (!(supress & AC_SUPPRESS_SET) || lastSet != cmd.getFloat()) {
executeCommand(acObj,-1,cmd.Cmd(0));
} }
setValToJson(acObj,"@lastSet",cmd.getFloat());
break;
default:
executeCommand(acObj,-1,cmd.Cmd(0));
break;
}
} }
return 1; return 1;
} }
@@ -875,22 +960,22 @@ void out_Multivent::setBoost(itemCmd cmd)
{ {
if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return; if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return;
debugSerial<<"VENT: boost on"<<endl; debugSerial<<"VENT: boost on"<<endl;
int acTemp = getIntFromJson(acObj,"set",21); int acSetTemp = getIntFromJson(acObj,"set",AC_PRESET_TEMP);
setValToJson(acObj,"@preset",acTemp); setValToJson(acObj,"@preset",acSetTemp);
//executeCommand(acObj,-1,cmd.setArgType(0).setSuffix(S_CMD));
sendACcmd(cmd); sendACcmd(cmd);
//executeCommand(acObj,-1,cmd.Cmd(0).setSuffix(S_SET));
setFlag(acObj,FLAG_ACTION_NEEDED); setFlag(acObj,FLAG_ACTION_NEEDED);
} }
void out_Multivent::resetBoost() void out_Multivent::resetBoost()
{ {
if (!acObj || !getFlag(acObj,FLAG_ACTION_NEEDED)) return; if (!acObj) return;
int acSetTemp = getIntFromJson(acObj,"set");
if (acSetTemp != AC_BOOST_HIGH_TEMP && acSetTemp != AC_BOOST_LOW_TEMP && !getFlag(acObj,FLAG_ACTION_NEEDED)) return;
debugSerial<<"VENT: boost off"<<endl; debugSerial<<"VENT: boost off"<<endl;
int preTemp = getIntFromJson(acObj,"@preset",21); int preTemp = getIntFromJson(acObj,"@preset",AC_PRESET_TEMP);
if (preTemp<17) preTemp = 21; if (preTemp<=AC_BOOST_LOW_TEMP) preTemp = AC_PRESET_TEMP;
if (preTemp>=AC_BOOST_HIGH_TEMP) preTemp = AC_PRESET_TEMP;
sendACcmd(itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET)); sendACcmd(itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
//executeCommand(acObj,-1,itemCmd().Cmd(0).Int(preTemp).setSuffix(S_SET));
clearFlag(acObj,FLAG_ACTION_NEEDED); clearFlag(acObj,FLAG_ACTION_NEEDED);
} }

View File

@@ -6,8 +6,16 @@
#include "itemCmd.h" #include "itemCmd.h"
#include <PID_v1.h> #include <PID_v1.h>
#define AC_BOOST_HIGH_TEMP 30
#define AC_PRESET_TEMP 21
#define AC_BOOST_LOW_TEMP 17
#define AC_BOOST_TRESHOLD 230
#define AC_BOOST_DEADBAND 10
#define AC_SUPPRESS_FAN 1
#define AC_SUPPRESS_SET 2
#define AC_SUPPRESS_CMD 4
//static int8_t motorQuote = 0;
class out_Multivent : public abstractOut { class out_Multivent : public abstractOut {
public: public:
@@ -26,6 +34,9 @@ public:
protected: protected:
void getConfig(); void getConfig();
int sendACcmd (itemCmd cmd); int sendACcmd (itemCmd cmd);
void checkACcmd (int acCmd);
void checkACfan (int acFan);
void checkACset (int acSet);
void setPassiveMode(aJsonObject* zone, bool mode); void setPassiveMode(aJsonObject* zone, bool mode);
uint32_t getFlag (aJsonObject* zone, uint32_t flag); uint32_t getFlag (aJsonObject* zone, uint32_t flag);
void setFlag (aJsonObject* zone, uint32_t flag); void setFlag (aJsonObject* zone, uint32_t flag);