Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to PID controller driver #19285

Merged
merged 16 commits into from
Aug 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 81 additions & 30 deletions tasmota/tasmota_xdrv_driver/xdrv_49_pid.ino
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@
// If not using the sensor then you can supply process values via MQTT using
// cmnd PidPv

#define PID_LOCAL_SENSOR_NAME "DS18B20" // local sensor name when PID_USE_LOCAL_SENSOR is defined above
// the JSON payload is parsed for this sensor to find the present value
// eg "ESP32":{"Temperature":31.4},"DS18B20":{"Temperature":12.6}

#define PID_LOCAL_SENSOR_TYPE D_JSON_TEMPERATURE // Choose one of D_JSON_TEMPERATURE D_JSON_HUMIDITY D_JSON_PRESSURE
// or any string as the sensor type. The JSON payload is parsed for the
// value in this field
// eg "HDC1080":{"Temperature":24.8,"Humidity":79.2}

#define PID_SHUTTER 1 // if using the PID to control a 3-way valve, create Tasmota Shutter and define the
// number of the shutter here. Otherwise leave this commented out

Expand Down Expand Up @@ -159,7 +168,15 @@
#ifndef PID_USE_TIMPROP
#define PID_USE_TIMPROP 1 // To disable this feature define as false in user_config_override
#endif
//#define PID_USE_LOCAL_SENSOR // [PidPv] If defined then the local sensor will be used for pv.

// #define PID_USE_LOCAL_SENSOR // If defined then the local sensor will be used for pv.
#ifndef PID_LOCAL_SENSOR_NAME
#define PID_LOCAL_SENSOR_NAME "DS18B20" // local sensor name when PID_USE_LOCAL_SENSOR is defined
#endif
#ifndef PID_LOCAL_SENSOR_TYPE
#define PID_LOCAL_SENSOR_TYPE D_JSON_TEMPERATURE // local sensor type
#endif

//#define PID_SHUTTER 1 // Number of the shutter here. Otherwise leave this commented out
#ifndef PID_REPORT_MORE_SETTINGS
#define PID_REPORT_MORE_SETTINGS true // Override to false if less details are required in SENSOR JSON
Expand Down Expand Up @@ -242,31 +259,41 @@ void PIDShowSensor() {
// Called each time new sensor data available, data in mqtt data in same format
// as published in tele/SENSOR
// Update period is specified in TELE_PERIOD
if (!isnan(TasmotaGlobal.temperature_celsius)) {
const float temperature = TasmotaGlobal.temperature_celsius;

float sensor_reading = NAN;

#if defined PID_USE_LOCAL_SENSOR
// copy the string into a new buffer that will be modified and
// parsed to find the local sensor reading if it's there
String buf = ResponseData();
JsonParser parser((char*)buf.c_str());
JsonParserObject root = parser.getRootObject();
if (root) {
JsonParserToken value_token = root[PID_LOCAL_SENSOR_NAME].getObject()[PSTR(PID_LOCAL_SENSOR_TYPE)];
if (value_token.isNum()) {
sensor_reading = value_token.getFloat();
}
}
#endif // PID_USE_LOCAL_SENSOR

if (!isnan(sensor_reading)) {

// pass the value to the pid alogorithm to use as current pv
Pid.last_pv_update_secs = Pid.current_time_secs;
Pid.pid.setPv(temperature, Pid.last_pv_update_secs);
Pid.pid.setPv(sensor_reading, Pid.last_pv_update_secs);
// also trigger running the pid algorithm if we have been told to run it each pv sample
if (Pid.update_secs == 0) {
// this runs it at the next second
Pid.run_pid_now = true;
}
} else {
AddLog(LOG_LEVEL_ERROR, PSTR("PID: No local temperature sensor found"));
// limit sensor not seen message to every 60 seconds to avoid flooding the logs
if ((Pid.current_time_secs - Pid.last_pv_update_secs) > Pid.max_interval && ((Pid.current_time_secs - Pid.last_pv_update_secs)%60)==0) {
AddLog(LOG_LEVEL_ERROR, PSTR("PID: Local temperature sensor missing for longer than PID_MAX_INTERVAL"));
}
}
}

/* struct XDRVMAILBOX { */
/* uint16_t valid; */
/* uint16_t index; */
/* uint16_t data_len; */
/* int16_t payload; */
/* char *topic; */
/* char *data; */
/* } XdrvMailbox; */

void CmndSetPv(void) {
Pid.last_pv_update_secs = Pid.current_time_secs;
if (XdrvMailbox.data_len > 0) {
Expand Down Expand Up @@ -339,14 +366,11 @@ void CmndSetManualPower(void) {
void CmndSetMaxInterval(void) {
if (XdrvMailbox.payload >= 0) {
Pid.pid.setMaxInterval(XdrvMailbox.payload);
Pid.max_interval=XdrvMailbox.payload;
}
ResponseCmndNumber(Pid.pid.getMaxInterval());
}

// case CMND_PID_SETUPDATE_SECS:
// Pid.update_secs = atoi(XdrvMailbox.data) ;
// if (Pid.update_secs < 0)
// Pid.update_secs = 0;
void CmndSetUpdateSecs(void) {
if (XdrvMailbox.payload >= 0) {
Pid.update_secs = (XdrvMailbox.payload);
Expand All @@ -364,51 +388,43 @@ void PIDShowValues(void) {
double d_buf;
ResponseAppend_P(PSTR(",\"PID\":{"));

// #define D_CMND_PID_SETPV "Pv"
d_buf = Pid.pid.getPv();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPv\":%s,"), str_buf);
// #define D_CMND_PID_SETSETPOINT "Sp"
d_buf = Pid.pid.getSp();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidSp\":%s,"), str_buf);

#if PID_REPORT_MORE_SETTINGS
// #define D_CMND_PID_SETPROPBAND "Pb"
d_buf = Pid.pid.getPb();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidPb\":%s,"), str_buf);
// #define D_CMND_PID_SETINTEGRAL_TIME "Ti"
d_buf = Pid.pid.getTi();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidTi\":%s,"), str_buf);
// #define D_CMND_PID_SETDERIVATIVE_TIME "Td"
d_buf = Pid.pid.getTd();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidTd\":%s,"), str_buf);
// #define D_CMND_PID_SETINITIAL_INT "Initint"
d_buf = Pid.pid.getInitialInt();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidInitialInt\":%s,"), str_buf);
// #define D_CMND_PID_SETDERIV_SMOOTH_FACTOR "DSmooth"
d_buf = Pid.pid.getDSmooth();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidDSmooth\":%s,"), str_buf);
// #define D_CMND_PID_SETAUTO "Auto"
chr_buf = Pid.pid.getAuto();
ResponseAppend_P(PSTR("\"PidAuto\":%d,"), chr_buf);
// #define D_CMND_PID_SETMANUAL_POWER "ManualPower"
d_buf = Pid.pid.getManualPower();
dtostrfd(d_buf, 2, str_buf);
ResponseAppend_P(PSTR("\"PidManualPower\":%s,"), str_buf);
// #define D_CMND_PID_SETMAX_INTERVAL "MaxInterval"
i_buf = Pid.pid.getMaxInterval();
ResponseAppend_P(PSTR("\"PidMaxInterval\":%d,"), i_buf);

// #define D_CMND_PID_SETUPDATE_SECS "UpdateSecs"
i_buf = Pid.current_time_secs - Pid.last_pv_update_secs;
ResponseAppend_P(PSTR("\"PidInterval\":%d,"), i_buf);
ResponseAppend_P(PSTR("\"PidUpdateSecs\":%d,"), Pid.update_secs);
#endif // PID_REPORT_MORE_SETTINGS

i_buf = (Pid.current_time_secs - Pid.last_pv_update_secs) > Pid.pid.getMaxInterval();
ResponseAppend_P(PSTR("\"PidSensorLost\":%d,"), i_buf);
// The actual power value
d_buf = Pid.pid.tick(Pid.current_time_secs);
dtostrfd(d_buf, 2, str_buf);
Expand All @@ -417,6 +433,38 @@ void PIDShowValues(void) {
ResponseAppend_P(PSTR("}"));
}

void PIDShowValuesWeb(void) {

#define D_PID_DISPLAY_NAME "PID Controller"
#define D_PID_SET_POINT "Set Point"
#define D_PID_PRESENT_VALUE "Current Value"
#define D_PID_POWER "Power"
#define D_PID_MODE "Controller Mode"
#define D_PID_MODE_AUTO "Auto"
#define D_PID_MODE_MANUAL "Manual"
#define D_PID_MODE_OFF "Off"

const char HTTP_PID_HL[] PROGMEM = "{s}<hr>{m}<hr>{e}";
const char HTTP_PID_INFO[] PROGMEM = "{s}" D_PID_DISPLAY_NAME "{m}%s{e}";
const char HTTP_PID_SP_FORMAT[] PROGMEM = "{s}%s " "{m}%*_f ";
const char HTTP_PID_PV_FORMAT[] PROGMEM = "{s}%s " "{m}%*_f ";
const char HTTP_PID_POWER_FORMAT[] PROGMEM = "{s}%s " "{m}%*_f " D_UNIT_PERCENT;

float f_buf;

WSContentSend_P(HTTP_PID_HL);
WSContentSend_P(HTTP_PID_INFO, (Pid.pid.getAuto()==1) ? D_PID_MODE_AUTO : Pid.pid.tick(Pid.current_time_secs)>0.0f ? D_PID_MODE_MANUAL : D_PID_MODE_OFF);

if (Pid.pid.getAuto()==1 || Pid.pid.tick(Pid.current_time_secs)>0.0f) {
f_buf = (float)Pid.pid.getSp();
WSContentSend_PD(HTTP_PID_SP_FORMAT, D_PID_SET_POINT, 1, &f_buf);
f_buf = (float)Pid.pid.getPv();
WSContentSend_PD(HTTP_PID_PV_FORMAT, D_PID_PRESENT_VALUE, 1, &f_buf);
f_buf = Pid.pid.tick(Pid.current_time_secs)*100.0f;
WSContentSend_PD(HTTP_PID_POWER_FORMAT, D_PID_POWER, 0, &f_buf);
}
}

void PIDRun(void) {
double power = Pid.pid.tick(Pid.current_time_secs);
#ifdef PID_DONT_USE_PID_TOPIC
Expand Down Expand Up @@ -468,6 +516,9 @@ bool Xdrv49(uint32_t function) {
case FUNC_JSON_APPEND:
PIDShowValues();
break;
case FUNC_WEB_SENSOR:
PIDShowValuesWeb();
break;
}
return result;
}
Expand Down