mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 00:39:50 +03:00
Version 1.1. See ChangeLog
This commit is contained in:
@@ -70,7 +70,7 @@ ESPHelper::ESPHelper(netInfo * startingNet) {
|
||||
//start the wifi & mqtt systems and attempt connection (currently blocking)
|
||||
//true on: parameter check validated
|
||||
//false on: parameter check failed
|
||||
bool ESPHelper::begin(const char * hostname) {
|
||||
bool ESPHelper::begin(const char * hostname, const char * app_name, const char * app_version) {
|
||||
#ifdef USE_SERIAL1
|
||||
Serial1.begin(115200);
|
||||
Serial1.setDebugOutput(true);
|
||||
@@ -85,6 +85,10 @@ bool ESPHelper::begin(const char * hostname) {
|
||||
strcpy(_hostname, hostname);
|
||||
OTA_enable();
|
||||
|
||||
strcpy(_app_name, app_name); // app name
|
||||
strcpy(_app_version, app_version); // app version
|
||||
|
||||
|
||||
setBoottime("<unknown>");
|
||||
|
||||
if (_ssidSet) {
|
||||
@@ -176,7 +180,7 @@ bool ESPHelper::begin(const char * hostname) {
|
||||
|
||||
consoleShowHelp(); // show this at bootup
|
||||
|
||||
//mark the system as started and return
|
||||
// mark the system as started and return
|
||||
_hasBegun = true;
|
||||
|
||||
return true;
|
||||
@@ -341,6 +345,12 @@ void ESPHelper::setWifiCallback(void (*callback)()) {
|
||||
_wifiCallbackSet = true;
|
||||
}
|
||||
|
||||
//sets a custom function to run when telnet is started
|
||||
void ESPHelper::setInitCallback(void (*callback)()) {
|
||||
_initCallback = callback;
|
||||
_initCallbackSet = true;
|
||||
}
|
||||
|
||||
//attempts to connect to wifi & mqtt server if not connected
|
||||
void ESPHelper::reconnect() {
|
||||
static uint8_t tryCount = 0;
|
||||
@@ -614,6 +624,8 @@ void ESPHelper::consoleHandle() {
|
||||
// Show the initial message
|
||||
consoleShowHelp();
|
||||
|
||||
_initCallback(); // call callback to set any custom things
|
||||
|
||||
// Empty buffer
|
||||
while (telnetClient.available()) {
|
||||
telnetClient.read();
|
||||
@@ -655,7 +667,7 @@ void ESPHelper::consoleHandle() {
|
||||
|
||||
// Inactivity - close connection if not received commands from user in telnet to reduce overheads
|
||||
if ((millis() - _lastTimeCommand) > MAX_TIME_INACTIVE) {
|
||||
telnetClient.println("* Closing session due to inactivity");
|
||||
telnetClient.println("* Closing telnet session due to inactivity");
|
||||
telnetClient.flush();
|
||||
telnetClient.stop();
|
||||
_telnetConnected = false;
|
||||
@@ -749,6 +761,10 @@ void ESPHelper::consoleShowHelp() {
|
||||
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
||||
help += "* Boot time: ";
|
||||
help.concat(_boottime);
|
||||
help += "\n\r* ";
|
||||
help.concat(_app_name);
|
||||
help += " Version ";
|
||||
help.concat(_app_version);
|
||||
help += "\n\r* Free RAM: ";
|
||||
help.concat(ESP.getFreeHeap());
|
||||
help += " bytes\n\r";
|
||||
|
||||
@@ -90,7 +90,8 @@ class ESPHelper : public Print {
|
||||
|
||||
ESPHelper(netInfo * startingNet);
|
||||
|
||||
bool begin(const char * hostname);
|
||||
bool begin(const char * hostname, const char * app_name, const char * app_version);
|
||||
|
||||
void end();
|
||||
|
||||
void useSecureClient(const char * fingerprint);
|
||||
@@ -110,6 +111,7 @@ class ESPHelper : public Print {
|
||||
void setMQTTCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
|
||||
void setWifiCallback(void (*callback)());
|
||||
void setInitCallback(void (*callback)());
|
||||
|
||||
void sendHACommand(const char * s);
|
||||
void sendStart();
|
||||
@@ -162,6 +164,8 @@ class ESPHelper : public Print {
|
||||
char _clientName[40];
|
||||
void (*_wifiCallback)();
|
||||
bool _wifiCallbackSet = false;
|
||||
void (*_initCallback)();
|
||||
bool _initCallbackSet = false;
|
||||
|
||||
std::function<void(char *, uint8_t *, uint8_t)> _mqttCallback;
|
||||
|
||||
@@ -181,7 +185,7 @@ class ESPHelper : public Print {
|
||||
netInfo ** _netList;
|
||||
bool _verboseMessages = true;
|
||||
subscription _subscriptions[MAX_SUBSCRIPTIONS];
|
||||
char _hostname[64];
|
||||
char _hostname[24];
|
||||
uint8_t _qos = DEFAULT_QOS;
|
||||
IPAddress _apIP = IPAddress(192, 168, 1, 254);
|
||||
void changeNetwork();
|
||||
@@ -190,7 +194,9 @@ class ESPHelper : public Print {
|
||||
void resubscribe();
|
||||
uint8_t setConnectionStatus();
|
||||
|
||||
char _boottime[50];
|
||||
char _boottime[24];
|
||||
char _app_name[24];
|
||||
char _app_version[10];
|
||||
|
||||
// console/telnet specific
|
||||
WiFiClient telnetClient;
|
||||
|
||||
493
src/boiler.ino
493
src/boiler.ino
@@ -11,6 +11,7 @@
|
||||
#include "ems.h"
|
||||
#include "emsuart.h"
|
||||
#include "my_config.h"
|
||||
#include "version.h"
|
||||
|
||||
// public libraries
|
||||
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
||||
@@ -20,13 +21,13 @@
|
||||
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
|
||||
|
||||
// timers, all values are in seconds
|
||||
#define PUBLISHVALUES_TIME 300 // every 5 mins post HA values
|
||||
#define PUBLISHVALUES_TIME 120 // every 2 minutes post HA values
|
||||
Ticker publishValuesTimer;
|
||||
|
||||
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
|
||||
Ticker systemCheckTimer;
|
||||
|
||||
#define REGULARUPDATES_TIME 60 // every minute a call is made, so for our 2 calls theres a write cmd every 30seconds
|
||||
#define REGULARUPDATES_TIME 60 // every minute a call is made
|
||||
Ticker regularUpdatesTimer;
|
||||
|
||||
#define HEARTBEAT_TIME 1 // every second blink heartbeat LED
|
||||
@@ -37,11 +38,7 @@ Ticker scanThermostat;
|
||||
#define SCANTHERMOSTAT_TIME 4
|
||||
uint8_t scanThermostat_count;
|
||||
|
||||
Ticker showerColdShotStopTimer;
|
||||
uint8_t regularUpdatesCount = 0;
|
||||
|
||||
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
|
||||
|
||||
Ticker showerColdShotStopTimer;
|
||||
|
||||
// GPIOs
|
||||
#define LED_HEARTBEAT LED_BUILTIN // onboard LED
|
||||
@@ -62,11 +59,9 @@ uint8_t regularUpdatesCount = 0;
|
||||
#define TOPIC_THERMOSTAT_MODE "thermostat_mode" // mode
|
||||
|
||||
// boiler
|
||||
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
||||
#define TOPIC_BOILER_ MQTT_BOILER "boiler_wwtemp" // warm water selected temp
|
||||
#define TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE MQTT_BOILER "boiler_wwtemp" // warm water selected temp
|
||||
#define TOPIC_BOILER_TAPWATER_ACTIVE MQTT_BOILER "tapwater_active" // if hot tap water is running
|
||||
#define TOPIC_BOILER_HEATING_ACTIVE MQTT_BOILER "heating_active" // if heating is on
|
||||
#define TOPIC_BOILER_DATA MQTT_BOILER "boiler_data" // for sending boiler values
|
||||
#define TOPIC_BOILER_TAPWATER_ACTIVE MQTT_BOILER "tapwater_active" // if hot tap water is running
|
||||
#define TOPIC_BOILER_HEATING_ACTIVE MQTT_BOILER "heating_active" // if heating is on
|
||||
|
||||
// shower time
|
||||
#define TOPIC_SHOWERTIME MQTT_BOILER "showertime" // for sending shower time results
|
||||
@@ -94,8 +89,6 @@ const unsigned long SHOWER_OFFSET_TIME = 0; // 0 seconds grace time, to ca
|
||||
|
||||
typedef struct {
|
||||
bool wifi_connected;
|
||||
bool boiler_online;
|
||||
bool thermostat_enabled;
|
||||
bool shower_timer; // true if we want to report back on shower times
|
||||
bool shower_alert; // true if we want the cold water reminder
|
||||
} _Boiler_Status;
|
||||
@@ -109,31 +102,33 @@ typedef struct {
|
||||
} _Boiler_Shower;
|
||||
|
||||
// ESPHelper
|
||||
netInfo homeNet = {.mqttHost = MQTT_IP,
|
||||
netInfo homeNet = {.mqttHost = MQTT_IP,
|
||||
.mqttUser = MQTT_USER,
|
||||
.mqttPass = MQTT_PASS,
|
||||
.mqttPort = 1883, // this is the default, change if using another port
|
||||
.ssid = WIFI_SSID,
|
||||
.pass = WIFI_PASSWORD};
|
||||
|
||||
ESPHelper myESP(&homeNet);
|
||||
|
||||
command_t PROGMEM project_cmds[] = {
|
||||
|
||||
{"v [n]", "set logging (0=none, 1=basic, 2=thermostat only, 3=verbose)"},
|
||||
{"l [n]", "set logging (0=none, 1=raw, 2=basic, 3=thermostat only, 4=verbose)"},
|
||||
{"s", "show statistics"},
|
||||
{"h", "list supported EMS telegram type IDs"},
|
||||
{"P", "publish all stat to MQTT"},
|
||||
{"p", "toggle EMS Poll response on/off"},
|
||||
{"M", "publish to MQTT"},
|
||||
{"Q", "print Tx Queue"},
|
||||
{"P", "toggle EMS Poll response on/off"},
|
||||
{"X", "toggle EMS Tx transmission on/off"},
|
||||
{"S", "toggle Shower timer on/off"},
|
||||
{"A", "toggle shower Alert on/off"},
|
||||
{"b [xx]", "boiler request (xx=telegram type ID)"},
|
||||
{"r [s]", "send raw telegram to EMS (s=XX XX XX...)"},
|
||||
{"b [xx]", "send boiler read request (xx=telegram type ID in hex)"},
|
||||
{"t [xx]", "send thermostat read request (xx=telegram type ID in hex)"},
|
||||
{"w [nn]", "set boiler warm water temperature (min 30)"},
|
||||
{"a [n]", "boiler warm water (1=on, 2=off)"},
|
||||
{"t [xx]", "thermostat request (xx=telegram type ID)"},
|
||||
{"a [n]", "set boiler warm tap water (0=off, 1=on)"},
|
||||
{"T [xx]", "set thermostat temperature"},
|
||||
{"m [n]", "set thermostat mode (1=manual, 2=auto)"},
|
||||
{"x [xx]", "experimental code for debugging."}
|
||||
{"m [n]", "set thermostat mode (1=manual, 2=auto)"}
|
||||
//{"U [c]", "do a thermostat scan on all ids (c=start id) for debugging only"}
|
||||
|
||||
};
|
||||
|
||||
@@ -160,6 +155,7 @@ const unsigned long TX_HOLD_LED_TIME = 2000; // how long to hold the Tx LED bec
|
||||
|
||||
unsigned long timestamp; // for internal timings, via millis()
|
||||
static int connectionStatus = NO_CONNECTION;
|
||||
int boilerStatus = false;
|
||||
bool startMQTTsent = false;
|
||||
|
||||
uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
|
||||
@@ -172,18 +168,14 @@ 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
|
||||
//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};
|
||||
|
||||
char * ret = a;
|
||||
// check for 0x8000 (sensor missing), which has a -1 value
|
||||
// check for 0x8000 (sensor missing)
|
||||
if (f == EMS_VALUE_FLOAT_NOTSET) {
|
||||
strcpy(ret, "?");
|
||||
} else {
|
||||
@@ -272,58 +264,20 @@ void showInfo() {
|
||||
|
||||
myDebug("\n # EMS type handlers: %d\n", ems_getEmsTypesCount());
|
||||
|
||||
myDebug(" Thermostat is %s, Poll is %s, Shower timer is %s, Shower alert is %s\n",
|
||||
((Boiler_Status.thermostat_enabled) ? "enabled" : "disabled"),
|
||||
myDebug(" Thermostat is %s, Poll is %s, Tx is %s, Shower Timer is %s, Shower Alert is %s\n",
|
||||
(ems_getThermostatEnabled() ? "enabled" : "disabled"),
|
||||
((EMS_Sys_Status.emsPollEnabled) ? "enabled" : "disabled"),
|
||||
((EMS_Sys_Status.emsTxEnabled) ? "enabled" : "disabled"),
|
||||
((Boiler_Status.shower_timer) ? "enabled" : "disabled"),
|
||||
((Boiler_Status.shower_alert) ? "enabled" : "disabled"));
|
||||
|
||||
myDebug(" EMS Bus Stats: Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d, ",
|
||||
(Boiler_Status.boiler_online ? "yes" : "no"),
|
||||
myDebug(" EMS Bus Stats: Connected=%s, # Rx telegrams=%d, # Tx telegrams=%d, # Crc Errors=%d\n",
|
||||
(ems_getBoilerEnabled() ? "yes" : "no"),
|
||||
EMS_Sys_Status.emsRxPgks,
|
||||
EMS_Sys_Status.emsTxPkgs,
|
||||
EMS_Sys_Status.emxCrcErr);
|
||||
|
||||
myDebug("Rx Status=");
|
||||
switch (EMS_Sys_Status.emsRxStatus) {
|
||||
case EMS_RX_IDLE:
|
||||
myDebug("idle");
|
||||
break;
|
||||
case EMS_RX_ACTIVE:
|
||||
myDebug("active");
|
||||
break;
|
||||
}
|
||||
|
||||
myDebug(", Tx Status=");
|
||||
switch (EMS_Sys_Status.emsTxStatus) {
|
||||
case EMS_TX_IDLE:
|
||||
myDebug("idle");
|
||||
break;
|
||||
case EMS_TX_PENDING:
|
||||
myDebug("pending");
|
||||
break;
|
||||
case EMS_TX_ACTIVE:
|
||||
myDebug("active");
|
||||
break;
|
||||
}
|
||||
|
||||
myDebug(", Last Tx Action=");
|
||||
switch (EMS_TxTelegram.action) {
|
||||
case EMS_TX_READ:
|
||||
myDebug("read");
|
||||
break;
|
||||
case EMS_TX_WRITE:
|
||||
myDebug("write");
|
||||
break;
|
||||
case EMS_TX_VALIDATE:
|
||||
myDebug("validate");
|
||||
break;
|
||||
case EMS_TX_NONE:
|
||||
myDebug("none");
|
||||
break;
|
||||
}
|
||||
|
||||
myDebug("\n\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug("\n%sBoiler stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
|
||||
// active stats
|
||||
myDebug(" Hot tap water is %s\n", (EMS_Boiler.tapwaterActive ? "running" : "off"));
|
||||
@@ -373,15 +327,22 @@ void showInfo() {
|
||||
EMS_Boiler.heatWorkMin % 60);
|
||||
|
||||
// Thermostat stats
|
||||
if (Boiler_Status.thermostat_enabled) {
|
||||
if (ems_getThermostatEnabled()) {
|
||||
myDebug("\n%sThermostat stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug(" Thermostat time is %02d:%02d:%02d %d/%d/%d\n",
|
||||
EMS_Thermostat.hour,
|
||||
EMS_Thermostat.minute,
|
||||
EMS_Thermostat.second,
|
||||
EMS_Thermostat.day,
|
||||
EMS_Thermostat.month,
|
||||
EMS_Thermostat.year + 2000);
|
||||
myDebug(" Thermostat type: ");
|
||||
ems_printThermostatType();
|
||||
myDebug("\n Thermostat time is ");
|
||||
if (EMS_ID_THERMOSTAT != EMS_ID_THERMOSTAT_EASY) {
|
||||
myDebug("%02d:%02d:%02d %d/%d/%d\n",
|
||||
EMS_Thermostat.hour,
|
||||
EMS_Thermostat.minute,
|
||||
EMS_Thermostat.second,
|
||||
EMS_Thermostat.day,
|
||||
EMS_Thermostat.month,
|
||||
EMS_Thermostat.year + 2000);
|
||||
} else {
|
||||
myDebug("<not supported>\n");
|
||||
}
|
||||
|
||||
_renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp);
|
||||
_renderFloatValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp);
|
||||
@@ -444,11 +405,9 @@ void publishValues(bool force) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
uint32_t checksum = crc.finalize();
|
||||
//myDebug("Boiler HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data);
|
||||
|
||||
if ((previousBoilerPublishCRC != checksum) || force) {
|
||||
previousBoilerPublishCRC = checksum;
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing boiler data via MQTT\n");
|
||||
}
|
||||
|
||||
@@ -458,8 +417,8 @@ void publishValues(bool force) {
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
if (last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) {
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
if ((last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) || force) {
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing hot water and heating state via MQTT\n");
|
||||
}
|
||||
myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0");
|
||||
@@ -468,9 +427,8 @@ void publishValues(bool force) {
|
||||
last_boilerActive = ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive); // remember last state
|
||||
}
|
||||
|
||||
|
||||
// handle the thermostat values separately
|
||||
if (EMS_Sys_Status.emsThermostatEnabled) {
|
||||
if (ems_getThermostatEnabled()) {
|
||||
// only send thermostat values if we actually have them
|
||||
if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0))
|
||||
return;
|
||||
@@ -498,11 +456,9 @@ void publishValues(bool force) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
uint32_t checksum = crc.finalize();
|
||||
//myDebug("Thermostat HASH=%d %08x, len=%d, s=%s\n", checksum, checksum, len, data);
|
||||
|
||||
if ((previousThermostatPublishCRC != checksum) || force) {
|
||||
previousThermostatPublishCRC = checksum;
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
if (ems_getLogging() >= EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Publishing thermostat data via MQTT\n");
|
||||
}
|
||||
|
||||
@@ -538,11 +494,15 @@ void myDebugCallback() {
|
||||
case 's':
|
||||
showInfo();
|
||||
break;
|
||||
case 'p':
|
||||
case 'P': // toggle Poll
|
||||
b = !ems_getPoll();
|
||||
ems_setPoll(b);
|
||||
break;
|
||||
case 'P':
|
||||
case 'X': // toggle Tx
|
||||
b = !ems_getTxEnabled();
|
||||
ems_setTxEnabled(b);
|
||||
break;
|
||||
case 'M':
|
||||
//myESP.logger(LOG_HA, "Force publish values");
|
||||
publishValues(true);
|
||||
break;
|
||||
@@ -557,6 +517,9 @@ void myDebugCallback() {
|
||||
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
|
||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||
break;
|
||||
case 'Q': //print Tx Queue
|
||||
ems_printTxQueue();
|
||||
break;
|
||||
default:
|
||||
myDebug("Unknown command. Use ? for help.\n");
|
||||
break;
|
||||
@@ -564,9 +527,6 @@ void myDebugCallback() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 2)
|
||||
return;
|
||||
|
||||
// for commands with parameters, assume command is just one letter
|
||||
switch (cmd[0]) {
|
||||
case 'T': // set thermostat temp
|
||||
@@ -581,15 +541,15 @@ void myDebugCallback() {
|
||||
case 'w': // set warm water temp
|
||||
ems_setWarmWaterTemp((uint8_t)strtol(&cmd[2], 0, 10));
|
||||
break;
|
||||
case 'v': // verbose
|
||||
case 'l': // logging
|
||||
ems_setLogging((_EMS_SYS_LOGGING)(cmd[2] - '0'));
|
||||
updateHeartbeat();
|
||||
break;
|
||||
case 'a': // set ww activate on or off
|
||||
if ((cmd[2] - '0') == 1)
|
||||
ems_setWarmWaterActivated(true);
|
||||
ems_setWarmTapWaterActivated(true);
|
||||
else if ((cmd[2] - '0') == 0)
|
||||
ems_setWarmWaterActivated(false);
|
||||
ems_setWarmTapWaterActivated(false);
|
||||
break;
|
||||
case 'b': // boiler read command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_BOILER);
|
||||
@@ -597,13 +557,16 @@ void myDebugCallback() {
|
||||
case 't': // thermostat command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_THERMOSTAT);
|
||||
break;
|
||||
case 'r': // send raw data
|
||||
ems_sendRawTelegram(&cmd[2]);
|
||||
break;
|
||||
case 'x': // experimental, not displayed!
|
||||
myDebug("Calling experimental...\n");
|
||||
ems_setLogging(EMS_SYS_LOGGING_VERBOSE);
|
||||
ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param
|
||||
break;
|
||||
case 'U': // thermostat scan
|
||||
myDebug("Doing a scan on thermostat IDs\n");
|
||||
myDebug("Doing a type ID scan on thermostat...\n");
|
||||
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
|
||||
publishValuesTimer.detach();
|
||||
systemCheckTimer.detach();
|
||||
@@ -651,18 +614,6 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
||||
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);
|
||||
#ifndef NO_TX
|
||||
ems_setWarmWaterTemp(i);
|
||||
#endif
|
||||
// publish back so HA is immediately updated
|
||||
publishValues(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// shower timer
|
||||
if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) {
|
||||
if (payload[0] == '1') {
|
||||
@@ -687,9 +638,7 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
||||
|
||||
// shower cold shot
|
||||
if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) {
|
||||
if (Boiler_Status.shower_alert) {
|
||||
_showerColdShotStart();
|
||||
}
|
||||
_showerColdShotStart();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -703,15 +652,16 @@ void MQTTcallback(char * topic, byte * payload, uint8_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
// Init callback, which is used to set functions and call methods when telnet has started
|
||||
void InitCallback() {
|
||||
ems_setLogging(BOILER_DEFAULT_LOGGING); // turn off logging as default startup
|
||||
}
|
||||
|
||||
// WifiCallback, called when a WiFi connect has successfully been established
|
||||
void WIFIcallback() {
|
||||
Boiler_Status.wifi_connected = true;
|
||||
|
||||
#ifdef USE_LED
|
||||
// turn off the LEDs since we've finished the boot loading
|
||||
digitalWrite(LED_RX, LOW);
|
||||
digitalWrite(LED_TX, LOW);
|
||||
digitalWrite(LED_ERR, LOW);
|
||||
digitalWrite(LED_HEARTBEAT, HIGH);
|
||||
#endif
|
||||
|
||||
@@ -727,23 +677,20 @@ void updateHeartbeat() {
|
||||
} else {
|
||||
heartbeatEnabled = false;
|
||||
#ifdef USE_LED
|
||||
// ...and turn off LED
|
||||
digitalWrite(LED_HEARTBEAT, HIGH);
|
||||
digitalWrite(LED_HEARTBEAT, HIGH); // ...and turn off LED
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the boiler settings
|
||||
void _initBoiler() {
|
||||
void initBoiler() {
|
||||
// default settings
|
||||
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
||||
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
|
||||
Boiler_Status.thermostat_enabled = BOILER_THERMOSTAT_ENABLED;
|
||||
ems_setThermostatEnabled(Boiler_Status.thermostat_enabled);
|
||||
Boiler_Status.shower_timer = BOILER_SHOWER_TIMER;
|
||||
Boiler_Status.shower_alert = BOILER_SHOWER_ALERT;
|
||||
ems_setThermostatEnabled(BOILER_THERMOSTAT_ENABLED);
|
||||
|
||||
// init boiler
|
||||
Boiler_Status.wifi_connected = false;
|
||||
Boiler_Status.boiler_online = false;
|
||||
|
||||
// init shower
|
||||
Boiler_Shower.timerStart = 0;
|
||||
@@ -768,13 +715,7 @@ void do_publishValues() {
|
||||
void setup() {
|
||||
#ifdef USE_LED
|
||||
// set pin for LEDs - start up with all lit up while we sort stuff out
|
||||
pinMode(LED_RX, OUTPUT);
|
||||
pinMode(LED_TX, OUTPUT);
|
||||
pinMode(LED_ERR, OUTPUT);
|
||||
pinMode(LED_HEARTBEAT, OUTPUT);
|
||||
digitalWrite(LED_RX, HIGH);
|
||||
digitalWrite(LED_TX, HIGH);
|
||||
digitalWrite(LED_ERR, HIGH);
|
||||
digitalWrite(LED_HEARTBEAT, LOW); // onboard LED is on
|
||||
heartbeatTimer.attach(HEARTBEAT_TIME, heartbeat); // blink heartbeat LED
|
||||
#endif
|
||||
@@ -782,10 +723,7 @@ void setup() {
|
||||
// Timers using Ticker library
|
||||
publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post HA values
|
||||
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if Boiler is online
|
||||
|
||||
#ifndef NO_TX
|
||||
regularUpdatesTimer.attach((REGULARUPDATES_TIME / MAX_MANUAL_CALLS), regularUpdates); // regular reads from the EMS
|
||||
#endif
|
||||
regularUpdatesTimer.attach(REGULARUPDATES_TIME, regularUpdates); // regular reads from the EMS
|
||||
|
||||
// set up WiFi
|
||||
myESP.setWifiCallback(WIFIcallback);
|
||||
@@ -798,42 +736,20 @@ void setup() {
|
||||
myESP.addSubscription(TOPIC_THERMOSTAT_CMD_MODE);
|
||||
myESP.addSubscription(TOPIC_SHOWER_TIMER);
|
||||
myESP.addSubscription(TOPIC_SHOWER_ALERT);
|
||||
myESP.addSubscription(TOPIC_BOILER_WARM_WATER_SELECTED_TEMPERATURE);
|
||||
myESP.addSubscription(TOPIC_BOILER_TAPWATER_ACTIVE);
|
||||
myESP.addSubscription(TOPIC_BOILER_HEATING_ACTIVE);
|
||||
myESP.addSubscription(TOPIC_SHOWER_COLDSHOT);
|
||||
|
||||
myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands
|
||||
myESP.begin(HOSTNAME); // start wifi and mqtt services
|
||||
myESP.setInitCallback(InitCallback);
|
||||
|
||||
// init ems stats
|
||||
myESP.consoleSetCallBackProjectCmds(project_cmds, ArraySize(project_cmds), myDebugCallback); // set up Telnet commands
|
||||
myESP.begin(HOSTNAME, APP_NAME, APP_VERSION); // start wifi and mqtt services
|
||||
|
||||
// init ems statisitcs
|
||||
ems_init();
|
||||
|
||||
// init Boiler specific params
|
||||
_initBoiler();
|
||||
}
|
||||
|
||||
// flash LEDs
|
||||
// Using a faster way to write to pins as digitalWrite does a lot of overhead like pin checking & disabling interrupts
|
||||
void showLEDs() {
|
||||
#ifdef USE_LED
|
||||
// ERR LED
|
||||
if (!Boiler_Status.boiler_online) {
|
||||
WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 4, (1 << LED_ERR)); // turn on
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_IDLE;
|
||||
EMS_Sys_Status.emsTxStatus = EMS_TX_IDLE;
|
||||
} else {
|
||||
WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + 8, (1 << LED_ERR)); // turn off
|
||||
}
|
||||
|
||||
// Rx LED
|
||||
WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + ((EMS_Sys_Status.emsRxStatus == EMS_RX_IDLE) ? 8 : 4), (1 << LED_RX));
|
||||
|
||||
// Tx LED
|
||||
// because sends are quick, if we did a recent send show the LED for a short while
|
||||
uint64_t t = (timestamp - EMS_Sys_Status.emsLastTx);
|
||||
WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + ((t < TX_HOLD_LED_TIME) ? 4 : 8), (1 << LED_TX));
|
||||
#endif
|
||||
// init Boiler specific parameters
|
||||
initBoiler();
|
||||
}
|
||||
|
||||
// heartbeat callback to light up the LED, called via Ticker
|
||||
@@ -856,33 +772,37 @@ void do_scanThermostat() {
|
||||
// do a healthcheck every now and then to see if we connections
|
||||
void do_systemCheck() {
|
||||
// first do a system check to see if there is still a connection to the EMS
|
||||
Boiler_Status.boiler_online = ((timestamp - EMS_Sys_Status.emsLastPoll) < POLL_TIMEOUT_ERR);
|
||||
if (!Boiler_Status.boiler_online) {
|
||||
myDebug("Error! Unable to connect to EMS bus. Please check connections. Retry in 10 seconds...\n");
|
||||
if (!ems_getBoilerEnabled()) {
|
||||
myDebug("Error! Unable to connect to EMS bus. Please check connections. Retry in %d seconds...\n",
|
||||
SYSTEMCHECK_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
// EMS telegrams to send after startup
|
||||
void firstTimeFetch() {
|
||||
ems_doReadCommand(EMS_TYPE_UBAMonitorFast, EMS_ID_BOILER); // get boiler stats which usually comes every 10 sec
|
||||
ems_doReadCommand(EMS_TYPE_UBAMonitorSlow, EMS_ID_BOILER); // get boiler stats which usually comes every 60 sec
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
|
||||
if (ems_getThermostatEnabled()) {
|
||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
||||
ems_doReadCommand(EMS_TYPE_RCTime, EMS_ID_THERMOSTAT); // get Thermostat time
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// it's done as a cycle to prevent collisions, since we can only do 1 read command at a time
|
||||
void regularUpdates() {
|
||||
uint8_t cycle = (regularUpdatesCount++ % MAX_MANUAL_CALLS);
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
|
||||
// only do calls if the EMS is connected and alive
|
||||
if (Boiler_Status.boiler_online) {
|
||||
if ((cycle == 0) && Boiler_Status.thermostat_enabled) {
|
||||
// force get the thermostat data which are not usually automatically broadcasted
|
||||
ems_getThermostatTemps();
|
||||
} else if (cycle == 1) {
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
}
|
||||
if (ems_getThermostatEnabled()) {
|
||||
ems_getThermostatValues(); // get Thermostat temps (if supported)
|
||||
}
|
||||
}
|
||||
|
||||
// turn off hot water to send a shot of cold
|
||||
void _showerColdShotStart() {
|
||||
myDebugLog("Shower: doing a shot of cold");
|
||||
ems_setWarmWaterActivated(false);
|
||||
ems_setWarmTapWaterActivated(false);
|
||||
Boiler_Shower.doingColdShot = true;
|
||||
// start the timer for n seconds which will reset the water back to hot
|
||||
showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
|
||||
@@ -892,12 +812,101 @@ void _showerColdShotStart() {
|
||||
void _showerColdShotStop() {
|
||||
if (Boiler_Shower.doingColdShot) {
|
||||
myDebugLog("Shower: finished shot of cold. hot water back on");
|
||||
ems_setWarmWaterActivated(true);
|
||||
ems_setWarmTapWaterActivated(true);
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
showerColdShotStopTimer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Shower Logic
|
||||
*/
|
||||
void showerCheck() {
|
||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
||||
if (!Boiler_Shower.doingColdShot) {
|
||||
// is the hot water running?
|
||||
if (EMS_Boiler.tapwaterActive) {
|
||||
// if heater was previously off, start the timer
|
||||
if (Boiler_Shower.timerStart == 0) {
|
||||
// hot water just started...
|
||||
Boiler_Shower.timerStart = timestamp;
|
||||
Boiler_Shower.timerPause = 0; // remove any last pauses
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
Boiler_Shower.duration = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water on...");
|
||||
#endif
|
||||
} 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;
|
||||
#ifdef SHOWER_TEST
|
||||
|
||||
myDebugLog("Shower: hot water still running, starting shower timer");
|
||||
#endif
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) && !Boiler_Shower.doingColdShot)
|
||||
&& Boiler_Status.shower_alert) {
|
||||
myESP.sendHACommand(TOPIC_SHOWER_ALARM);
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: exceeded max shower time");
|
||||
#endif
|
||||
_showerColdShotStart();
|
||||
}
|
||||
}
|
||||
} else { // hot water is off
|
||||
// if it just turned off, record the time as it could be a short pause
|
||||
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
||||
Boiler_Shower.timerPause = timestamp;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water turned off");
|
||||
#endif
|
||||
}
|
||||
|
||||
// if shower has been off for longer than the wait time
|
||||
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
||||
/*
|
||||
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) {
|
||||
char s[50];
|
||||
sprintf(s,
|
||||
"%d minutes and %d seconds",
|
||||
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
||||
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
||||
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Shower: finished with duration %s\n", s);
|
||||
}
|
||||
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHOWER_TEST
|
||||
// reset everything
|
||||
myDebugLog("Shower: resetting timers");
|
||||
#endif
|
||||
Boiler_Shower.timerStart = 0;
|
||||
Boiler_Shower.timerPause = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
_showerColdShotStop(); // turn hot water back on in case its off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Main loop
|
||||
//
|
||||
@@ -915,7 +924,7 @@ void loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// if first time connected to MQTT, send welcome start message
|
||||
// if this is the first time we've connected to MQTT, send a welcome start message
|
||||
// which will send all the state values from HA back to the clock via MQTT and return the boottime
|
||||
if ((!startMQTTsent) && (connectionStatus == FULL_CONNECTION)) {
|
||||
myESP.sendStart();
|
||||
@@ -924,116 +933,24 @@ void loop() {
|
||||
// publish to HA the status of the Shower parameters
|
||||
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0");
|
||||
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
|
||||
|
||||
#ifndef NO_TX
|
||||
if (Boiler_Status.boiler_online) {
|
||||
// now that we're connected lets get some data from the EMS
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER);
|
||||
ems_setWarmWaterActivated(true); // make sure warm water if activated, in case it got stuck with the shower alert
|
||||
} else {
|
||||
myDebugLog("Boot: can't connect to EMS.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// publish the values to MQTT (only if there are changes)
|
||||
// if we received new data and flagged for pushing, do it
|
||||
if (EMS_Sys_Status.emsRefreshed) {
|
||||
EMS_Sys_Status.emsRefreshed = false;
|
||||
publishValues(false);
|
||||
// if the EMS bus has just connected, send a request to fetch some initial values
|
||||
if (ems_getBoilerEnabled() && boilerStatus == false) {
|
||||
boilerStatus = true;
|
||||
firstTimeFetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* Shower Logic
|
||||
*/
|
||||
// publish the values to MQTT, regardless if the values haven't changed
|
||||
if (ems_getEmsRefreshed()) {
|
||||
publishValues(true);
|
||||
ems_setEmsRefreshed(false);
|
||||
}
|
||||
|
||||
// do shower logic if its enabled
|
||||
if (Boiler_Status.shower_timer) {
|
||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
||||
if (!Boiler_Shower.doingColdShot) {
|
||||
// is the hot water running?
|
||||
if (EMS_Boiler.tapwaterActive) {
|
||||
// if heater was previously off, start the timer
|
||||
if (Boiler_Shower.timerStart == 0) {
|
||||
// hot water just started...
|
||||
Boiler_Shower.timerStart = timestamp;
|
||||
Boiler_Shower.timerPause = 0; // remove any last pauses
|
||||
Boiler_Shower.doingColdShot = false;
|
||||
Boiler_Shower.duration = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water on...");
|
||||
#endif
|
||||
} 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;
|
||||
#ifdef SHOWER_TEST
|
||||
|
||||
myDebugLog("Shower: hot water still running, starting shower timer");
|
||||
#endif
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION)
|
||||
&& !Boiler_Shower.doingColdShot)
|
||||
&& Boiler_Status.shower_alert) {
|
||||
myESP.sendHACommand(TOPIC_SHOWER_ALARM);
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: exceeded max shower time");
|
||||
#endif
|
||||
_showerColdShotStart();
|
||||
}
|
||||
}
|
||||
} else { // hot water is off
|
||||
// if it just turned off, record the time as it could be a short pause
|
||||
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
|
||||
Boiler_Shower.timerPause = timestamp;
|
||||
#ifdef SHOWER_TEST
|
||||
myDebugLog("Shower: hot water turned off");
|
||||
#endif
|
||||
}
|
||||
|
||||
// if shower has been off for longer than the wait time
|
||||
if ((Boiler_Shower.timerPause != 0) && ((timestamp - Boiler_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
|
||||
/*
|
||||
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) {
|
||||
char s[50];
|
||||
sprintf(s,
|
||||
"%d minutes and %d seconds",
|
||||
(uint8_t)((Boiler_Shower.duration / (1000 * 60)) % 60),
|
||||
(uint8_t)((Boiler_Shower.duration / 1000) % 60));
|
||||
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Shower: finished with duration %s\n", s);
|
||||
}
|
||||
myESP.publish(TOPIC_SHOWERTIME, s); // publish to HA
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHOWER_TEST
|
||||
// reset everything
|
||||
myDebugLog("Shower: resetting timers");
|
||||
#endif
|
||||
Boiler_Shower.timerStart = 0;
|
||||
Boiler_Shower.timerPause = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
_showerColdShotStop(); // turn hot water back on in case its off
|
||||
}
|
||||
}
|
||||
}
|
||||
showerCheck();
|
||||
}
|
||||
|
||||
// yield to prevent watchdog from timing out
|
||||
yield();
|
||||
yield(); // yield to prevent watchdog from timing out
|
||||
}
|
||||
|
||||
1226
src/ems.cpp
1226
src/ems.cpp
File diff suppressed because it is too large
Load Diff
157
src/ems.h
157
src/ems.h
@@ -9,26 +9,27 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
// EMS IDs
|
||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages
|
||||
#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs
|
||||
#define EMS_ID_BOILER 0x08 // Fixed - also known as MC10.
|
||||
#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as "Service Key"
|
||||
|
||||
// Special EMS Telegram Types
|
||||
#define EMS_TYPE_NONE 0x00 // none
|
||||
|
||||
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
||||
#define EMS_MAX_TELEGRAM_LENGTH 99 // max length of a telegram, including CRC
|
||||
|
||||
#define EMS_TX_MAXBUFFERSIZE 128 // max size of the buffer. packets are 32 bits
|
||||
|
||||
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (older Moduline 300)
|
||||
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (Moduline 300)
|
||||
#define EMS_ID_THERMOSTAT_RC35 0x10 // RC35 (Moduline 400)
|
||||
#define EMS_ID_THERMOSTAT_EASY 0x18 // Nefit Easy
|
||||
#define EMS_ID_THERMOSTAT_RC20 0x17 // RC20 (e.g. Moduline 300)
|
||||
#define EMS_ID_THERMOSTAT_RC30 0x10 // RC30 (e.g. Moduline 400)
|
||||
#define EMS_ID_THERMOSTAT_EASY 0x18 // TC100 (Nefit Easy)
|
||||
|
||||
// define here the EMS telegram types you need
|
||||
|
||||
// Boiler...
|
||||
// Common for all EMS devices
|
||||
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler)
|
||||
|
||||
/*
|
||||
* Boiler...
|
||||
*/
|
||||
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
||||
@@ -38,26 +39,39 @@
|
||||
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
||||
#define EMS_TYPE_UBAParametersMessage 0x16
|
||||
#define EMS_TYPE_UBASetPoints 0x1A
|
||||
#define EMS_TYPE_UBAFunctionTest 0x1D
|
||||
|
||||
// Thermostat...
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCTempMessage 0xA3 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RC20Temperature 0xA8
|
||||
#define EMS_TYPE_EasyTemperature 0x0A // reading values on an Easy Thermostat
|
||||
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler)
|
||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
||||
|
||||
// Offsets for specific values in a telegram, per type, used for validation
|
||||
#define EMS_OFFSET_RC20Temperature_temp 0x1C // thermostat set temp
|
||||
#define EMS_OFFSET_RC20Temperature_mode 0x17 // thermostat mode
|
||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 0x02 // WW Temperature
|
||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 0x01 // WW Activated
|
||||
/*
|
||||
* Thermostat...
|
||||
*/
|
||||
|
||||
// Common for all thermostats
|
||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
||||
|
||||
// RC20 specific
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// RC30 specific
|
||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// Easy specific
|
||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||
|
||||
// default values
|
||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||
#define EMS_VALUE_FLOAT_NOTSET -1 // float unset
|
||||
#define EMS_VALUE_INT_ON 1 // boolean true
|
||||
#define EMS_VALUE_INT_OFF 0 // boolean false
|
||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints
|
||||
#define EMS_VALUE_FLOAT_NOTSET -255 // float unset
|
||||
|
||||
/* EMS UART transfer status */
|
||||
typedef enum {
|
||||
@@ -67,20 +81,23 @@ typedef enum {
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_IDLE,
|
||||
EMS_TX_PENDING, // got Tx package to send, waiting for next Poll to send
|
||||
EMS_TX_ACTIVE // Tx package being sent, no break sent
|
||||
EMS_TX_ACTIVE, // Tx package being sent, no break sent
|
||||
EMS_TX_SUCCESS,
|
||||
EMS_TX_ERROR
|
||||
} _EMS_TX_STATUS;
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_NONE,
|
||||
EMS_TX_READ, // doing a read request
|
||||
EMS_TX_WRITE, // doing a write request
|
||||
EMS_TX_VALIDATE // do a validate after a write
|
||||
} _EMS_TX_ACTION;
|
||||
EMS_TX_TELEGRAM_INIT, // just initialized
|
||||
EMS_TX_TELEGRAM_READ, // doing a read request
|
||||
EMS_TX_TELEGRAM_WRITE, // doing a write request
|
||||
EMS_TX_TELEGRAM_VALIDATE, // do a read but only to validate the last write
|
||||
EMS_TX_TELEGRAM_RAW // sending in raw mode
|
||||
} _EMS_TX_TELEGRAM_ACTION;
|
||||
|
||||
/* EMS logging */
|
||||
typedef enum {
|
||||
EMS_SYS_LOGGING_NONE, // no messages
|
||||
EMS_SYS_LOGGING_RAW, // raw data mode
|
||||
EMS_SYS_LOGGING_BASIC, // only basic read/write messages
|
||||
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
|
||||
EMS_SYS_LOGGING_VERBOSE // everything
|
||||
@@ -94,31 +111,51 @@ typedef struct {
|
||||
uint16_t emsTxPkgs; // sent
|
||||
uint16_t emxCrcErr; // CRC errors
|
||||
bool emsPollEnabled; // flag enable the response to poll messages
|
||||
bool emsTxEnabled; // flag if we're allowing sending of Tx packages
|
||||
bool emsThermostatEnabled; // if there is a RCxx thermostat active
|
||||
bool emsBoilerEnabled; // is the boiler online
|
||||
_EMS_SYS_LOGGING emsLogging; // logging
|
||||
unsigned long emsLastPoll; // in ms, last time we received a poll
|
||||
unsigned long emsLastRx; // timings
|
||||
unsigned long emsLastTx; // timings
|
||||
bool emsRefreshed; // fresh data, needs to be pushed out to MQTT
|
||||
} _EMS_Sys_Status;
|
||||
|
||||
// The Tx send package
|
||||
typedef struct {
|
||||
_EMS_TX_ACTION action; // read or write
|
||||
uint8_t dest;
|
||||
uint8_t type;
|
||||
uint8_t offset;
|
||||
uint8_t length;
|
||||
uint8_t checkValue; // value to validate against
|
||||
uint8_t type_validate; // type to call after a successful Write command
|
||||
uint8_t data[EMS_TX_MAXBUFFERSIZE];
|
||||
_EMS_TX_TELEGRAM_ACTION action; // read or write
|
||||
uint8_t dest;
|
||||
uint8_t type;
|
||||
uint8_t offset;
|
||||
uint8_t length;
|
||||
uint8_t dataValue; // value to validate against
|
||||
uint8_t type_validate; // type to call after a successful Write command
|
||||
uint8_t comparisonValue; // value to compare against during a validate
|
||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
||||
uint8_t comparisonPostRead; // after a successful write call this to read
|
||||
bool hasSent; // has been sent, just pending ack
|
||||
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
||||
uint8_t data[EMS_TX_MAXBUFFERSIZE];
|
||||
} _EMS_TxTelegram;
|
||||
|
||||
// default empty Tx
|
||||
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
EMS_TX_TELEGRAM_INIT, // action
|
||||
EMS_ID_NONE, // dest
|
||||
EMS_ID_NONE, // type
|
||||
0, // offset
|
||||
0, // length
|
||||
0, // data value
|
||||
EMS_ID_NONE, // type_validate
|
||||
0, // comparisonValue
|
||||
0, // comparisonOffset
|
||||
EMS_ID_NONE, // comparisonPostRead
|
||||
false, // hasSent
|
||||
false, // forceRefresh
|
||||
{0x00} // data
|
||||
};
|
||||
|
||||
/*
|
||||
* Telegram package defintions
|
||||
*/
|
||||
typedef struct {
|
||||
// UBAParameterWW
|
||||
typedef struct { // UBAParameterWW
|
||||
uint8_t wWActivated; // Warm Water activated
|
||||
uint8_t wWSelTemp; // Warm Water selected temperature
|
||||
uint8_t wWCircPump; // Warm Water circulation pump Available
|
||||
@@ -161,7 +198,7 @@ typedef struct {
|
||||
|
||||
// Thermostat data
|
||||
typedef struct {
|
||||
uint8_t type; // thermostat type (RC20, RC30, RC35 etc)
|
||||
uint8_t type; // thermostat type (RC30, Easy etc)
|
||||
float setpoint_roomTemp; // current set temp
|
||||
float curr_roomTemp; // current room temp
|
||||
uint8_t mode; // 0=low, 1=manual, 2=auto
|
||||
@@ -174,9 +211,9 @@ typedef struct {
|
||||
} _EMS_Thermostat;
|
||||
|
||||
// call back function signature
|
||||
typedef bool (*EMS_processType_cb)(uint8_t * data, uint8_t length);
|
||||
typedef void (*EMS_processType_cb)(uint8_t * data, uint8_t length);
|
||||
|
||||
// Definition for each type, including the relative callback function
|
||||
// Definition for each EMS type, including the relative callback function
|
||||
typedef struct {
|
||||
uint8_t src;
|
||||
uint8_t type;
|
||||
@@ -184,6 +221,12 @@ typedef struct {
|
||||
EMS_processType_cb processType_cb;
|
||||
} _EMS_Types;
|
||||
|
||||
// Definition for thermostat type
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
const char typeString[50];
|
||||
} _Thermostat_Types;
|
||||
|
||||
// ANSI Colors
|
||||
#define COLOR_RESET "\x1B[0m"
|
||||
#define COLOR_BLACK "\x1B[0;30m"
|
||||
@@ -200,34 +243,42 @@ typedef struct {
|
||||
// function definitions
|
||||
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
||||
void ems_init();
|
||||
void ems_doReadCommand(uint8_t type, uint8_t dest);
|
||||
void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh = false);
|
||||
void ems_sendRawTelegram(char * telegram);
|
||||
|
||||
void ems_setThermostatTemp(float temp);
|
||||
void ems_setThermostatMode(uint8_t mode);
|
||||
void ems_setWarmWaterTemp(uint8_t temperature);
|
||||
void ems_setWarmWaterActivated(bool activated);
|
||||
void ems_setWarmTapWaterActivated(bool activated);
|
||||
void ems_setExperimental(uint8_t value);
|
||||
void ems_setPoll(bool b);
|
||||
void ems_setTxEnabled(bool b);
|
||||
void ems_setThermostatEnabled(bool b);
|
||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel);
|
||||
void ems_setEmsRefreshed(bool b);
|
||||
|
||||
void ems_getThermostatTemps();
|
||||
void ems_getThermostatValues();
|
||||
bool ems_getPoll();
|
||||
bool ems_getTxEnabled();
|
||||
bool ems_getThermostatEnabled();
|
||||
bool ems_getBoilerEnabled();
|
||||
_EMS_SYS_LOGGING ems_getLogging();
|
||||
uint8_t ems_getEmsTypesCount();
|
||||
uint8_t ems_getThermostatTypesCount();
|
||||
bool ems_getEmsRefreshed();
|
||||
|
||||
void ems_printAllTypes();
|
||||
void ems_printThermostatType();
|
||||
void ems_printTxQueue();
|
||||
|
||||
// private functions
|
||||
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
||||
void _processType(uint8_t * telegram, uint8_t length);
|
||||
void _initTxBuffer();
|
||||
void _buildTxTelegram(uint8_t data_value);
|
||||
void _debugPrintPackage(const char * prefix, uint8_t * data, uint8_t len, const char * color);
|
||||
void _ems_clearTxData();
|
||||
|
||||
// global so can referenced in other classes
|
||||
extern _EMS_Sys_Status EMS_Sys_Status;
|
||||
extern _EMS_TxTelegram EMS_TxTelegram;
|
||||
extern _EMS_Boiler EMS_Boiler;
|
||||
extern _EMS_Thermostat EMS_Thermostat;
|
||||
|
||||
@@ -25,20 +25,21 @@
|
||||
#define BOILER_SHOWER_TIMER 1 // monitors how long a shower has taken
|
||||
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
|
||||
|
||||
// define here the Thermostat type. see ems.h for options
|
||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20 // your thermostat ID
|
||||
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
|
||||
// define here the Thermostat type. see ems.h for the supported types
|
||||
#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC20
|
||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_RC30
|
||||
//#define EMS_ID_THERMOSTAT EMS_ID_THERMOSTAT_EASY
|
||||
|
||||
// trigger settings to determine if hot tap water or the heating is active
|
||||
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
||||
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
|
||||
|
||||
// if using the shower timer, change these settings (in ms)
|
||||
#define SHOWER_PAUSE_TIME 15000 // 15 seconds, max time if water is switched off & on during a shower
|
||||
#define SHOWER_MIN_DURATION 180000 // 3 minutes, before recognizing its a shower
|
||||
#define SHOWER_MAX_DURATION 420000 // 7 minutes, before trigger a shot of cold water
|
||||
#define SHOWER_COLDSHOT_DURATION 5 // 5 seconds for cold water - note, must be in seconds
|
||||
#define SHOWER_OFFSET_TIME 8000 // 8 seconds grace time, to calibrate actual time under the shower
|
||||
// if using the shower timer, change these settings
|
||||
#define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower
|
||||
#define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower
|
||||
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
|
||||
#define SHOWER_OFFSET_TIME 5000 // in ms. 5 seconds grace time, to calibrate actual time under the shower
|
||||
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
|
||||
|
||||
// if using LEDs to show traffic, configure the GPIOs here
|
||||
// only works if -DUSE_LED is set in platformio.ini
|
||||
|
||||
2
src/version.h
Normal file
2
src/version.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#define APP_NAME "EMS-ESP-Boiler"
|
||||
#define APP_VERSION "1.1.0"
|
||||
Reference in New Issue
Block a user