mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 16:59:50 +03:00
version 1.1
This commit is contained in:
333
src/boiler.ino
333
src/boiler.ino
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Boiler Project
|
* EMS-ESP-Boiler
|
||||||
* Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler
|
* Paul Derbyshire - May 2018 - https://github.com/proddy/EMS-ESP-Boiler
|
||||||
*
|
*
|
||||||
* See Readme for Acknowledgments
|
* See ReadMe for Acknowledgments
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// local libraries
|
// local libraries
|
||||||
@@ -11,7 +11,10 @@
|
|||||||
#include "emsuart.h"
|
#include "emsuart.h"
|
||||||
|
|
||||||
// public libraries
|
// public libraries
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
||||||
|
#include <CRC32.h> // https://github.com/bakercp/CRC32
|
||||||
|
|
||||||
|
// standard libs
|
||||||
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
|
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
|
||||||
|
|
||||||
// these are set as -D build flags. If you're not using PlatformIO hard code them
|
// these are set as -D build flags. If you're not using PlatformIO hard code them
|
||||||
@@ -21,17 +24,7 @@
|
|||||||
//#define MQTT_USER "<broker_username>"
|
//#define MQTT_USER "<broker_username>"
|
||||||
//#define MQTT_PASS "<broker_password>"
|
//#define MQTT_PASS "<broker_password>"
|
||||||
|
|
||||||
// private function prototypes
|
// timers, all values are in seconds
|
||||||
void heartbeat();
|
|
||||||
void systemCheck();
|
|
||||||
void regulatUpdates();
|
|
||||||
void publishValues();
|
|
||||||
void _showerColdShotStart();
|
|
||||||
void _showerColdShotStop();
|
|
||||||
|
|
||||||
char * _float_to_char(char * a, float f, uint8_t precision = 1);
|
|
||||||
|
|
||||||
// timers
|
|
||||||
#define PUBLISHVALUES_TIME 300 // every 5 mins post HA values
|
#define PUBLISHVALUES_TIME 300 // every 5 mins post HA values
|
||||||
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
|
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
|
||||||
#define REGULARUPDATES_TIME 60 // every minute a call is made
|
#define REGULARUPDATES_TIME 60 // every minute a call is made
|
||||||
@@ -40,26 +33,28 @@ Ticker publishValuesTimer;
|
|||||||
Ticker systemCheckTimer;
|
Ticker systemCheckTimer;
|
||||||
Ticker regularUpdatesTimer;
|
Ticker regularUpdatesTimer;
|
||||||
Ticker heartbeatTimer;
|
Ticker heartbeatTimer;
|
||||||
Ticker showerResetTimer;
|
Ticker showerColdShotStopTimer;
|
||||||
uint8_t regularUpdatesCount = 0;
|
uint8_t regularUpdatesCount = 0;
|
||||||
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
|
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
|
||||||
|
|
||||||
// Project commands for telnet
|
// Project commands for telnet
|
||||||
// Note: ?, *, $, ! and & are reserved
|
// Note: ?, *, $, ! and & are reserved
|
||||||
#define PROJECT_CMDS \
|
#define PROJECT_CMDS \
|
||||||
"* s=show statistics\n\r" \
|
"* s show statistics\n\r" \
|
||||||
"* P=publish stats to MQTT\n\r" \
|
"* P publish stats to MQTT\n\r" \
|
||||||
"* v [n] set logging to 0=none, 1=basic, 2=verbose\n\r" \
|
"* v [n] set logging (0=none, 1=basic, 2=verbose)\n\r" \
|
||||||
"* p=Poll response on/off\n\r" \
|
"* p poll response on/off\n\r" \
|
||||||
"* T=Thermostat Support on/off\n\r" \
|
"* T thermostat Support on/off\n\r" \
|
||||||
"* S=Shower Timer on/off\n\r" \
|
"* S shower timer on/off\n\r" \
|
||||||
"* r [n] to request for data from EMS, some examples:\n\r" \
|
"* A shower alert on/off\n\r" \
|
||||||
|
"* r [n] request for data from EMS. Examples:\n\r" \
|
||||||
"* from Boiler: 33=UBAParameterWW, 18=UBAMonitorFast, 19=UBAMonitorSlow, 34=UBAMonitorWWMessage\n\r" \
|
"* from Boiler: 33=UBAParameterWW, 18=UBAMonitorFast, 19=UBAMonitorSlow, 34=UBAMonitorWWMessage\n\r" \
|
||||||
"* from Thermostat: 91=RC20StatusMessage, A8=RC20Temperature, 6=RC20Time, 2=Version\n\r" \
|
"* from Thermostat: 91=RC20StatusMessage, A8=RC20Temperature, 6=RC20Time, 2=Version\n\r" \
|
||||||
"* t [n] set thermostat temperature to n\n\r" \
|
"* t [n] set thermostat temperature to n\n\r" \
|
||||||
"* m [n] set thermostat mode (0=low, 1=manual, 2=clock)\n\r" \
|
"* m [n] set thermostat mode (0=low, 1=manual, 2=clock)\n\r" \
|
||||||
"* w [n] set boiler warm water temperature to n (min 30)\n\r" \
|
"* w [n] set boiler warm water temperature to n (min 30)\n\r" \
|
||||||
"* a [n] activate boiler warm water on (n=1) or off (n=0)"
|
"* a [n] boiler warm water on (n=1) or off (n=0)\n\r" \
|
||||||
|
"* x [n] experimental (warning: for debugging only!)"
|
||||||
|
|
||||||
// GPIOs
|
// GPIOs
|
||||||
#define LED_RX D1 // (GPIO5 on nodemcu)
|
#define LED_RX D1 // (GPIO5 on nodemcu)
|
||||||
@@ -78,37 +73,57 @@ uint8_t regularUpdatesCount = 0;
|
|||||||
#define TOPIC_THERMOSTAT_CURRTEMP MQTT_BOILER "thermostat_currtemp" // current temperature
|
#define TOPIC_THERMOSTAT_CURRTEMP MQTT_BOILER "thermostat_currtemp" // current temperature
|
||||||
#define TOPIC_THERMOSTAT_SELTEMP MQTT_BOILER "thermostat_seltemp" // selected temperature
|
#define TOPIC_THERMOSTAT_SELTEMP MQTT_BOILER "thermostat_seltemp" // selected temperature
|
||||||
#define TOPIC_THERMOSTAT_MODE MQTT_BOILER "thermostat_mode" // selected temperature
|
#define TOPIC_THERMOSTAT_MODE MQTT_BOILER "thermostat_mode" // selected temperature
|
||||||
|
#define TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE MQTT_BOILER "boiler_wwtemp" // warm water selected temp
|
||||||
|
|
||||||
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
||||||
|
|
||||||
#define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results
|
#define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results
|
||||||
|
#define TOPIC_SHOWER_ALARM "shower_alarm" // for notifying HA that shower time has reached its limit
|
||||||
|
#define TOPIC_SHOWER_TIMER MQTT_BOILER "shower_timer" // toggle switch for enabling the shower logic
|
||||||
|
#define TOPIC_SHOWER_ALERT MQTT_BOILER "shower_alert" // toggle switch for enabling the shower alarm logic
|
||||||
|
#define TOPIC_SHOWER_COLDSHOT MQTT_BOILER "shower_coldshot" // used to trigger a coldshot from HA publish
|
||||||
|
|
||||||
// default values
|
// default values
|
||||||
// thermostat support, shower timing and shower alert all enabled
|
// thermostat support, shower timing and shower alert all enabled (1) (disabled = 0)
|
||||||
#define BOILER_THERMOSTAT_ENABLED 1
|
#define BOILER_THERMOSTAT_ENABLED 1
|
||||||
#define BOILER_SHOWER_ENABLED 1
|
#define BOILER_SHOWER_TIMER 1
|
||||||
#define BOILER_SHOWER_TIMER 0
|
#define BOILER_SHOWER_ALERT 0
|
||||||
#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_BASIC // or EMS_SYS_LOGGING_VERBOSE
|
|
||||||
|
// logging - EMS_SYS_LOGGING_VERBOSE, EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC (see ems.h)
|
||||||
|
#define BOILER_DEFAULT_LOGGING EMS_SYS_LOGGING_NONE
|
||||||
|
|
||||||
// shower settings
|
// shower settings
|
||||||
|
#ifndef SHOWER_TEST
|
||||||
const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower
|
const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower
|
||||||
const unsigned long SHOWER_MIN_DURATION = 120000; // 2 minutes, before recognizing its a shower
|
const unsigned long SHOWER_MIN_DURATION = 180000; // 3 minutes, before recognizing its a shower
|
||||||
const unsigned long SHOWER_MAX_DURATION = 420000; // 7 minutes, before trigger a shot of cold water
|
const unsigned long SHOWER_MAX_DURATION = 420000; // 7 minutes, before trigger a shot of cold water
|
||||||
const unsigned long SHOWER_OFF_DURATION = 3000; // 3 seconds long for cold water
|
const unsigned long SHOWER_COLDSHOT_DURATION = 5; // 5 seconds for cold water - note, must be in seconds
|
||||||
|
const unsigned long SHOWER_OFFSET_TIME = 8000; // 8 seconds grace time, to calibrate actual time under the shower
|
||||||
|
#else
|
||||||
|
// for DEBUGGING only
|
||||||
|
const unsigned long SHOWER_PAUSE_TIME = 15000; // 15 seconds, max time if water is switched off & on during a shower
|
||||||
|
const unsigned long SHOWER_MIN_DURATION = 20000; // 20 secs, before recognizing its a shower
|
||||||
|
const unsigned long SHOWER_MAX_DURATION = 25000; // 25 secs, before trigger a shot of cold water
|
||||||
|
const unsigned long SHOWER_COLDSHOT_DURATION = 5; // in seconds! how long for cold water shot
|
||||||
|
const unsigned long SHOWER_OFFSET_TIME = 0; // 0 seconds grace time, to calibrate actual time under the shower
|
||||||
|
#endif
|
||||||
|
|
||||||
const uint8_t SHOWER_BURNPOWER_MIN = 80;
|
const uint8_t SHOWER_BURNPOWER_MIN = 80;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool wifi_connected;
|
bool wifi_connected;
|
||||||
bool boiler_online;
|
bool boiler_online;
|
||||||
bool thermostat_enabled;
|
bool thermostat_enabled;
|
||||||
bool shower_enabled; // true if we want to report back on shower times
|
bool shower_timer; // true if we want to report back on shower times
|
||||||
bool shower_timer; // true if we want the cold water reminder
|
bool shower_alert; // true if we want the cold water reminder
|
||||||
} _Boiler_Status;
|
} _Boiler_Status;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool showerOn;
|
bool showerOn;
|
||||||
|
bool hotWaterOn;
|
||||||
unsigned long timerStart; // ms
|
unsigned long timerStart; // ms
|
||||||
unsigned long timerPause; // ms
|
unsigned long timerPause; // ms
|
||||||
unsigned long duration; // ms
|
unsigned long duration; // ms
|
||||||
bool isColdShot; // true if we've just sent a jolt of cold water
|
bool doingColdShot; // true if we've just sent a jolt of cold water
|
||||||
} _Boiler_Shower;
|
} _Boiler_Shower;
|
||||||
|
|
||||||
// ESPHelper
|
// ESPHelper
|
||||||
@@ -128,6 +143,9 @@ _Boiler_Shower Boiler_Shower;
|
|||||||
// Debugger to telnet
|
// Debugger to telnet
|
||||||
#define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__);
|
#define myDebug(x, ...) myESP.printf(x, ##__VA_ARGS__);
|
||||||
|
|
||||||
|
// CRC
|
||||||
|
uint32_t previousPublishCRC = 0;
|
||||||
|
|
||||||
// Times
|
// Times
|
||||||
const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline
|
const unsigned long POLL_TIMEOUT_ERR = 10000; // if no signal from boiler for last 10 seconds, assume its offline
|
||||||
const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED because its so quick
|
const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED because its so quick
|
||||||
@@ -139,8 +157,19 @@ bool startMQTTsent = false;
|
|||||||
// toggle for heartbeat LED
|
// toggle for heartbeat LED
|
||||||
bool heartbeatEnabled;
|
bool heartbeatEnabled;
|
||||||
|
|
||||||
|
// logging messages with fixed strings (newline done automatically)
|
||||||
|
void myDebugLog(const char * s) {
|
||||||
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("%s\n", s);
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
myESP.logger(LOG_HA, s);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// convert float to char
|
// convert float to char
|
||||||
char * _float_to_char(char * a, float f, uint8_t precision) {
|
//char * _float_to_char(char * a, float f, uint8_t precision = 1);
|
||||||
|
char * _float_to_char(char * a, float f, uint8_t precision = 1) {
|
||||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
||||||
|
|
||||||
char * ret = a;
|
char * ret = a;
|
||||||
@@ -218,7 +247,7 @@ void _renderBoolValue(const char * prefix, uint8_t value) {
|
|||||||
void showInfo() {
|
void showInfo() {
|
||||||
// General stats from EMS bus
|
// General stats from EMS bus
|
||||||
|
|
||||||
myDebug("%sEMS-ESP-Boiler general info:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||||
myDebug(" System Logging is set to ");
|
myDebug(" System Logging is set to ");
|
||||||
_EMS_SYS_LOGGING sysLog = ems_getLogging();
|
_EMS_SYS_LOGGING sysLog = ems_getLogging();
|
||||||
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
||||||
@@ -229,11 +258,11 @@ void showInfo() {
|
|||||||
myDebug("None");
|
myDebug("None");
|
||||||
}
|
}
|
||||||
|
|
||||||
myDebug("\n Thermostat is %s, Poll is %s, Shower is %s, Shower timer is %s\n",
|
myDebug("\n Thermostat is %s, Poll is %s, Shower timer is %s, Shower alert is %s\n",
|
||||||
((Boiler_Status.thermostat_enabled) ? "enabled" : "disabled"),
|
((Boiler_Status.thermostat_enabled) ? "enabled" : "disabled"),
|
||||||
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
||||||
((Boiler_Status.shower_enabled) ? "enabled" : "disabled"),
|
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
|
||||||
((Boiler_Status.shower_timer) ? "enabled" : "disabled"));
|
((Boiler_Status.shower_alert) ? "enabled" : "disabled"));
|
||||||
|
|
||||||
myDebug(" EMS Bus Stats: RxPgks=%d, TxPkgs=%d, #CrcErrors=%d, ",
|
myDebug(" EMS Bus Stats: RxPgks=%d, TxPkgs=%d, #CrcErrors=%d, ",
|
||||||
EMS_Sys_Status.emsRxPgks,
|
EMS_Sys_Status.emsRxPgks,
|
||||||
@@ -305,7 +334,7 @@ void showInfo() {
|
|||||||
_renderBoolValue("Fan", EMS_Boiler.fanWork);
|
_renderBoolValue("Fan", EMS_Boiler.fanWork);
|
||||||
_renderBoolValue("Ignition", EMS_Boiler.ignWork);
|
_renderBoolValue("Ignition", EMS_Boiler.ignWork);
|
||||||
_renderBoolValue("Circulation pump", EMS_Boiler.wWCirc);
|
_renderBoolValue("Circulation pump", EMS_Boiler.wWCirc);
|
||||||
_renderIntValue("Burner max power", "%", EMS_Boiler.selBurnPow);
|
_renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow);
|
||||||
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
||||||
_renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
_renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
||||||
_renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress);
|
_renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress);
|
||||||
@@ -350,14 +379,10 @@ void showInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// show the Shower Info
|
// show the Shower Info
|
||||||
if (Boiler_Status.shower_enabled) {
|
if (Boiler_Status.shower_timer) {
|
||||||
myDebug("\n%sShower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
myDebug("\n%sShower stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||||
|
myDebug(" Hot water is %s\n", (Boiler_Shower.hotWaterOn ? "running" : "stopped"));
|
||||||
myDebug(" Shower is %s\n", (Boiler_Shower.showerOn ? "on" : "off"));
|
myDebug(" Shower is %s\n", (Boiler_Shower.showerOn ? "on" : "off"));
|
||||||
char s[70];
|
|
||||||
uint8_t sec = (uint8_t)((Boiler_Shower.duration / 1000) % 60);
|
|
||||||
uint8_t min = (uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60);
|
|
||||||
sprintf(s, " Last shower duration was %d minutes and %d %s\n", min, sec, (sec == 1) ? "second" : "seconds");
|
|
||||||
myDebug(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
myDebug("\n");
|
myDebug("\n");
|
||||||
@@ -365,14 +390,15 @@ void showInfo() {
|
|||||||
|
|
||||||
// send values to HA via MQTT
|
// send values to HA via MQTT
|
||||||
void publishValues() {
|
void publishValues() {
|
||||||
myDebug("Publishing data to MQTT topics\n");
|
|
||||||
|
|
||||||
char s[20]; // for formatting strings
|
char s[20]; // for formatting strings
|
||||||
|
|
||||||
// Boiler values as one JSON object
|
// Boiler values as one JSON object
|
||||||
StaticJsonBuffer<512> jsonBuffer;
|
StaticJsonBuffer<512> jsonBuffer;
|
||||||
char data[512];
|
char data[512];
|
||||||
JsonObject & root = jsonBuffer.createObject();
|
JsonObject & root = jsonBuffer.createObject();
|
||||||
|
|
||||||
|
root["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp);
|
||||||
|
root["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated);
|
||||||
root["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp);
|
root["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp);
|
||||||
root["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
|
root["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat);
|
||||||
root["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp);
|
root["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp);
|
||||||
@@ -386,11 +412,31 @@ void publishValues() {
|
|||||||
root["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow);
|
root["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow);
|
||||||
root["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress);
|
root["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress);
|
||||||
root["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp);
|
root["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp);
|
||||||
root["pumpMod"] = _float_to_char(s, EMS_Boiler.pumpMod);
|
root["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod);
|
||||||
|
|
||||||
root.printTo(data, root.measureLength() + 1);
|
size_t len = root.measureLength();
|
||||||
|
root.printTo(data, len + 1); // form the json string
|
||||||
|
|
||||||
|
// calculate hash
|
||||||
|
CRC32 crc;
|
||||||
|
for (size_t i = 0; i < len - 1; i++) {
|
||||||
|
crc.update(data[i]);
|
||||||
|
}
|
||||||
|
uint32_t checksum = crc.finalize();
|
||||||
|
//myDebug("HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data);
|
||||||
|
|
||||||
|
// only send values if something has changed, to save unnecessary wifi traffic
|
||||||
|
if (previousPublishCRC != checksum) {
|
||||||
|
previousPublishCRC = checksum;
|
||||||
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("Publishing data to MQTT topics\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// send values via MQTT
|
||||||
myESP.publish(TOPIC_BOILER_DATA, data);
|
myESP.publish(TOPIC_BOILER_DATA, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle the thermostat values separately
|
||||||
if (EMS_Sys_Status.emsThermostatEnabled) {
|
if (EMS_Sys_Status.emsThermostatEnabled) {
|
||||||
// only send thermostat values if we actually have them
|
// only send thermostat values if we actually have them
|
||||||
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) {
|
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) {
|
||||||
@@ -408,7 +454,20 @@ void publishValues() {
|
|||||||
} else {
|
} else {
|
||||||
myESP.publish(TOPIC_THERMOSTAT_MODE, "auto");
|
myESP.publish(TOPIC_THERMOSTAT_MODE, "auto");
|
||||||
}
|
}
|
||||||
myESP.publish(TOPIC_THERMOSTAT_SELTEMP, _float_to_char(s, EMS_Thermostat.setpoint_roomTemp));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets the shower timer on/off
|
||||||
|
void set_showerTimer() {
|
||||||
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("Shower timer is %s\n", Boiler_Status.shower_timer ? "enabled" : "disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets the shower alert on/off
|
||||||
|
void set_showerAlert() {
|
||||||
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("Shower alert is %s\n", Boiler_Status.shower_alert ? "enabled" : "disabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,6 +485,7 @@ void myDebugCallback() {
|
|||||||
ems_setPoll(b);
|
ems_setPoll(b);
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
|
myESP.logger(LOG_HA, "Force publish values");
|
||||||
publishValues();
|
publishValues();
|
||||||
break;
|
break;
|
||||||
case 'r': // read command for Boiler or Thermostat
|
case 'r': // read command for Boiler or Thermostat
|
||||||
@@ -450,14 +510,21 @@ void myDebugCallback() {
|
|||||||
else if ((cmd[2] - '0') == 0)
|
else if ((cmd[2] - '0') == 0)
|
||||||
ems_setWarmWaterActivated(false);
|
ems_setWarmWaterActivated(false);
|
||||||
break;
|
break;
|
||||||
|
case 'x': // experimental code for debugging - use with caution!
|
||||||
|
ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param
|
||||||
|
break;
|
||||||
case 'T': // toggle Thermostat
|
case 'T': // toggle Thermostat
|
||||||
b = !ems_getThermostatEnabled();
|
b = !ems_getThermostatEnabled();
|
||||||
ems_setThermostatEnabled(b);
|
ems_setThermostatEnabled(b);
|
||||||
Boiler_Status.thermostat_enabled = b;
|
Boiler_Status.thermostat_enabled = b;
|
||||||
break;
|
break;
|
||||||
case 'S': // toggle Shower timer support
|
case 'S': // toggle Shower timer support
|
||||||
Boiler_Status.shower_enabled = !Boiler_Status.shower_enabled;
|
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
|
||||||
myDebug("Shower timer is %s\n", Boiler_Status.shower_enabled ? "enabled" : "disabled");
|
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
||||||
|
break;
|
||||||
|
case 'A': // toggle Shower alert
|
||||||
|
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
|
||||||
|
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
myDebug("Unknown command '%c'. Use ? for help.\n", cmd[0]);
|
myDebug("Unknown command '%c'. Use ? for help.\n", cmd[0]);
|
||||||
@@ -470,7 +537,7 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
|||||||
// check if start is received, if so return boottime - defined in ESPHelper.h
|
// check if start is received, if so return boottime - defined in ESPHelper.h
|
||||||
if (strcmp(topic, TOPIC_START) == 0) {
|
if (strcmp(topic, TOPIC_START) == 0) {
|
||||||
payload[length] = '\0'; // add null terminator
|
payload[length] = '\0'; // add null terminator
|
||||||
myDebug("MQTT topic boottime: %s\n", payload);
|
//myDebug("MQTT topic boottime: %s\n", payload);
|
||||||
myESP.setBoottime((char *)payload);
|
myESP.setBoottime((char *)payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -481,8 +548,59 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
|||||||
char s[10];
|
char s[10];
|
||||||
myDebug("MQTT topic: thermostat_temp value %s\n", _float_to_char(s, f));
|
myDebug("MQTT topic: thermostat_temp value %s\n", _float_to_char(s, f));
|
||||||
ems_setThermostatTemp(f);
|
ems_setThermostatTemp(f);
|
||||||
|
// publish back so HA is immediately updated
|
||||||
|
publishValues();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boiler_warm_water_selected_temperature
|
||||||
|
if (strcmp(topic, TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE) == 0) {
|
||||||
|
uint8_t i = strtol((char *)payload, 0, 10);
|
||||||
|
myDebug("MQTT topic: boiler_warm_water_selected_temperature value %d\n", i);
|
||||||
|
ems_setWarmWaterTemp(i);
|
||||||
|
// publish back so HA is immediately updated
|
||||||
|
publishValues();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shower timer
|
||||||
|
if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) {
|
||||||
|
if (payload[0] == '1') {
|
||||||
|
Boiler_Status.shower_timer = true;
|
||||||
|
} else if (payload[0] == '0') {
|
||||||
|
Boiler_Status.shower_timer = false;
|
||||||
|
}
|
||||||
|
set_showerTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shower alert
|
||||||
|
if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) {
|
||||||
|
if (payload[0] == '1') {
|
||||||
|
Boiler_Status.shower_alert = true;
|
||||||
|
} else if (payload[0] == '0') {
|
||||||
|
Boiler_Status.shower_alert = false;
|
||||||
|
}
|
||||||
|
set_showerAlert();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shower cold shot
|
||||||
|
if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) {
|
||||||
|
if (Boiler_Status.shower_alert) {
|
||||||
|
_showerColdShotStart();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if HA is booted, restart device too
|
||||||
|
if (strcmp(topic, MQTT_HA) == 0) {
|
||||||
|
payload[length] = '\0'; // add null terminator
|
||||||
|
if (strcmp((char *)payload, "start") == 0) {
|
||||||
|
myDebug("HA rebooted - restarting device\n");
|
||||||
|
myESP.resetESP();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WifiCallback, called when a WiFi connect has successfully been established
|
// WifiCallback, called when a WiFi connect has successfully been established
|
||||||
@@ -516,8 +634,8 @@ void updateHeartbeat() {
|
|||||||
// Initialize the boiler settings
|
// Initialize the boiler settings
|
||||||
void _initBoiler() {
|
void _initBoiler() {
|
||||||
// default settings
|
// default settings
|
||||||
Boiler_Status.shower_enabled = BOILER_SHOWER_ENABLED;
|
|
||||||
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
||||||
|
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
|
||||||
Boiler_Status.thermostat_enabled = BOILER_THERMOSTAT_ENABLED;
|
Boiler_Status.thermostat_enabled = BOILER_THERMOSTAT_ENABLED;
|
||||||
ems_setThermostatEnabled(Boiler_Status.thermostat_enabled);
|
ems_setThermostatEnabled(Boiler_Status.thermostat_enabled);
|
||||||
|
|
||||||
@@ -529,7 +647,7 @@ void _initBoiler() {
|
|||||||
Boiler_Shower.timerStart = 0;
|
Boiler_Shower.timerStart = 0;
|
||||||
Boiler_Shower.timerPause = 0;
|
Boiler_Shower.timerPause = 0;
|
||||||
Boiler_Shower.duration = 0;
|
Boiler_Shower.duration = 0;
|
||||||
Boiler_Shower.isColdShot = false;
|
Boiler_Shower.doingColdShot = false;
|
||||||
|
|
||||||
// heartbeat only if verbose logging
|
// heartbeat only if verbose logging
|
||||||
ems_setLogging(BOILER_DEFAULT_LOGGING);
|
ems_setLogging(BOILER_DEFAULT_LOGGING);
|
||||||
@@ -564,10 +682,16 @@ void setup() {
|
|||||||
|
|
||||||
// set up MQTT
|
// set up MQTT
|
||||||
myESP.setMQTTCallback(MQTTcallback);
|
myESP.setMQTTCallback(MQTTcallback);
|
||||||
|
myESP.addSubscription(MQTT_HA);
|
||||||
myESP.addSubscription(TOPIC_START);
|
myESP.addSubscription(TOPIC_START);
|
||||||
myESP.addSubscription(TOPIC_THERMOSTAT_TEMP);
|
myESP.addSubscription(TOPIC_THERMOSTAT_TEMP);
|
||||||
|
myESP.addSubscription(TOPIC_SHOWER_TIMER);
|
||||||
|
myESP.addSubscription(TOPIC_SHOWER_ALERT);
|
||||||
|
myESP.addSubscription(TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE);
|
||||||
|
myESP.addSubscription(TOPIC_SHOWER_COLDSHOT);
|
||||||
|
|
||||||
// set up Telnet
|
// set up Telnet
|
||||||
|
|
||||||
myESP.consoleSetHelpProjectsCmds(PROJECT_CMDS);
|
myESP.consoleSetHelpProjectsCmds(PROJECT_CMDS);
|
||||||
myESP.consoleSetCallBackProjectCmds(myDebugCallback);
|
myESP.consoleSetCallBackProjectCmds(myDebugCallback);
|
||||||
myESP.begin(HOSTNAME);
|
myESP.begin(HOSTNAME);
|
||||||
@@ -617,9 +741,9 @@ void systemCheck() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls to get data from EMS for the types that aren't sent as broadcasts
|
// force calls to get data from EMS for the types that aren't sent as broadcasts
|
||||||
// number of calls is defined in MAX_MANUAL_CALLS
|
// number of calls is defined in MAX_MANUAL_CALLS
|
||||||
// it's done sequentially with a count since we don't queue sends (there's really no point)
|
// it's done as a cycle to prevent collisions, since we can only do 1 read command at a time
|
||||||
void regularUpdates() {
|
void regularUpdates() {
|
||||||
uint8_t cycle = (regularUpdatesCount++ % MAX_MANUAL_CALLS);
|
uint8_t cycle = (regularUpdatesCount++ % MAX_MANUAL_CALLS);
|
||||||
|
|
||||||
@@ -632,18 +756,20 @@ void regularUpdates() {
|
|||||||
|
|
||||||
// turn off hot water to send a shot of cold
|
// turn off hot water to send a shot of cold
|
||||||
void _showerColdShotStart() {
|
void _showerColdShotStart() {
|
||||||
myDebug("Shower: exceeded max shower time, doing a shot of cold...\n");
|
myDebugLog("Shower: doing a shot of cold");
|
||||||
ems_setWarmWaterActivated(false);
|
ems_setWarmWaterActivated(false);
|
||||||
Boiler_Shower.isColdShot = true;
|
Boiler_Shower.doingColdShot = true;
|
||||||
|
// start the timer for n seconds which will reset the water back to hot
|
||||||
|
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn back on the hot water for the shower
|
// turn back on the hot water for the shower
|
||||||
void _showerColdShotStop() {
|
void _showerColdShotStop() {
|
||||||
if (Boiler_Shower.isColdShot) {
|
if (Boiler_Shower.doingColdShot) {
|
||||||
myDebug("Shower: turning back hot shower water.\n");
|
myDebugLog("Shower: finished shot of cold. hot water back on");
|
||||||
ems_setWarmWaterActivated(true);
|
ems_setWarmWaterActivated(true);
|
||||||
Boiler_Shower.isColdShot = false;
|
Boiler_Shower.doingColdShot = false;
|
||||||
showerResetTimer.detach();
|
showerColdShotStopTimer.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,18 +787,24 @@ void loop() {
|
|||||||
|
|
||||||
// do not continue unless we have a wifi connection
|
// do not continue unless we have a wifi connection
|
||||||
if (connectionStatus < WIFI_ONLY) {
|
if (connectionStatus < WIFI_ONLY) {
|
||||||
myDebug("Waiting to connect to wifi...\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if first time connected to MQTT, send welcome start message
|
// if first time connected to MQTT, send welcome start message
|
||||||
// which will send all the state values from HA back to the clock via MQTT and return the boottime
|
// which will send all the state values from HA back to the clock via MQTT and return the boottime
|
||||||
if ((!startMQTTsent) && (connectionStatus == FULL_CONNECTION)) {
|
if ((!startMQTTsent) && (connectionStatus == FULL_CONNECTION)) {
|
||||||
|
// now that we're connected lets get some data from the EMS
|
||||||
|
ems_doReadCommand(EMS_TYPE_UBAParameterWW);
|
||||||
|
|
||||||
myESP.sendStart();
|
myESP.sendStart();
|
||||||
startMQTTsent = true;
|
startMQTTsent = true;
|
||||||
|
|
||||||
// now that we're connected lets get some data from the EMS
|
// publish to HA the status of the Shower parameters
|
||||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW);
|
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
||||||
|
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||||
|
|
||||||
|
// make sure warm water if activated, in case it got stuck with the shower alert
|
||||||
|
ems_setWarmWaterActivated(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we received new data and flagged for pushing, do it
|
// if we received new data and flagged for pushing, do it
|
||||||
@@ -684,58 +816,83 @@ void loop() {
|
|||||||
/*
|
/*
|
||||||
* Shower Logic
|
* Shower Logic
|
||||||
*/
|
*/
|
||||||
if (Boiler_Status.shower_enabled) {
|
if (Boiler_Status.shower_timer) {
|
||||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
||||||
if (!Boiler_Shower.isColdShot) {
|
if (!Boiler_Shower.doingColdShot) {
|
||||||
// these values come from UBAMonitorFast - type 0x18) which is broadcasted every second so our timings are accurate enough
|
// these values come from UBAMonitorFast - type 0x18) which is broadcasted every second so our timings are accurate enough
|
||||||
// and no need to fetch the values from the boiler
|
// and no need to fetch the values from the boiler
|
||||||
Boiler_Shower.showerOn =
|
Boiler_Shower.hotWaterOn =
|
||||||
((EMS_Boiler.selBurnPow >= SHOWER_BURNPOWER_MIN) && (EMS_Boiler.selFlowTemp == 0) && EMS_Boiler.burnGas);
|
((EMS_Boiler.selBurnPow >= SHOWER_BURNPOWER_MIN) && (EMS_Boiler.selFlowTemp == 0) && EMS_Boiler.burnGas);
|
||||||
|
|
||||||
// is the shower on?
|
// is the hot water running?
|
||||||
if (Boiler_Shower.showerOn) {
|
if (Boiler_Shower.hotWaterOn) {
|
||||||
// if heater was off, start the timer
|
// if heater was previously off, start the timer
|
||||||
if (Boiler_Shower.timerStart == 0) {
|
if (Boiler_Shower.timerStart == 0) {
|
||||||
|
// hot water just started...
|
||||||
Boiler_Shower.timerStart = timestamp;
|
Boiler_Shower.timerStart = timestamp;
|
||||||
Boiler_Shower.timerPause = 0; // remove any last pauses
|
Boiler_Shower.timerPause = 0; // remove any last pauses
|
||||||
Boiler_Shower.isColdShot = false;
|
Boiler_Shower.doingColdShot = false;
|
||||||
Boiler_Shower.duration = 0;
|
Boiler_Shower.duration = 0;
|
||||||
myDebug("Shower: starting timer...\n");
|
Boiler_Shower.showerOn = false;
|
||||||
|
myDebugLog("Shower: hot water on...");
|
||||||
} else {
|
} else {
|
||||||
|
// hot water has been on for a while
|
||||||
|
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath
|
||||||
|
if (!Boiler_Shower.showerOn && (timestamp - Boiler_Shower.timerStart) > SHOWER_MIN_DURATION) {
|
||||||
|
Boiler_Shower.showerOn = true;
|
||||||
|
myDebugLog("Shower: hot water still running, starting shower timer");
|
||||||
|
}
|
||||||
// check if the shower has been on too long
|
// check if the shower has been on too long
|
||||||
if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) && !Boiler_Shower.isColdShot)
|
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION)
|
||||||
&& Boiler_Status.shower_timer) {
|
&& !Boiler_Shower.doingColdShot)
|
||||||
|
&& Boiler_Status.shower_alert) {
|
||||||
|
myESP.sendHACommand(TOPIC_SHOWER_ALARM);
|
||||||
|
myDebugLog("Shower: exceeded max shower time");
|
||||||
_showerColdShotStart();
|
_showerColdShotStart();
|
||||||
// start the timer for n seconds which will reset the water back to hot
|
|
||||||
showerResetTimer.attach(SHOWER_OFF_DURATION, _showerColdShotStop);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // shower is off
|
} else { // hot water is off
|
||||||
// if it just turned off, record the time as it could be a pause
|
// if it just turned off, record the time as it could be a short pause
|
||||||
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
||||||
Boiler_Shower.timerPause = timestamp;
|
Boiler_Shower.timerPause = timestamp;
|
||||||
myDebug("Shower: water has just turned off...\n");
|
myDebugLog("Shower: hot water turned off");
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// if shower has been off for longer than the wait time
|
// if shower has been off for longer than the wait time
|
||||||
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
||||||
// its over the wait period, so assume that the shower has finished and calculate the total time and publish
|
/*
|
||||||
Boiler_Shower.duration = (Boiler_Shower.timerPause - Boiler_Shower.timerStart);
|
sprintf(s,
|
||||||
|
"Shower: duration %d offset %d",
|
||||||
|
(Boiler_Shower.timerPause - Boiler_Shower.timerStart),
|
||||||
|
SHOWER_OFFSET_TIME);
|
||||||
|
myDebugLog("s");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// it is over the wait period, so assume that the shower has finished and calculate the total time and publish
|
||||||
|
// because its unsigned long, can't have negative so check if length is less than OFFSET_TIME
|
||||||
|
if ((Boiler_Shower.timerPause - Boiler_Shower.timerStart) > SHOWER_OFFSET_TIME) {
|
||||||
|
Boiler_Shower.duration =
|
||||||
|
(Boiler_Shower.timerPause - Boiler_Shower.timerStart - SHOWER_OFFSET_TIME);
|
||||||
if (Boiler_Shower.duration > SHOWER_MIN_DURATION) {
|
if (Boiler_Shower.duration > SHOWER_MIN_DURATION) {
|
||||||
char s[50];
|
char s[50];
|
||||||
sprintf(s,
|
sprintf(s,
|
||||||
"%d minutes and %d seconds",
|
"%d minutes and %d seconds",
|
||||||
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
||||||
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
||||||
myDebug("Shower: finished, duration was %s\n", s);
|
|
||||||
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("Shower: finished with duration %s\n", s);
|
||||||
|
}
|
||||||
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reset
|
// reset everything
|
||||||
myDebug("Shower: resetting timers.\n");
|
myDebugLog("Shower: resetting timers");
|
||||||
Boiler_Shower.timerStart = 0;
|
Boiler_Shower.timerStart = 0;
|
||||||
Boiler_Shower.timerPause = 0;
|
Boiler_Shower.timerPause = 0;
|
||||||
_showerColdShotStop(); // turn heat back on in case its off
|
Boiler_Shower.showerOn = false;
|
||||||
}
|
_showerColdShotStop(); // turn hot water back on in case its off
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/ems.cpp
120
src/ems.cpp
@@ -15,7 +15,6 @@
|
|||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
extern void debugSend(const char * format, ...);
|
extern void debugSend(const char * format, ...);
|
||||||
#define myDebug(...) debugSend(__VA_ARGS__)
|
#define myDebug(...) debugSend(__VA_ARGS__)
|
||||||
// #define myDebug(x, ...) DEBUG_MSG(x, ##__VA_ARGS__)
|
|
||||||
#else
|
#else
|
||||||
#include <ESPHelper.h>
|
#include <ESPHelper.h>
|
||||||
extern ESPHelper myESP;
|
extern ESPHelper myESP;
|
||||||
@@ -183,9 +182,16 @@ _EMS_SYS_LOGGING ems_getLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
|
void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
|
||||||
if (loglevel <= 2) {
|
if (loglevel <= EMS_SYS_LOGGING_VERBOSE) {
|
||||||
EMS_Sys_Status.emsLogging = loglevel;
|
EMS_Sys_Status.emsLogging = loglevel;
|
||||||
myDebug("Logging is set to %d\n", EMS_Sys_Status.emsLogging);
|
myDebug("System Logging is set to ");
|
||||||
|
if (loglevel == EMS_SYS_LOGGING_BASIC) {
|
||||||
|
myDebug("Basic\n");
|
||||||
|
} else if (loglevel == EMS_SYS_LOGGING_VERBOSE) {
|
||||||
|
myDebug("Verbose\n");
|
||||||
|
} else {
|
||||||
|
myDebug("None\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +224,24 @@ uint16_t _toLong(uint8_t i, uint8_t * data) {
|
|||||||
return (((data[i]) << 16) + ((data[i + 1]) << 8) + (data[i + 2]));
|
return (((data[i]) << 16) + ((data[i + 1]) << 8) + (data[i + 2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the pointer to the EMS_Types array for a given type ID
|
||||||
|
*/
|
||||||
|
int ems_findType(uint8_t type) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
bool typeFound = false;
|
||||||
|
// scan through known ID types
|
||||||
|
while (i < MAX_TYPECALLBACK) {
|
||||||
|
if (EMS_Types[i].type == type) {
|
||||||
|
typeFound = true; // we have a match
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (typeFound ? i : -1);
|
||||||
|
}
|
||||||
|
|
||||||
// debug print a telegram to telnet console
|
// debug print a telegram to telnet console
|
||||||
// len is length in bytes including the CRC
|
// len is length in bytes including the CRC
|
||||||
void _debugPrintTelegram(const char * prefix, uint8_t * data, uint8_t len, const char * color) {
|
void _debugPrintTelegram(const char * prefix, uint8_t * data, uint8_t len, const char * color) {
|
||||||
@@ -231,8 +255,7 @@ void _debugPrintTelegram(const char * prefix, uint8_t * data, uint8_t len, const
|
|||||||
myDebug(COLOR_RED);
|
myDebug(COLOR_RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t currentTime = now();
|
myDebug("%s len=%d, data: ", prefix, len);
|
||||||
myDebug("[%02d:%02d:%02d] %s len=%d, data: ", hour(currentTime), minute(currentTime), second(currentTime), prefix, len);
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
myDebug("%02x ", data[i]);
|
myDebug("%02x ", data[i]);
|
||||||
}
|
}
|
||||||
@@ -282,7 +305,8 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
|
|||||||
myDebug("Error! no send acknowledgement. Giving up.\n");
|
myDebug("Error! no send acknowledgement. Giving up.\n");
|
||||||
_initTxBuffer();
|
_initTxBuffer();
|
||||||
} else {
|
} else {
|
||||||
myDebug("Didn't receive acknowledgement so resending (attempt #%d/%d)...\n",
|
myDebug("Didn't receive acknowledgement from the 0x%02x, so resending (attempt #%d/%d)...\n",
|
||||||
|
EMS_TxTelegram.type,
|
||||||
emsLastRxCount,
|
emsLastRxCount,
|
||||||
RX_READ_TIMEOUT_COUNT);
|
RX_READ_TIMEOUT_COUNT);
|
||||||
EMS_Sys_Status.emsTxStatus = EMS_TX_PENDING; // set to pending will trigger sending the same package again
|
EMS_Sys_Status.emsTxStatus = EMS_TX_PENDING; // set to pending will trigger sending the same package again
|
||||||
@@ -435,7 +459,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
|||||||
sprintf(s, "%s %s, type 0x%02x", src_s, dest_s, type);
|
sprintf(s, "%s %s, type 0x%02x", src_s, dest_s, type);
|
||||||
_debugPrintTelegram(s, telegram, length, color_s);
|
_debugPrintTelegram(s, telegram, length, color_s);
|
||||||
if (typeFound) {
|
if (typeFound) {
|
||||||
myDebug("--> %s(0x%02x) received.\n", EMS_Types[i].typeString, type);
|
myDebug("<--- %s(0x%02x) received\n", EMS_Types[i].typeString, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,17 +476,26 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
|||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the data at the position we wrote too
|
// look up the ID and fetch string
|
||||||
// do compare, when validating we always return a single value
|
int i = ems_findType(EMS_TxTelegram.type);
|
||||||
|
if (i != -1) {
|
||||||
|
myDebug("---> %s(0x%02x) sent with value %d at offset %d ",
|
||||||
|
EMS_Types[i].typeString,
|
||||||
|
type,
|
||||||
|
EMS_TxTelegram.checkValue,
|
||||||
|
offset);
|
||||||
|
} else {
|
||||||
|
myDebug("---> ?(0x%02x) sent with value %d at offset %d ", type, EMS_TxTelegram.checkValue, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the data at the position we wrote to and compare
|
||||||
|
// when validating we always return a single value
|
||||||
if (EMS_TxTelegram.checkValue == data[offset]) {
|
if (EMS_TxTelegram.checkValue == data[offset]) {
|
||||||
myDebug("Last write operation successful (value=%d, offset=%d)\n", EMS_TxTelegram.checkValue, offset);
|
myDebug("(successful)\n");
|
||||||
EMS_Sys_Status.emsRefreshed = true; // flag this so values are sent back to HA via MQTT
|
EMS_Sys_Status.emsRefreshed = true; // flag this so values are sent back to HA via MQTT
|
||||||
EMS_TxTelegram.action = EMS_TX_NONE; // no more sends
|
EMS_TxTelegram.action = EMS_TX_NONE; // no more sends
|
||||||
} else {
|
} else {
|
||||||
myDebug("Last write operation failed. (value=%d, got=%d, offset=%d)\n",
|
myDebug("(failed, received %d)\n", data[offset]);
|
||||||
EMS_TxTelegram.checkValue,
|
|
||||||
data[offset],
|
|
||||||
offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,7 +505,9 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
|||||||
*/
|
*/
|
||||||
bool _checkWriteQueueFull() {
|
bool _checkWriteQueueFull() {
|
||||||
if (EMS_Sys_Status.emsTxStatus == EMS_TX_PENDING) { // send is already pending
|
if (EMS_Sys_Status.emsTxStatus == EMS_TX_PENDING) { // send is already pending
|
||||||
myDebug("Cannot write - already a telegram pending send.\n");
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
|
myDebug("Holding write command as a telegram (type 0x%02x) is already in the queue\n", EMS_TxTelegram.type);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +516,7 @@ bool _checkWriteQueueFull() {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* UBAParameterWW - type 0x33 - warm water parameters
|
* UBAParameterWW - type 0x33 - warm water parameters
|
||||||
|
* received only after requested
|
||||||
*/
|
*/
|
||||||
bool _process_UBAParameterWW(uint8_t * data, uint8_t length) {
|
bool _process_UBAParameterWW(uint8_t * data, uint8_t length) {
|
||||||
EMS_Boiler.wWSelTemp = data[2];
|
EMS_Boiler.wWSelTemp = data[2];
|
||||||
@@ -495,6 +531,7 @@ bool _process_UBAParameterWW(uint8_t * data, uint8_t length) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* UBAMonitorWWMessage - type 0x34 - warm water monitor. 19 bytes long
|
* UBAMonitorWWMessage - type 0x34 - warm water monitor. 19 bytes long
|
||||||
|
* received every 10 seconds
|
||||||
*/
|
*/
|
||||||
bool _process_UBAMonitorWWMessage(uint8_t * data, uint8_t length) {
|
bool _process_UBAMonitorWWMessage(uint8_t * data, uint8_t length) {
|
||||||
EMS_Boiler.wWCurTmp = _toFloat(1, data);
|
EMS_Boiler.wWCurTmp = _toFloat(1, data);
|
||||||
@@ -507,6 +544,7 @@ bool _process_UBAMonitorWWMessage(uint8_t * data, uint8_t length) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* UBAMonitorFast - type 0x18 - central heating monitor part 1 (25 bytes long)
|
* UBAMonitorFast - type 0x18 - central heating monitor part 1 (25 bytes long)
|
||||||
|
* received every 10 seconds
|
||||||
*/
|
*/
|
||||||
bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) {
|
bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) {
|
||||||
EMS_Boiler.selFlowTemp = data[0];
|
EMS_Boiler.selFlowTemp = data[0];
|
||||||
@@ -521,7 +559,7 @@ bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) {
|
|||||||
EMS_Boiler.wWHeat = bitRead(v, 6);
|
EMS_Boiler.wWHeat = bitRead(v, 6);
|
||||||
EMS_Boiler.wWCirc = bitRead(v, 7);
|
EMS_Boiler.wWCirc = bitRead(v, 7);
|
||||||
|
|
||||||
EMS_Boiler.selBurnPow = data[3];
|
EMS_Boiler.selBurnPow = data[3]; // max power
|
||||||
EMS_Boiler.curBurnPow = data[4];
|
EMS_Boiler.curBurnPow = data[4];
|
||||||
|
|
||||||
EMS_Boiler.flameCurr = _toFloat(15, data);
|
EMS_Boiler.flameCurr = _toFloat(15, data);
|
||||||
@@ -537,6 +575,7 @@ bool _process_UBAMonitorFast(uint8_t * data, uint8_t length) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* UBAMonitorSlow - type 0x19 - central heating monitor part 2 (27 bytes long)
|
* UBAMonitorSlow - type 0x19 - central heating monitor part 2 (27 bytes long)
|
||||||
|
* received every 60 seconds
|
||||||
*/
|
*/
|
||||||
bool _process_UBAMonitorSlow(uint8_t * data, uint8_t length) {
|
bool _process_UBAMonitorSlow(uint8_t * data, uint8_t length) {
|
||||||
EMS_Boiler.extTemp = _toFloat(0, data); // 0x8000 if not available
|
EMS_Boiler.extTemp = _toFloat(0, data); // 0x8000 if not available
|
||||||
@@ -553,6 +592,7 @@ bool _process_UBAMonitorSlow(uint8_t * data, uint8_t length) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* RC20StatusMessage - type 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
|
* RC20StatusMessage - type 0x91 - data from the RC20 thermostat (0x17) - 15 bytes long
|
||||||
|
* received every 60 seconds
|
||||||
*/
|
*/
|
||||||
bool _process_RC20StatusMessage(uint8_t * data, uint8_t length) {
|
bool _process_RC20StatusMessage(uint8_t * data, uint8_t length) {
|
||||||
EMS_Thermostat.setpoint_roomTemp = ((float)data[1]) / (float)2;
|
EMS_Thermostat.setpoint_roomTemp = ((float)data[1]) / (float)2;
|
||||||
@@ -565,6 +605,7 @@ bool _process_RC20StatusMessage(uint8_t * data, uint8_t length) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* RC20Temperature - type 0xa8 - for set temp value and mode from the RC20 thermostat (0x17)
|
* RC20Temperature - type 0xa8 - for set temp value and mode from the RC20 thermostat (0x17)
|
||||||
|
* received only after requested
|
||||||
*/
|
*/
|
||||||
bool _process_RC20Temperature(uint8_t * data, uint8_t length) {
|
bool _process_RC20Temperature(uint8_t * data, uint8_t length) {
|
||||||
// check if this was called specifically to validate a single value
|
// check if this was called specifically to validate a single value
|
||||||
@@ -658,9 +699,8 @@ void _buildTxTelegram(uint8_t data_value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send a command to Tx to Read from another device
|
* Send a command to UART Tx to Read from another device
|
||||||
* Read commands when sent must to responded too by the destination (target) immediately
|
* Read commands when sent must respond by the destination (target) immediately (or within 10ms)
|
||||||
* usually within a 10ms window
|
|
||||||
*/
|
*/
|
||||||
void ems_doReadCommand(uint8_t type) {
|
void ems_doReadCommand(uint8_t type) {
|
||||||
if (type == EMS_TYPE_NONE)
|
if (type == EMS_TYPE_NONE)
|
||||||
@@ -669,28 +709,16 @@ void ems_doReadCommand(uint8_t type) {
|
|||||||
if (_checkWriteQueueFull())
|
if (_checkWriteQueueFull())
|
||||||
return; // check if there is already something in the queue
|
return; // check if there is already something in the queue
|
||||||
|
|
||||||
uint8_t dest;
|
int i = ems_findType(type);
|
||||||
|
uint8_t dest = (i == -1 ? EMS_ID_BOILER : EMS_Types[i].src); // default is Boiler
|
||||||
|
|
||||||
// scan through known types
|
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||||
bool typeFound = false;
|
if (i != -1) {
|
||||||
int i = 0;
|
|
||||||
while (i < MAX_TYPECALLBACK) {
|
|
||||||
if (EMS_Types[i].type == type) {
|
|
||||||
typeFound = true; // we have a match
|
|
||||||
// call callback to fetch the values from the telegram
|
|
||||||
dest = EMS_Types[i].src;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for adhoc calls use default values
|
|
||||||
if (!typeFound) {
|
|
||||||
dest = EMS_ID_BOILER; // default is boiler
|
|
||||||
myDebug("Requesting type (0x%02x) from dest 0x%02x\n", type, dest);
|
myDebug("Requesting type (0x%02x) from dest 0x%02x\n", type, dest);
|
||||||
} else {
|
} else {
|
||||||
myDebug("Requesting type %s(0x%02x) from dest 0x%02x\n", EMS_Types[i].typeString, type, dest);
|
myDebug("Requesting type %s(0x%02x) from dest 0x%02x\n", EMS_Types[i].typeString, type, dest);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EMS_TxTelegram.action = EMS_TX_READ; // read command
|
EMS_TxTelegram.action = EMS_TX_READ; // read command
|
||||||
EMS_TxTelegram.dest = dest | 0x80; // set 7th bit to indicate a read
|
EMS_TxTelegram.dest = dest | 0x80; // set 7th bit to indicate a read
|
||||||
@@ -784,3 +812,23 @@ void ems_setWarmWaterActivated(bool activated) {
|
|||||||
EMS_TxTelegram.checkValue = (activated ? 0xFF : 0x00);
|
EMS_TxTelegram.checkValue = (activated ? 0xFF : 0x00);
|
||||||
_buildTxTelegram(EMS_TxTelegram.checkValue);
|
_buildTxTelegram(EMS_TxTelegram.checkValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* experimental code for debugging - use with caution
|
||||||
|
*/
|
||||||
|
void ems_setExperimental(uint8_t value) {
|
||||||
|
if (_checkWriteQueueFull())
|
||||||
|
return; // check if there is already something in the queue
|
||||||
|
|
||||||
|
myDebug("Sending experimental code, value=%02x\n", value);
|
||||||
|
|
||||||
|
EMS_TxTelegram.action = EMS_TX_WRITE;
|
||||||
|
EMS_TxTelegram.dest = EMS_ID_BOILER;
|
||||||
|
EMS_TxTelegram.type = EMS_TYPE_UBAParameterWW;
|
||||||
|
EMS_TxTelegram.offset = 6;
|
||||||
|
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
|
||||||
|
EMS_TxTelegram.type_validate = EMS_ID_NONE; // don't force a send to check the value but do it during next broadcast
|
||||||
|
|
||||||
|
EMS_TxTelegram.checkValue = value;
|
||||||
|
_buildTxTelegram(value);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user