Files
lighthub/lighthub/modules/out_multivent.cpp

1133 lines
47 KiB
C++

#ifndef MULTIVENT_DISABLE
#include "modules/out_multivent.h"
#include "Arduino.h"
#include "options.h"
#include "Streaming.h"
#include "item.h"
#include "main.h"
#include "utils.h"
void convert2float(aJsonObject * o)
{
if (!o) return;
switch (o->type)
{
case aJson_Int:
o->valuefloat = o->valueint;
o->type = aJson_Float;
break;
}
}
void out_Multivent::getConfig()
{
gatesObj = NULL;
acObj = NULL;
if (!item || !item->itemArg || item->itemArg->type != aJson_Object) return;
gatesObj = item->itemArg;
if (gatesObj) acObj = aJson.getObjectItem(gatesObj, "");
}
int out_Multivent::Setup()
{
abstractOut::Setup();
//getConfig();
//Allocate objects to store persistent data in config tree
if (gatesObj)
{
aJsonObject * i = gatesObj->child;
while (i)
{
if (i->name && *i->name)
{
getCreateObject(i,"fan",-1L);
getCreateObject(i,"cmd",(long) CMD_OFF);
getCreateObject(i,"out",-1L);
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
if (pidObj && pidObj->type == aJson_Array && aJson.getArraySize(pidObj)>=3)
{
aJsonObject * setObj = getCreateObject(i,"set",(float) 20.0);
convert2float(setObj);
aJsonObject * valObj = getCreateObject(i,"val",(float) NAN);
convert2float(valObj);
aJsonObject * poObj = getCreateObject(i,"po", (float) -2.0);
convert2float(poObj);
int direction = DIRECT;
float kP=getFloatFromJson(pidObj,"kP",0,1.0);
if (kP<0)
{
kP=-kP;
direction=REVERSE;
}
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, 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);
debugSerial << F ("VENT: PID P=")<<kP<<" I="<<kI<<" D="<<kD<< endl;
}
}
i=i->next;
}
debugSerial << F ("VENT: init")<< endl;
item->setExt(0);
setStatus(CST_INITIALIZED);
return 1;
}
debugSerial << F ("VENT: config failed")<< endl;
return 0;
}
int out_Multivent::Stop()
{
debugSerial << F ("VENT: De-Init") << endl;
if (gatesObj)
{
aJsonObject * i = gatesObj->child;
while (i)
{
if (i->name && *i->name)
{
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
if (pidObj && pidObj->valueint)
{
delete ((PID *) pidObj->valueint);
pidObj->valueint = 0;//NULL;
}
}
i=i->next;
}
}
setStatus(CST_UNKNOWN);
return 1;
}
int out_Multivent::isActive()
{
//debugSerial<<"VENT:active: ";
if (gatesObj)
{
/*
// metrics, collected from AC
aJsonObject * a = aJson.getObjectItem(gatesObj, "");
if (!a) return 0;
float acTemp = getFloatFromJson(a,"val",NAN);
int actualCmd = getIntFromJson (a,"mode");
int actualMode = CMD_FAN;
if (acTemp>30.0) actualMode = CMD_HEAT;
else if (acTemp<15.0) actualMode = CMD_COOL;
*/
aJsonObject * i = gatesObj->child;
while (i)
{
if (i->name && *i->name)
{
int cmd = getIntFromJson(i,"cmd");
switch (cmd)
{
case CMD_ON:
case CMD_HEATCOOL:
case CMD_FAN:
case CMD_AUTO:
case CMD_COOL:
case CMD_HEAT:
case CMD_DRY:
//case CMD_OFF:
return 1;
break;
}
}
i=i->next;
}//while
} // if gatesObj
return 0;
}
void out_Multivent::stopAllzones(){
if (!gatesObj) return;
debugSerial << F("VENT: Stop all zones. ")<<endl;
aJsonObject * i = gatesObj->child;
while (i)
{
if (i->name && *i->name)
{
int cmd = getIntFromJson(i,"cmd");
switch (cmd)
{
case CMD_ON:
case CMD_HEATCOOL:
case CMD_FAN:
case CMD_AUTO:
case CMD_COOL:
case CMD_HEAT:
case CMD_DRY:
//case CMD_OFF:
setValToJson(i,"@preHaltcmd",cmd);
// setPassiveMode(i, true);
fanCtrl(itemCmd().Cmd(CMD_OFF).setSuffix(S_CMD),i->name,true);
break;
}
}
i=i->next;
}//while
}
void out_Multivent::restoreAllzones(){
if (!gatesObj) return;
debugSerial << F("VENT: Restore all zones. ")<<endl;
aJsonObject * i = gatesObj->child;
while (i)
{
if (i->name && *i->name)
{
int preHaltcmd = getIntFromJson(i,"@preHaltcmd",CMD_OFF);
if (preHaltcmd && (preHaltcmd != CMD_OFF)) //setPassiveMode(i, false);
fanCtrl(itemCmd().Cmd(preHaltcmd).setSuffix(S_CMD),i->name,true);
setValToJson(i,"@preHaltcmd",0); //reset preHaltcmd in any case
}
i=i->next;
}//while
};
#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;
if (cause == POLLING_SLOW && item->getExt() && isTimeOver(item->getExt(),millisNZ(),60000L))
{
item->setExt(0);
aJsonObject * a = aJson.getObjectItem(acObj,"val");
if (a) a->type = aJson_NULL; //invalidate in 60 sec after measure
}
// metrics, collected from AC
float acTemp = getFloatFromJson(acObj,"val",NAN);
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;
bool ventRequested = false; //At least 1 ch requested FAN mode
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");
float set = getFloatFromJson(i,"set");
float val = getFloatFromJson(i,"val");
int fan = getIntFromJson(i,"fan");
int execCmd = 0;
bool weakMode=false; // kind of modes when we activating PID only if AC in active mode
switch (cmd)
{
case CMD_HEATCOOL:
{
switch (acCmd) //TODO - release heat/cool then PID released it, not just set <> val
{
case CMD_HEAT:
if (set>val) execCmd = CMD_HEAT;
if (set<val-AC_HEATCOOL_DEADBAND) execCmd = CMD_COOL;
break;
case CMD_COOL:
if (set<val) execCmd = CMD_COOL;
if (set>val+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT;
break;
default:
if (set>val+AC_HEATCOOL_DEADBAND) execCmd = CMD_HEAT;
if (set<val-AC_HEATCOOL_DEADBAND) execCmd = CMD_COOL;
}
}
break;
case CMD_FAN:
if (fan>0) ventRequested = true;
weakMode = true;
execCmd = cmd;
break;
case CMD_AUTO:
if (fan>0) autoRequested = true;
weakMode = true;
execCmd = cmd;
break;
case CMD_COOL:
case CMD_HEAT:
case CMD_OFF:
execCmd = cmd;
break;
}
bool passiveMode = getIntFromJson(i,"@pasv",0);
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
aJsonObject * poObj = aJson.getObjectItem(i, "po");
aJsonObject * valObj = aJson.getObjectItem(i, "val");
if (pidObj && pidObj->valueint && poObj && poObj->type == aJson_Float && valObj && valObj->type == aJson_Float)
{
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:
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
p->SetControllerDirection(DIRECT);
break;
case CMD_COOL:
if (weakMode || passiveMode) p->SetMode(AUTOMATIC);
p->SetControllerDirection(REVERSE);
break;
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;
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<<F("VENT: ")
<<item->itemArr->name<<"/"<<i->name
<<F(" in:")<<p->GetIn()<<F(" set:")<<p->GetSet()<<F(" out:")<<p->GetOut()
<<" P:"<<p->GetKp()<<" I:"<<p->GetKi()<<" D:"<<p->GetKd()<<((p->GetDirection())?" Rev ":" Dir ")<<((p->GetMode())?"A":"M");
if (p->isOutdated()) debugSerial << F(" <ALM>");
debugSerial<<endl;
if (!isnan(poObj->valuefloat))
switch (execCmd)
{
case CMD_HEAT:
if (actualMode==CMD_COOL) //close
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
else
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 (actualMode==CMD_HEAT) //close
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(0).setSuffix(S_FAN),i->name,true))
else
assign_if_positive(lastACfan, fanCtrl(itemCmd().Percents255(poObj->valuefloat).setSuffix(S_FAN),i->name,true));
balance-=poObj->valuefloat;
pidComputed = true;
break;
default:
switch (actualMode)
{
case CMD_HEAT:
debugSerial<<F("VENT: HEAT PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
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<<F("VENT: COOL PASS PID: ")<<item->itemArr->name<<"/"<<i->name<<F(" out:")<< poObj->valuefloat <<endl;
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))
{
balance+=(p->GetDirection() == DIRECT) ? poObj->valuefloat : -poObj->valuefloat;
//if (execCmd == CMD_HEAT) balance+=poObj->valuefloat;
//else if (execCmd == CMD_COOL) balance-=poObj->valuefloat;
}
}
}
}
i=i->next;
}//while
if (pidComputed) //Active mode. PIDs in pasive mode do not triggered this flag
{
debugSerial<<F("VENT: Chan balance=")<<balance<<F(" treshold:")<<boostTreshold<<endl;
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(AC_BOOST_LOW_TEMP).setSuffix(S_SET));
else
{
if (abs(balance)<AC_BOOST_DEADBAND) pidActive = false; //No request from active PID = no active PID
}
}
bool noFurtherModes = false;
bool activeMode = (actualMode==CMD_HEAT || actualMode==CMD_COOL);
if (!pidActive) // No active mode PID - no boost needed, but maybe vent still requested
{
resetBoost();
if (autoRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_AUTO));
else if (ventRequested && lastACfan>0) sendACcmd(itemCmd().Cmd(CMD_FAN));
else noFurtherModes = true; //No AUTO or FAN mode requested - so we can skip sending command to AC at all and save some energy on it
}
if (lastACfan>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;
};
int out_Multivent::getChanType()
{
return CH_THERMO;
}
void SubmitParameters(aJsonObject * callbackObj, const char * name, itemCmd value, bool doMapping){
if (callbackObj && callbackObj->type == aJson_Object)
{
aJsonObject * execObj = aJson.getObjectItem(callbackObj,name);
if (execObj)
{
aJsonObject * mapObj = NULL;
if (doMapping) mapObj = aJson.getObjectItem(execObj, "map");
executeCommand(execObj,-1,value.doReverseMapping(mapObj));
}
}
}
void out_Multivent::setPassiveMode(aJsonObject* zone, bool mode)
{
bool passiveMode = getIntFromJson(zone,"@pasv",0);
if (passiveMode != mode)
{
aJsonObject * cascadeObj=aJson.getObjectItem(zone, "cas");
//aJsonObject * cmdObj=aJson.getObjectItem(zone, "cmd");
//Set up passive mode
debugSerial<<F("VENT: passive mode: ")<<mode<<endl;
setValToJson(zone,"@pasv",mode);
if (mode)
{
if (isNotRetainingStatus())
item->SendStatusImmediate(itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),FLAG_COMMAND,zone->name); //Send /fan->AUTO
SubmitParameters(cascadeObj,"fan",itemCmd().Cmd(CMD_AUTO).setSuffix(S_FAN),false);
setValToJson(zone,"fan",0); //reset fan level on passive mode ON
}
else
{
aJsonObject * fanObj=aJson.getObjectItem(zone, "fan");
if (!fanObj || fanObj->type!=aJson_Int) return;
if (isNotRetainingStatus())
item->SendStatusImmediate(itemCmd().Percents255(fanObj->valueint).setSuffix(S_FAN),FLAG_PARAMETERS,zone->name); //Send /fan->#
SubmitParameters(cascadeObj,"fan",itemCmd().Percents255(fanObj->valueint).setSuffix(S_FAN),true);
}
}
}
uint32_t out_Multivent::getFlag (aJsonObject* zone, uint32_t flag)
{
if (zone && (zone->type == aJson_Object))
{
return (uint32_t) zone->valueint & flag & FLAG_MASK;
}
return 0;
}
void out_Multivent::setFlag (aJsonObject* zone, uint32_t flag)
{
if (zone && (zone->type == aJson_Object))
{
zone->valueint |= flag & FLAG_MASK;
}
}
void out_Multivent::clearFlag (aJsonObject* zone, uint32_t flag)
{
if (zone && (zone->type == aJson_Object))
{
zone->valueint &= CMD_MASK | ~(flag & FLAG_MASK);
}
}
int out_Multivent::Ctrl(itemCmd cmd, char* subItem , bool toExecute, bool authorized)
{
int result = fanCtrl(cmd,subItem, false);
if (result>=0)
{
debugSerial << F("VENT: CTRL fan level: ")<<result<<endl;
sendACcmd(itemCmd().Percents255(result).setSuffix(S_FAN));
}
else debugSerial << F("VENT: CTRL result: ")<<(result==-1? F("OK") : F("not OK"))<<endl;
// else debugSerial << F("VENT: Skip turning OFF by fan")<<endl;
return (result == -2)? 0 : 1;
}
// Return:
// Main AC fan value (0..255) or
// -1 unknown fan + success
// -2 unknown fan + error (forbidden by PID or frozen by freeze flag)
int out_Multivent::fanCtrl(itemCmd cmd, char* subItem , bool force)
{
if (!gatesObj || !acObj) return -2;
if (cmd.getCmd()==CMD_DISABLE || cmd.getCmd()==CMD_ENABLE) return -2;
int suffixCode = cmd.getSuffix();
debugSerial << "VENT: CTRL " << subItem << " "; cmd.debugOut();
if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it
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
{
switch (suffixCode)
{
case S_VAL:
if (cmd.isValue())
{
debugSerial << F("VENT: ")<<F("AC air temp: ")<< cmd.getFloat()<<endl;
item->setExt(millisNZ()); //setup validity interval
setValToJson(acObj,"val",cmd.getFloat());
}
return -1;
case S_FAN:
if (cmd.isValue())
{
debugSerial << F("VENT: ")<<F("AC fan: ")<< cmd.getInt()<<endl;
checkACfan(cmd.getInt());
setValToJson(acObj,"fan",cmd.getInt());
}
return -1;
case S_SET:
if (cmd.isValue())
{
debugSerial << F("VENT: ")<<F("AC set: ")<< cmd.getFloat()<<endl;
checkACset(cmd.getFloat());
setValToJson(acObj,"set",cmd.getFloat());
if (cmd.getFloat() == AC_BOOST_HIGH_TEMP || cmd.getFloat() == AC_BOOST_LOW_TEMP)
{
setFlag(acObj, FLAG_ACTION_NEEDED);
debugSerial<<F("VENT: boost mode detected")<<endl;
}
}
return -1;
case S_MODE:
if (cmd.isCommand())
{
debugSerial << F("VENT: ")<<F("AC mode: ")<< cmd.getCmd()<<endl;
checkACcmd(cmd.getCmd());
setValToJson(acObj,"mode",cmd.getCmd());
}
return -1;
case S_CMD:
switch (cmd.getCmd())
{
case CMD_OFF:
stopAllzones();
break;
case CMD_ON:
case CMD_HEAT:
case CMD_COOL:
case CMD_HEATCOOL:
case CMD_DRY:
case CMD_FAN:
case CMD_AUTO:
restoreAllzones();
}
return -1;
//case S_TEMP: - temp corrupted by core
// debugSerial << F("VENT:")<<F("AC air roomtemp: ")<< cmd.getFloat()<<endl;
// setValToJson(acObj,"roomtemp",cmd.getFloat());
//return -1;
}
}
aJsonObject * i = NULL;
if (gatesObj) i = gatesObj->child; // Pass 1 - calculate summ air value, max value etc
int activeV = 0;
int totalV = 0;
int maxV=0;
int maxRequestedV=0;
int maxPercent=0;
bool exitAfterSendingStatus = false;
while (i)
{
aJsonObject * fanObj=aJson.getObjectItem(i, "fan");
aJsonObject * cmdObj=aJson.getObjectItem(i, "cmd");
aJsonObject * cascadeObj=aJson.getObjectItem(i, "cas");
//aJsonObject * setObj=aJson.getObjectItem(i, "set");
aJsonObject * pidObj=aJson.getObjectItem(i, "pid");
if (fanObj && cmdObj && fanObj->type==aJson_Int && cmdObj->type==aJson_Int)
{
int V = getIntFromJson(i,"V",60);
bool passiveMode = getIntFromJson(i,"@pasv",0);
int requestedV = 0;
if (subItem && !strcmp (i->name,subItem))
{
long sendFlags = 0;
switch (suffixCode)
{
case S_FAN:
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
if (cmd.isValue())
{
if (!force)
{
setValToJson(i,"fan_in",cmd.getInt()); //fan controlled by external source, save input value to restore after exit from passive mode
debugSerial<<"VENT: external fan level stored: "<<cmd.getInt()<<endl;
if (pidEnabled(pidObj))
{
debugSerial<<F("VENT: FAN control disabled by PID")<<endl;
return -2;
}
}
if (cmd.getInt())
{
if (cmdObj->valueint == CMD_OFF && turnbyfan)
{
cmd.Cmd(turnbyfan);
debugSerial<<"VENT: generating cmd by fan: "<<turnbyfan<<endl;
}
}
else
{
if (cmdObj->valueint != CMD_OFF && turnbyfan)
{
cmd.Cmd(CMD_OFF);
debugSerial<<"VENT: Turning OFF by fan"<<endl;
}
}
fanObj->valueint = cmd.getInt(); //
if (!passiveMode) sendFlags |= FLAG_PARAMETERS; //
//if (isNotRetainingStatus()) item->SendStatusImmediate(cmd,FLAG_PARAMETERS|FLAG_COMMAND,i->name);
}
else if (cmd.getCmd() == CMD_AUTO)
{
setPassiveMode(i,true); //Setup flag
passiveMode = true;
cmd.Cmd(CMD_OFF);
cmd.setSuffix(S_CMD);
}
else if (cmd.getCmd() == CMD_OFF)
{
setPassiveMode(i,false);
passiveMode = false;
}
if (!cmd.isCommand()) break; // if have command in FAN suffix - continue processing
debugSerial<<"VENT: cmd in FAN suffix, process as command. cmd="<<cmd.getCmd()<<endl;
case S_CMD:
if (cmd.isCommand())
{
bool checkMinFanLevel = false;
if (cmd.getCmd() == CMD_ON)
{
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
if (cmdObj->valueint != CMD_OFF && cmdObj->valueint != CMD_HALT) break;
cmd.Percents255(fanObj->valueint);
cmd.setSuffix(S_FAN);
sendFlags |= FLAG_COMMAND | FLAG_PARAMETERS;
//cmdObj->valueint = cmd.getCmd();
cmdObj->valueint = getIntFromJson(i,"@precmd",CMD_FAN);
debugSerial<<"VENT: Turning ON. cmd:"<<cmdObj->valueint<<endl;
cmd.Cmd(cmdObj->valueint);
setPassiveMode(i,false);
}
switch (cmd.getCmd())
{
case CMD_ON:
checkMinFanLevel = true;
break;
case CMD_OFF:
if (getFlag(i,FLAG_FREEZED)) {debugSerial<<F("VENT: zone frozen")<<endl; return -2;}
if (cmdObj->valueint != CMD_OFF) setValToJson(i,"@precmd",cmdObj->valueint); //saving previous mode
cmd.Percents255(0);
cmd.setSuffix(S_FAN);
debugSerial<<"VENT: Turning OFF. saving cmd:"<<cmdObj->valueint<<endl;
sendFlags |= FLAG_COMMAND;
//sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
//
if (!passiveMode) sendFlags |= FLAG_PARAMETERS;
// else cmd.Cmd(CMD_AUTO);
cmdObj->valueint = CMD_OFF;
enablePid(pidObj,false);
break;
/*
case CMD_ENABLE:
if (pidObj && pidObj->valueint) ((PID *) pidObj->valueint)->SetMode(AUTOMATIC);
sendFlags |= FLAG_FLAGS;
setPassiveMode(i,false);
break;
case CMD_DISABLE:
if (pidObj && pidObj->valueint) ((PID *) pidObj->valueint)->SetMode(MANUAL);
sendFlags |= FLAG_FLAGS;
setPassiveMode(i,false);
break;
*/
case CMD_FREEZE:
setFlag(i,FLAG_FREEZED);
return -1;
case CMD_UNFREEZE:
clearFlag(i,FLAG_FREEZED);
return -1;
case CMD_COOL:
case CMD_DRY:
enablePid(pidObj,true,REVERSE);
sendFlags |= FLAG_COMMAND;
cmdObj->valueint = cmd.getCmd();
setPassiveMode(i,false);
checkMinFanLevel = true;
break;
case CMD_HEAT:
enablePid(pidObj,true,DIRECT);
case CMD_HEATCOOL:
enablePid(pidObj,true);
sendFlags |= FLAG_COMMAND;
cmdObj->valueint = cmd.getCmd();
setPassiveMode(i,false);
checkMinFanLevel = true;
break;
case CMD_FAN:
sendFlags |= FLAG_PARAMETERS; //experimental 30/03/26
cmd.Percents255(fanObj->valueint);
cmd.setSuffix(S_FAN);
// continue
case CMD_AUTO:
enablePid(pidObj,false);
sendFlags |= FLAG_COMMAND;
cmdObj->valueint = cmd.getCmd();
setPassiveMode(i,false);
checkMinFanLevel = true;
break;
//todo - halt-rest-xon-xoff
}
if (isNotRetainingStatus() && checkMinFanLevel && (fanObj->valueint<20))
{
fanObj->valueint=30;
cmd.Percents255(30);
sendFlags |= FLAG_PARAMETERS;
}
}
break;
case S_SET:
if (cmd.isValue())
{
setValToJson(i,"set",cmd.getFloat());
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())
{
aJsonObject * pidObj = aJson.getObjectItem(i, "pid");
if (pidObj && pidObj->valueint)
{
((PID *) pidObj->valueint)->SetVal(cmd.getFloat());
debugSerial<<F("VENT: PID input: ")<<i->name<<F(" updated by temp: ")<< cmd.getFloat()<<endl;
}
}
return -1;
break;
default:
break;
} //switch
if (isNotRetainingStatus()) //Send status separately for cmd, param, flags
{
if (sendFlags & FLAG_COMMAND) item->SendStatusImmediate(itemCmd().Cmd(cmd).setSuffix(S_CMD),FLAG_COMMAND,i->name);
if (sendFlags & FLAG_PARAMETERS ) item->SendStatusImmediate(cmd,FLAG_PARAMETERS,i->name);
if (sendFlags & FLAG_FLAGS) item->SendStatusImmediate(cmd,FLAG_FLAGS,i->name);
}
if (cascadeObj)
{
if (sendFlags & FLAG_COMMAND) SubmitParameters(cascadeObj,"cmd",itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0),true);
if (sendFlags & FLAG_PARAMETERS)
switch (cmd.getSuffix())
{
case S_SET:
SubmitParameters(cascadeObj,"set",cmd,true);
break;
case S_FAN:
SubmitParameters(cascadeObj,"fan",cmd,true);
break;
}
}
} // subitem
if (exitAfterSendingStatus) return -1; //if setpoint updated - send status immediately to update PID and sending status
if ((cmdObj->valueint != CMD_OFF && cmdObj->valueint != -1) || passiveMode)
{
requestedV=V*fanObj->valueint;
activeV+=requestedV;
if (fanObj->valueint>maxPercent )
{
maxRequestedV=requestedV;
maxV=V;
maxPercent=fanObj->valueint;
}
}
totalV+=V;
}
i=i->next;
}
if (!totalV) return 0;
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;
//if (fanV || allowToTurnOffByFan) sendACcmd(itemCmd().Percents255(fanV).setSuffix(S_FAN));
// else debugSerial << F("VENT: Skip turning OFF by fan")<<endl;
//Move gates only if fan is actually on
if (!fanV) return 0;
i=NULL;
if (gatesObj) i = gatesObj->child; //Pass 2: re-distribute airflow
while (i)
{
int V = getIntFromJson(i,"V",60);
aJsonObject * outObj=aJson.getObjectItem(i, "out");
aJsonObject * fanObj=aJson.getObjectItem(i, "fan");
aJsonObject * cmdObj=aJson.getObjectItem(i, "cmd");
bool passiveMode = getIntFromJson(i,"@pasv",0);
if (outObj && fanObj && cmdObj && outObj->type==aJson_Int && fanObj->type==aJson_Int && cmdObj->type==aJson_Int && V)
{
long int out = 0;
if (((cmdObj->valueint != CMD_OFF && cmdObj->valueint != -1) || passiveMode) && maxRequestedV)
{
int requestedV=V*fanObj->valueint;
out = (( long)requestedV*255L)/(( long)V)*( long)maxV/( long)maxRequestedV;
debugSerial<<F("VENT: ")<<i->name<<F(" Req:")<<requestedV/255<<F(" Out:")<<out<<endl;
}
if ((out != outObj->valueint))
{
//report out
executeCommand(i,-1,itemCmd().Percents255(out));
outObj->valueint=out;
}
}
i=i->next;
}
return fanV;
}
void out_Multivent::enablePid(aJsonObject* pidObj, int enable, int direction )
{
if (pidObj && pidObj->valueint)
{
((PID *) pidObj->valueint)->SetMode(enable);
if (direction != -1) ((PID *) pidObj->valueint)->SetControllerDirection(direction);
}
}
bool out_Multivent::pidEnabled(aJsonObject* pidObj)
{
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");
int lastFan = getIntFromJson(acObj,"@lastFan",-1);
if (!prevACcmd) {
debugSerial<<"VENT: AC MODE received initially "<<acCmd<<endl;
setValToJson(acObj,"@lastCmd",acCmd);
return;
}
if (lastCmd && (acCmd != lastCmd) ) {
if (!(acCmd == CMD_OFF && lastFan == 0))
{
debugSerial<<"VENT: AC MODE changed manually from "<<lastCmd<<" to "<<acCmd<<endl;
switch (acCmd)
{
case CMD_OFF:
stopAllzones();
break;
default:
restoreAllzones();
break;
}
aJsonObject * execObj = aJson.getObjectItem(acObj, "onextcmd");
if (execObj)
{
executeCommand(execObj,-1,itemCmd().Cmd(acCmd).setSuffix(S_CMD).setArgType(0).doReverseMapping(aJson.getObjectItem(execObj, "map")));
}
}
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;
aJsonObject * execObj = aJson.getObjectItem(acObj, "onextcmd");
if (execObj)
{
executeCommand(execObj,-1,itemCmd().Int(acSet).setSuffix(S_SET).doReverseMapping(aJson.getObjectItem(execObj, "map")));
}
setValToJson(acObj,"@lastSet",acSet);
return;}
}
int out_Multivent::sendACcmd (itemCmd cmd)
{
if (!acObj) return 0;
int supress = getIntFromJson(acObj,"supress",0xF);
//int acCmd = getIntFromJson(acObj,"mode");
//if (lastCmd && (acCmd != lastCmd)) {
// //debugSerial<<"VENT: AC MODE changed manually to "<<item->getCmd()<<endl;
// return 0;}
if (cmd.isCommand())
{
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;
}
else
{
debugSerial<<"VENT: sendACcmd. cmd="; cmd.debugOut(); debugSerial<<endl;
executeCommand(acObj,-1,itemCmd().Cmd(cmd).setSuffix(S_CMD).setArgType(0));
setValToJson(acObj,"@lastCmd",cmd.getCmd());
setValToJson(acObj,"@lastFan",-1); //reset last fan to avoid suppression after mode change
}
}
if (cmd.isValue())
{
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())
{
case S_FAN:
if (!(supress & AC_SUPPRESS_FAN) || lastFan != cmd.getInt()) {
debugSerial<<"VENT: sendACcmd. fan="; cmd.debugOut(); debugSerial<<endl;
executeCommand(acObj,-1,cmd.Cmd(0));
}
setValToJson(acObj,"@lastFan",cmd.getInt());
if (cmd.getInt() == 0)
{
/////setValToJson(acObj,"@lastCmd",CMD_OFF);
if (isActiveNow) setValToJson(acObj,"@preCmd",prevACcmd);
}
else if (cmd.getInt() > 0 && !isActiveNow)
{
int preCmd = getIntFromJson(acObj,"@preCmd",CMD_FAN);
setValToJson(acObj,"@lastCmd",preCmd);
}
break;
case S_SET:
if (!(supress & AC_SUPPRESS_SET) || lastSet != cmd.getFloat()) {
debugSerial<<"VENT: sendACcmd. set="; cmd.debugOut(); debugSerial<<endl;
executeCommand(acObj,-1,cmd.Cmd(0));
}
setValToJson(acObj,"@lastSet",cmd.getFloat());
break;
default:
executeCommand(acObj,-1,cmd.Cmd(0));
break;
}
}
return 1;
}
void out_Multivent::setBoost(itemCmd cmd)
{
if (!acObj || getFlag(acObj,FLAG_ACTION_NEEDED)) return;
debugSerial<<"VENT: boost on"<<endl;
int acSetTemp = getIntFromJson(acObj,"set",AC_PRESET_TEMP);
setValToJson(acObj,"@preset",acSetTemp);
sendACcmd(cmd);
setFlag(acObj,FLAG_ACTION_NEEDED);
}
void out_Multivent::resetBoost()
{
if (!acObj) return;
//int acSetTemp = getIntFromJson(acObj,"set");
if (!getFlag(acObj,FLAG_ACTION_NEEDED)) return;
debugSerial<<"VENT: boost off"<<endl;
int preTemp = getIntFromJson(acObj,"@preset",AC_PRESET_TEMP);
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));
clearFlag(acObj,FLAG_ACTION_NEEDED);
}
void out_Multivent::notifyState(itemCmd state)
{
char val[16];
state.toString(val,sizeof(val),FLAG_COMMAND);
publishTopic(item->itemArr->name,val,"/$state");
}
#endif