PID+PWM relay thermostat, init from flash fixed

This commit is contained in:
2021-12-29 02:06:52 +03:00
parent c67bab2173
commit c03821e94a
9 changed files with 397 additions and 35 deletions

View File

@@ -19,6 +19,6 @@ int abstractOut::isActive()
int abstractOut::Setup() int abstractOut::Setup()
{ {
if (item) item->setCmd(CMD_OFF); if (item && (item->getCmd()==-1)) item->setCmd(CMD_OFF);
return 1; return 1;
} }

View File

@@ -50,6 +50,7 @@ e-mail anklimov@gmail.com
#include "modules/out_pid.h" #include "modules/out_pid.h"
#include "modules/out_multivent.h" #include "modules/out_multivent.h"
#include "modules/out_uartbridge.h" #include "modules/out_uartbridge.h"
#include "modules/out_relay.h"
#ifdef ELEVATOR_ENABLE #ifdef ELEVATOR_ENABLE
#include "modules/out_elevator.h" #include "modules/out_elevator.h"
@@ -147,48 +148,48 @@ void Item::Parse() {
case CH_DIMMER: case CH_DIMMER:
case CH_RGBWW: case CH_RGBWW:
driver = new out_dmx (this); driver = new out_dmx (this);
// debugSerial<<F("DMX driver created")<<endl;
break; break;
#endif #endif
#ifndef SPILED_DISABLE #ifndef SPILED_DISABLE
case CH_SPILED: case CH_SPILED:
driver = new out_SPILed (this); driver = new out_SPILed (this);
// debugSerial<<F("SPILED driver created")<<endl;
break; break;
#endif #endif
#ifndef AC_DISABLE #ifndef AC_DISABLE
case CH_AC: case CH_AC:
driver = new out_AC (this); driver = new out_AC (this);
// debugSerial<<F("AC driver created")<<endl;
break; break;
#endif #endif
#ifndef MOTOR_DISABLE #ifndef MOTOR_DISABLE
case CH_MOTOR: case CH_MOTOR:
driver = new out_Motor (this); driver = new out_Motor (this);
// debugSerial<<F("AC driver created")<<endl;
break; break;
#endif #endif
#ifndef MBUS_DISABLE #ifndef MBUS_DISABLE
case CH_MBUS: case CH_MBUS:
driver = new out_Modbus (this); driver = new out_Modbus (this);
// debugSerial<<F("AC driver created")<<endl;
break; break;
#endif #endif
#ifndef PID_DISABLE #ifndef PID_DISABLE
case CH_PID: case CH_PID:
driver = new out_pid (this); driver = new out_pid (this);
// debugSerial<<F("AC driver created")<<endl; break;
#endif
#ifndef RELAY_DISABLE
case CH_RELAYX:
driver = new out_relay (this);
break; break;
#endif #endif
#ifndef MULTIVENT_DISABLE #ifndef MULTIVENT_DISABLE
case CH_MULTIVENT: case CH_MULTIVENT:
driver = new out_Multivent (this); driver = new out_Multivent (this);
// debugSerial<<F("AC driver created")<<endl;
break; break;
#endif #endif
@@ -217,7 +218,11 @@ boolean Item::Setup()
if (driver) if (driver)
{ {
if (driver->Status()) driver->Stop(); if (driver->Status()) driver->Stop();
driver->Setup(); if (driver->Setup())
{
if (getCmd()) setFlag(SEND_COMMAND);
if (itemVal) setFlag(SEND_PARAMETERS);
}
return true; return true;
} }
else return false; else return false;
@@ -237,7 +242,6 @@ Item::~Item()
if (driver) if (driver)
{ {
delete driver; delete driver;
// debugSerial<<F("Driver destroyed")<<endl;
} }
} }

View File

@@ -55,9 +55,10 @@ e-mail anklimov@gmail.com
#define CH_PID 13 #define CH_PID 13
#define CH_MBUS 14 #define CH_MBUS 14
#define CH_UARTBRIDGE 15 #define CH_UARTBRIDGE 15
#define CH_ELEVATOR 16 #define CH_RELAYX 16
#define CH_RGBWW 17 #define CH_RGBWW 17
#define CH_MULTIVENT 18 #define CH_MULTIVENT 18
#define CH_ELEVATOR 19
//#define CHANNEL_TYPES 13 //#define CHANNEL_TYPES 13

View File

@@ -970,7 +970,7 @@ bool itemCmd::loadItem(Item * item, uint16_t optionsFlag)
cmd.itemArgType= subtype; cmd.itemArgType= subtype;
if (optionsFlag & SEND_PARAMETERS) param.asInt32 = item->getVal(); if (optionsFlag & SEND_PARAMETERS) param.asInt32 = item->getVal();
//debugSerial<<F("Loaded :"); //debugSerial<<F("Loaded :");
debugOut(); //debugOut();
return true; return true;
} }

View File

@@ -1108,7 +1108,7 @@ void Changed(int i, DeviceAddress addr, float currentTemp) {
debugSerial<<endl<<F("T:")<<currentTemp<<F("<")<<addrstr<<F(">")<<endl; debugSerial<<endl<<F("T:")<<currentTemp<<F("<")<<addrstr<<F(">")<<endl;
aJsonObject *owObj = aJson.getObjectItem(owArr, addrstr); aJsonObject *owObj = aJson.getObjectItem(owArr, addrstr);
if ((currentTemp != -127.0) && (currentTemp != 85.0) && (currentTemp != 0.0)) if ((currentTemp != -127.0) && (currentTemp != 85.0) && (currentTemp != 0.0))
executeCommand(owObj,-1,itemCmd(currentTemp)); executeCommand(owObj,-1,itemCmd(currentTemp).setSuffix(S_VAL));
/* /*
if (owObj) { if (owObj) {
@@ -1322,7 +1322,11 @@ setupSyslog();
printConfigSummary(); printConfigSummary();
configLoaded=true; configLoaded=true;
ethClient.stop(); //Refresh MQTT connection if (ethClient.connected())
{
ethClient.stop(); //Refresh MQTT connection
lanStatus=IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
}
configLocked--; configLocked--;
} }

View File

@@ -28,27 +28,37 @@ bool out_pid::getConfig()
errorSerial<<F("Invalid PID param array.")<<endl; errorSerial<<F("Invalid PID param array.")<<endl;
return false; return false;
} }
double outMin=0.; double outMin=0.; //UNUSED
double outMax=255.; double outMax=255.;//UNUSED
unsigned int alarmTO=0;
aJsonObject * param; aJsonObject * param;
switch (aJson.getArraySize(kPIDObj)) switch (aJson.getArraySize(kPIDObj))
{ case 5: { case 7: //kP,kI,kD, alarmTO, alarmVal, outMin, outMax
param = aJson.getArrayItem(kPIDObj, 4); param = aJson.getArrayItem(kPIDObj, 6);
if (param->type == aJson_Float) outMax=param->valuefloat; if (param->type == aJson_Float) outMax=param->valuefloat;
else if (param->type == aJson_Int) outMax=param->valueint; else if (param->type == aJson_Int) outMax=param->valueint;
case 4:
param = aJson.getArrayItem(kPIDObj, 3); case 6: //kP,kI,kD, alarmTO, alarmVal, outMin
param = aJson.getArrayItem(kPIDObj, 5);
if (param->type == aJson_Float) outMin=param->valuefloat; if (param->type == aJson_Float) outMin=param->valuefloat;
else if (param->type == aJson_Int) outMin=param->valueint; else if (param->type == aJson_Int) outMin=param->valueint;
case 3:
case 5: //kP,kI,kD, alarmTO, alarmVal
case 4: //kP,kI,kD, alarmTO
param = aJson.getArrayItem(kPIDObj, 3);
if (param->type == aJson_Int) alarmTO=param->valueint;
case 3: //kP,kI,kD
param = aJson.getArrayItem(kPIDObj, 2); param = aJson.getArrayItem(kPIDObj, 2);
if (param->type == aJson_Float) kD=param->valuefloat; if (param->type == aJson_Float) kD=param->valuefloat;
else if (param->type == aJson_Int) kD=param->valueint; else if (param->type == aJson_Int) kD=param->valueint;
case 2:
case 2: //kP,kI
param = aJson.getArrayItem(kPIDObj, 1); param = aJson.getArrayItem(kPIDObj, 1);
if (param->type == aJson_Float) kI=param->valuefloat; if (param->type == aJson_Float) kI=param->valuefloat;
else if (param->type == aJson_Int) kI=param->valueint; else if (param->type == aJson_Int) kI=param->valueint;
case 1:
case 1: //kP
param = aJson.getArrayItem(kPIDObj, 0); param = aJson.getArrayItem(kPIDObj, 0);
if (param->type == aJson_Float) kP=param->valuefloat; if (param->type == aJson_Float) kP=param->valuefloat;
else if (param->type == aJson_Int) kP=param->valueint; else if (param->type == aJson_Int) kP=param->valueint;
@@ -79,6 +89,9 @@ bool out_pid::getConfig()
store->pid->SetMode(AUTOMATIC); store->pid->SetMode(AUTOMATIC);
//store->pid->SetOutputLimits(outMin,outMax); //store->pid->SetOutputLimits(outMin,outMax);
store->pid->SetSampleTime(5000); store->pid->SetSampleTime(5000);
store->alarmTimer=millis();
store->alarmArmed=false;
store->alarmTimeout=alarmTO; //in sec
return true;} return true;}
else errorSerial<<F("PID already initialized")<<endl; else errorSerial<<F("PID already initialized")<<endl;
@@ -99,7 +112,10 @@ store->pid=NULL;
if (getConfig()) if (getConfig())
{ {
infoSerial<<F("PID config loaded ")<< item->itemArr->name<<endl; infoSerial<<F("PID config loaded ")<< item->itemArr->name<<endl;
item->On(); // Turn ON pid by default //item->On(); // Turn ON pid by default
// if (item->getCmd()) item->setFlag(SEND_COMMAND);
// if (item->itemVal) item->setFlag(SEND_PARAMETERS);
store->prevOut = -2.0;
store->driverStatus = CST_INITIALIZED; store->driverStatus = CST_INITIALIZED;
return 1; return 1;
} }
@@ -138,21 +154,92 @@ int out_pid::Poll(short cause)
{ {
if (store && store->pid && (Status() == CST_INITIALIZED) && item && (item->getCmd()!=CMD_OFF)) if (store && store->pid && (Status() == CST_INITIALIZED) && item && (item->getCmd()!=CMD_OFF))
{ {
double prevOut=store->output; //double prevOut=store->output;
if(store->pid->Compute()) //itemCmd st;
//if (abs(store->output-store-prevOut)>OUTPUT_TRESHOLD) //st.loadItem(item);
//short cmd = st.getCmd();
if (item->getCmd() != CMD_OFF)
{
if(store->pid->Compute() && !store->alarmArmed)
if (abs(store->output-store->prevOut)>OUTPUT_TRESHOLD)
{ {
aJsonObject * oCmd = aJson.getArrayItem(item->itemArg, 1); aJsonObject * oCmd = aJson.getArrayItem(item->itemArg, 1);
itemCmd value((float) (store->output));// * (100./255.))); itemCmd value((float) (store->output));// * (100./255.)));
value.setSuffix(S_SET);
executeCommand(oCmd,-1,value); executeCommand(oCmd,-1,value);
store->prevOut=store->output;
} }
if(!store->alarmArmed && isTimeOver(store->alarmTimer,millis(),store->alarmTimeout*1000) )
{
store->alarmArmed=true;
alarm(true);
}
}
} }
return 1;//store->pollingInterval; return 1;//store->pollingInterval;
}; };
void out_pid::alarm(bool state)
{
if (!item || item->itemArg) return;
if (state)
{
aJsonObject * kPIDObj = aJson.getArrayItem(item->itemArg, 0);
if (kPIDObj->type != aJson_Array)
{
errorSerial<<F("Invalid PID param array.")<<endl;
return;
}
float outAlarm=0.;
double kP=0.;
bool alarmValDefined = false;
aJsonObject * param;
switch (aJson.getArraySize(kPIDObj))
{
case 7: //kP,kI,kD, alarmTO, alarmVal, outMin, outMax
case 6: //kP,kI,kD, alarmTO, alarmVal, outMin
case 5: //kP,kI,kD, alarmTO, alarmVal
param = aJson.getArrayItem(kPIDObj, 4);
alarmValDefined=true;
if (param->type == aJson_Float) outAlarm=param->valuefloat;
else if (param->type == aJson_Int) outAlarm=param->valueint;
else alarmValDefined=false;
case 4: //kP,kI,kD, alarmTO
case 3: //kP,kI,kD
case 2: //kP,kI
case 1: //kP
param = aJson.getArrayItem(kPIDObj, 0);
if (param->type == aJson_Float) kP=param->valuefloat;
else if (param->type == aJson_Int) kP=param->valueint;
{
if (kP<0)
{
if (!alarmValDefined) outAlarm = 0.;
}
else if (!alarmValDefined) outAlarm = .255;
}
}
errorSerial<<item->itemArr->name<<F(" PID alarm. Set out to ")<<outAlarm<<endl;
aJsonObject * oCmd = aJson.getArrayItem(item->itemArg, 1);
itemCmd value (outAlarm);// * (100./255.)));
executeCommand(oCmd,-1,value);
}
else
{
infoSerial<<item->itemArr->name<<F(" PID alarm: closed")<<endl;
}
}
int out_pid::getChanType() int out_pid::getChanType()
{ {
return CH_THERMO; return CH_THERMO;
@@ -180,6 +267,13 @@ case S_VAL:
if (!cmd.isValue()) return 0; if (!cmd.isValue()) return 0;
store->input=cmd.getFloat(); store->input=cmd.getFloat();
debugSerial<<F("Input value:")<<store->input<<endl; debugSerial<<F("Input value:")<<store->input<<endl;
store->alarmTimer=millis();
if (store->alarmArmed)
{
alarm(false);
store->alarmArmed=false;
}
return 1; return 1;
//break; //break;
@@ -196,17 +290,40 @@ return 1;
//break; //break;
case S_CMD: case S_CMD:
switch (cmd.getCmd()) {
aJsonObject * oCmd = aJson.getArrayItem(item->itemArg, 1);
short command = cmd.getCmd();
itemCmd value(ST_VOID,command);
value.setSuffix(S_CMD);
switch (command)
{ {
case CMD_ON:
case CMD_OFF: case CMD_OFF:
//item->setCmd(cmd.getCmd()); //value.Percents255(0);
//item->SendStatus(SEND_COMMAND);
case CMD_ON:
case CMD_HEAT:
case CMD_COOL:
case CMD_AUTO:
case CMD_FAN:
case CMD_DRY:
executeCommand(oCmd,-1,value);
return 1;
/*
case CMD_OFF:
{
aJsonObject * oCmd = aJson.getArrayItem(item->itemArg, 1);
itemCmd value((float) 0.);// * (100./255.)));
value.setSuffix(S_SET);
executeCommand(oCmd,-1,value);
return 1; return 1;
} */
default: default:
debugSerial<<F("Unknown cmd ")<<cmd.getCmd()<<endl; debugSerial<<F("Unknown cmd ")<<cmd.getCmd()<<endl;
} //switch cmd } //switch cmd
}
default: default:
debugSerial<<F("Unknown suffix ")<<suffixCode<<endl; debugSerial<<F("Unknown suffix ")<<suffixCode<<endl;
} //switch suffix } //switch suffix

View File

@@ -6,7 +6,7 @@
#include <PID_v1.h> #include <PID_v1.h>
#include "itemCmd.h" #include "itemCmd.h"
#define OUTPUT_TRESHOLD 1 #define OUTPUT_TRESHOLD 1.0
class pidPersistent : public chPersistent { class pidPersistent : public chPersistent {
public: public:
@@ -16,6 +16,9 @@ public:
double setpoint; double setpoint;
float prevOut; float prevOut;
int driverStatus; int driverStatus;
uint32_t alarmTimer;
bool alarmArmed;
uint16_t alarmTimeout; //in sec
}; };
@@ -32,6 +35,7 @@ public:
int getChanType() override; int getChanType() override;
int getDefaultStorageType(){return ST_FLOAT;}; int getDefaultStorageType(){return ST_FLOAT;};
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true) override; int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true) override;
void alarm(bool);
protected: protected:

View File

@@ -0,0 +1,204 @@
#ifndef RELAY_DISABLE
#include "modules/out_relay.h"
#include "Arduino.h"
#include "options.h"
#include "Streaming.h"
#include "item.h"
#include "main.h"
#include "dmx.h"
static int driverStatus = CST_UNKNOWN;
void out_relay::getConfig()
{
inverted=false;
pin=item->getArg(0);
if (pin<0)
{
pin=-pin;
inverted=true;
}
if(pin==0 || pin>=PINS_COUNT) pin=32;
period = item->getArg(1);
}
#define ACTIVE (inverted)?LOW:HIGH
#define INACTIVE (inverted)?HIGH:LOW
int out_relay::Setup()
{
abstractOut::Setup();
debugSerial<<F("Relay-Out #")<<pin<<F(" init")<<endl;
if (!item ) return 0;
pinMode(pin, OUTPUT);
item->setExt(0);
digitalWrite(pin,INACTIVE);
//if (item->getCmd()) item->setFlag(SEND_COMMAND);
//if (item->itemVal) item->setFlag(SEND_PARAMETERS);
driverStatus = CST_INITIALIZED;
return 1;
}
int out_relay::Stop()
{
debugSerial<<F("Relay-Out #")<<pin<<F(" stop")<<endl;
pinMode(pin, INPUT);
driverStatus = CST_UNKNOWN;
return 1;
}
int out_relay::Status()
{
return driverStatus;
}
const char action_P[] PROGMEM = "/action";
const char cooling_P[] PROGMEM = "cooling";
const char heating_P[] PROGMEM = "heating";
const char drying_P[] PROGMEM = "drying";
const char idle_P[] PROGMEM = "idle";
const char fan_P[] PROGMEM = "fan";
const char off_P[] PROGMEM = "off";
void out_relay::relay(bool state)
{
char subtopic[10];
char val[10];
digitalWrite(pin,(state)?ACTIVE:INACTIVE);
if (period<1000) return;
debugSerial<<F("Out ")<<pin<<F(" is ")<<(state)<<endl;
strcpy_P(subtopic,action_P);
short cmd=item->getCmd();
if (state)
switch(cmd)
{
case CMD_COOL:
strcpy_P(val,cooling_P);
break;
//case CMD_AUTO:
//case CMD_HEAT:
//case CMD_ON:
//
// break;
case CMD_DRY:
strcpy_P(val,drying_P);
break;
case CMD_FAN:
strcpy_P(val,fan_P);
break;
default:
strcpy_P(val,heating_P);
}
else //turned off
if (cmd==CMD_OFF) strcpy_P(val,off_P);
else strcpy_P(val,idle_P);
publishTopic(item->itemArr->name,val,subtopic);
}
bool getPinVal(uint8_t pin)
{return (0!=(*portOutputRegister( digitalPinToPort(pin) ) & digitalPinToBitMask(pin)));}
int out_relay::Poll(short cause)
{
if (!item) return 0;
itemCmd st;
st.loadItem(item);
int val = st.getPercents255();
short cmd = st.getCmd();
uint32_t timer = item->getExt();
if (timer && isTimeOver(timer,millis(),period))
{
item->setExt(millisNZ());
if (val && (getPinVal(pin) == INACTIVE)) relay(true);
}
else if (timer && (getPinVal(pin) == ACTIVE) && isTimeOver(timer,millis(),period*val/255))
{
relay(false);
if (!item->isActive()) item->setExt(0);
}
return 0;
};
int out_relay::Ctrl(itemCmd cmd, char* subItem, bool toExecute)
{
debugSerial<<F("relayCtr: ");
cmd.debugOut();
int suffixCode;
if (cmd.isCommand()) suffixCode = S_CMD;
else suffixCode = cmd.getSuffix();
switch(suffixCode)
{
case S_NOTFOUND:
// turn on and set
toExecute = true;
case S_SET:
if (toExecute)
{
if (cmd.getPercents255())
{
if (!item->getExt())
{
item->setExt(millisNZ());
relay(true);
}
}
else
{
item->setExt(0);
relay(false);
}
}
return 1;
case S_CMD:
switch (cmd.getCmd())
{
case CMD_ON:
case CMD_HEAT:
case CMD_COOL:
case CMD_AUTO:
case CMD_FAN:
case CMD_DRY:
if (!item->getExt())
{
item->setExt(millisNZ());
relay(true);
}
return 1;
case CMD_OFF:
item->setExt(0);
relay(false);
return 1;
default:
debugSerial<<F("Unknown cmd ")<<cmd.getCmd()<<endl;
} //switch cmd
default:
debugSerial<<F("Unknown suffix ")<<suffixCode<<endl;
} //switch suffix
return 0;
}
int out_relay::getChanType()
{
return CH_PWM;
}
#endif

View File

@@ -0,0 +1,28 @@
#pragma once
#include "options.h"
#ifndef RELAY_DISABLE
#include <abstractout.h>
#include <item.h>
class out_relay : public abstractOut {
public:
out_relay(Item * _item):abstractOut(_item){ getConfig();};
void getConfig();
void relay(bool state);
int Setup() override;
int Poll(short cause) override;
int Stop() override;
int Status() override;
int getChanType() override;
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true) override;
protected:
short pin;
bool inverted;
uint32_t period;
};
#endif