mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 09:19:51 +03:00
small improvements
This commit is contained in:
@@ -666,7 +666,7 @@ void ESPHelper::consoleHandle() {
|
||||
// Set callback of sketch function to process project messages
|
||||
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
|
||||
_helpProjectCmds = cmds; // command list
|
||||
_helpProjectCmds_count = count; // numiber of commands
|
||||
_helpProjectCmds_count = count; // number of commands
|
||||
_consoleCallbackProjectCmds = callback; // external function to handle commands
|
||||
}
|
||||
|
||||
@@ -742,26 +742,29 @@ size_t ESPHelper::write(uint8_t character) {
|
||||
|
||||
// Show help of commands
|
||||
void ESPHelper::consoleShowHelp() {
|
||||
String help = "********************************\n\r* Remote Telnet Command Center "
|
||||
"*\n\r********************************\n\r";
|
||||
String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
|
||||
"*\n\r**********************************************\n\r";
|
||||
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
|
||||
+ "\tMAC address: " + WiFi.macAddress() + "\n\r";
|
||||
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
|
||||
help += "* Boot time: ";
|
||||
help.concat(_boottime);
|
||||
help += "\n\r* Free Heap RAM: ";
|
||||
help += "\n\r* Free RAM: ";
|
||||
help.concat(ESP.getFreeHeap());
|
||||
help += " bytes\n\r";
|
||||
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show used memory, !=reboot, &=suspend all "
|
||||
help += "*\n\r* Commands:\n\r* ?=this help, q=quit telnet, $=show free memory, !=reboot, &=suspend all "
|
||||
"notifications\n\r";
|
||||
|
||||
char s[100];
|
||||
|
||||
// print custom commands if available
|
||||
if (_consoleCallbackProjectCmds) {
|
||||
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
|
||||
//for (uint8_t i = 0; i < 5; i++) {
|
||||
help += FPSTR("* ");
|
||||
help += FPSTR(_helpProjectCmds[i].key);
|
||||
help += FPSTR(" ");
|
||||
for (int j = 0; j < (8 - strlen(_helpProjectCmds[i].key)); j++) { // padding
|
||||
help += FPSTR(" ");
|
||||
}
|
||||
help += FPSTR(_helpProjectCmds[i].description);
|
||||
help += FPSTR("\n\r");
|
||||
}
|
||||
@@ -812,7 +815,7 @@ void ESPHelper::consoleProcessCommand() {
|
||||
telnetClient.println("* Closing telnet connection...");
|
||||
telnetClient.stop();
|
||||
} else if (cmd == '$') {
|
||||
telnetClient.print("* Free Heap RAM (bytes): ");
|
||||
telnetClient.print("* Free RAM (bytes): ");
|
||||
telnetClient.println(ESP.getFreeHeap());
|
||||
} else if (cmd == '!') {
|
||||
resetESP();
|
||||
|
||||
@@ -74,7 +74,7 @@ typedef struct {
|
||||
} subscription;
|
||||
|
||||
typedef struct {
|
||||
char key[5];
|
||||
char key[10];
|
||||
char description[400];
|
||||
} command_t;
|
||||
|
||||
|
||||
183
src/boiler.ino
183
src/boiler.ino
@@ -21,18 +21,28 @@
|
||||
|
||||
// timers, all values are in seconds
|
||||
#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 REGULARUPDATES_TIME 60 // every minute a call is made, so for our 2 calls theres a write cmd every 30seconds
|
||||
#define HEARTBEAT_TIME 1 // every second blink heartbeat LED
|
||||
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
|
||||
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
|
||||
Ticker regularUpdatesTimer;
|
||||
|
||||
#define HEARTBEAT_TIME 1 // every second blink heartbeat LED
|
||||
Ticker heartbeatTimer;
|
||||
|
||||
// thermostat scan - for debugging
|
||||
Ticker scanThermostat;
|
||||
#define SCANTHERMOSTAT_TIME 4
|
||||
uint8_t scanThermostat_count;
|
||||
|
||||
Ticker publishValuesTimer;
|
||||
Ticker systemCheckTimer;
|
||||
Ticker regularUpdatesTimer;
|
||||
Ticker heartbeatTimer;
|
||||
Ticker showerColdShotStopTimer;
|
||||
uint8_t regularUpdatesCount = 0;
|
||||
|
||||
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
|
||||
|
||||
|
||||
// GPIOs
|
||||
#define LED_HEARTBEAT LED_BUILTIN // onboard LED
|
||||
|
||||
@@ -108,20 +118,24 @@ netInfo homeNet = {.mqttHost = MQTT_IP,
|
||||
|
||||
ESPHelper myESP(&homeNet);
|
||||
|
||||
command_t PROGMEM project_cmds[] = {{"s", "show statistics"},
|
||||
{"h", "list EMS telegram type ids with supported logic"},
|
||||
{"P", "publish all stat to MQTT"},
|
||||
{"v", "[n] set logging (0=none, 1=basic, 2=verbose)"},
|
||||
{"p", "toggle EMS Poll response on/off"},
|
||||
{"T", "toggle Thermostat monitoring on/off"},
|
||||
{"S", "toggle Shower timer on/off"},
|
||||
{"A", "toggle shower Alert on/off"},
|
||||
{"r", "[n] send EMS request (n=any telegram type id. Use 'h' for suppported types)"},
|
||||
{"t", "[n] set thermostat temperature"},
|
||||
{"m", "[n] set thermostat mode (1=manual, 2=auto)"},
|
||||
{"w", "[n] set boiler warm water temperature (min 30)"},
|
||||
{"a", "[n] boiler warm water (1=on, 2=off)"},
|
||||
{"x", "[n] experimental (warning: for debugging only!)"}};
|
||||
command_t PROGMEM project_cmds[] = {
|
||||
|
||||
{"v [n]", "set logging (0=none, 1=basic, 2=thermostat only, 3=verbose)"},
|
||||
{"s", "show statistics"},
|
||||
{"h", "list supported EMS telegram type IDs"},
|
||||
{"P", "publish all stat to MQTT"},
|
||||
{"p", "toggle EMS Poll response on/off"},
|
||||
{"S", "toggle Shower timer on/off"},
|
||||
{"A", "toggle shower Alert on/off"},
|
||||
{"b [xx]", "boiler request (xx=telegram type ID)"},
|
||||
{"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)"},
|
||||
{"T [xx]", "set thermostat temperature"},
|
||||
{"m [n]", "set thermostat mode (1=manual, 2=auto)"},
|
||||
{"x [xx]", "experimental code for debugging."}
|
||||
|
||||
};
|
||||
|
||||
// calculates size of an 2d array at compile time
|
||||
template <typename T, size_t N>
|
||||
@@ -244,12 +258,14 @@ void showInfo() {
|
||||
// General stats from EMS bus
|
||||
|
||||
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();
|
||||
if (sysLog == EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Basic");
|
||||
} else if (sysLog == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Verbose");
|
||||
} else if (sysLog == EMS_SYS_LOGGING_THERMOSTAT) {
|
||||
myDebug("Thermostat only");
|
||||
} else {
|
||||
myDebug("None");
|
||||
}
|
||||
@@ -432,7 +448,7 @@ void publishValues(bool force) {
|
||||
|
||||
if ((previousBoilerPublishCRC != checksum) || force) {
|
||||
previousBoilerPublishCRC = checksum;
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Publishing boiler data via MQTT\n");
|
||||
}
|
||||
|
||||
@@ -443,7 +459,7 @@ 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_NONE) {
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Publishing hot water and heating state via MQTT\n");
|
||||
}
|
||||
myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0");
|
||||
@@ -486,7 +502,7 @@ void publishValues(bool force) {
|
||||
|
||||
if ((previousThermostatPublishCRC != checksum) || force) {
|
||||
previousThermostatPublishCRC = checksum;
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Publishing thermostat data via MQTT\n");
|
||||
}
|
||||
|
||||
@@ -512,30 +528,50 @@ void set_showerAlert() {
|
||||
|
||||
// extra commands options for telnet debug window
|
||||
void myDebugCallback() {
|
||||
char * cmd = myESP.consoleGetLastCommand();
|
||||
bool b;
|
||||
char * cmd = myESP.consoleGetLastCommand();
|
||||
uint8_t len = strlen(cmd);
|
||||
bool b;
|
||||
|
||||
// look for single letter commands
|
||||
if (len == 1) {
|
||||
switch (cmd[0]) {
|
||||
case 's':
|
||||
showInfo();
|
||||
break;
|
||||
case 'p':
|
||||
b = !ems_getPoll();
|
||||
ems_setPoll(b);
|
||||
break;
|
||||
case 'P':
|
||||
//myESP.logger(LOG_HA, "Force publish values");
|
||||
publishValues(true);
|
||||
break;
|
||||
case 'h': // show type handlers
|
||||
ems_printAllTypes();
|
||||
break;
|
||||
case 'S': // toggle Shower timer support
|
||||
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
|
||||
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;
|
||||
default:
|
||||
myDebug("Unknown command. Use ? for help.\n");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 2)
|
||||
return;
|
||||
|
||||
// for commands with parameters, assume command is just one letter
|
||||
switch (cmd[0]) {
|
||||
case 's':
|
||||
showInfo();
|
||||
break;
|
||||
case 'p':
|
||||
b = !ems_getPoll();
|
||||
ems_setPoll(b);
|
||||
break;
|
||||
case 'P':
|
||||
//myESP.logger(LOG_HA, "Force publish values");
|
||||
publishValues(true);
|
||||
break;
|
||||
case 'r': // read command for Boiler or Thermostat
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16));
|
||||
break;
|
||||
case 't': // set thermostat temp
|
||||
case 'T': // set thermostat temp
|
||||
ems_setThermostatTemp(strtof(&cmd[2], 0));
|
||||
break;
|
||||
case 'h': // show type handlers
|
||||
ems_printAllTypes();
|
||||
break;
|
||||
case 'm': // set thermostat mode
|
||||
if ((cmd[2] - '0') == 1)
|
||||
ems_setThermostatMode(1);
|
||||
@@ -555,26 +591,31 @@ void myDebugCallback() {
|
||||
else if ((cmd[2] - '0') == 0)
|
||||
ems_setWarmWaterActivated(false);
|
||||
break;
|
||||
case 'x': // experimental code for debugging - use with caution!
|
||||
case 'b': // boiler read command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_BOILER);
|
||||
break;
|
||||
case 't': // thermostat command
|
||||
ems_doReadCommand((uint8_t)strtol(&cmd[2], 0, 16), EMS_ID_THERMOSTAT);
|
||||
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 'T': // toggle Thermostat
|
||||
b = !ems_getThermostatEnabled();
|
||||
ems_setThermostatEnabled(b);
|
||||
Boiler_Status.thermostat_enabled = b;
|
||||
break;
|
||||
case 'S': // toggle Shower timer support
|
||||
Boiler_Status.shower_timer = !Boiler_Status.shower_timer;
|
||||
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");
|
||||
case 'U': // thermostat scan
|
||||
myDebug("Doing a scan on thermostat IDs\n");
|
||||
ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
|
||||
publishValuesTimer.detach();
|
||||
systemCheckTimer.detach();
|
||||
regularUpdatesTimer.detach();
|
||||
scanThermostat_count = (uint8_t)strtol(&cmd[2], 0, 16);
|
||||
scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
|
||||
break;
|
||||
default:
|
||||
myDebug("Unknown command '%c'. Use ? for help.\n", cmd[0]);
|
||||
myDebug("Unknown command. Use ? for help.\n");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// MQTT Callback to handle incoming/outgoing changes
|
||||
@@ -805,6 +846,13 @@ void heartbeat() {
|
||||
}
|
||||
}
|
||||
|
||||
// Thermostat scan
|
||||
void do_scanThermostat() {
|
||||
//myDebug("Scanning %d..\n", scanThermostat_count);
|
||||
ems_doReadCommand(scanThermostat_count, EMS_ID_THERMOSTAT);
|
||||
scanThermostat_count++;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -826,7 +874,7 @@ void regularUpdates() {
|
||||
// force get the thermostat data which are not usually automatically broadcasted
|
||||
ems_getThermostatTemps();
|
||||
} else if (cycle == 1) {
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW); // get Warm Water values
|
||||
ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER); // get Warm Water values
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -880,7 +928,7 @@ void loop() {
|
||||
#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_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.");
|
||||
@@ -911,20 +959,27 @@ void loop() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -932,7 +987,9 @@ void loop() {
|
||||
// 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
|
||||
@@ -964,8 +1021,10 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHOWER_TEST
|
||||
// reset everything
|
||||
myDebugLog("Shower: resetting timers");
|
||||
#endif
|
||||
Boiler_Shower.timerStart = 0;
|
||||
Boiler_Shower.timerPause = 0;
|
||||
Boiler_Shower.showerOn = false;
|
||||
|
||||
190
src/ems.cpp
190
src/ems.cpp
@@ -44,6 +44,7 @@ bool _process_RC20Temperature(uint8_t * data, uint8_t length);
|
||||
bool _process_RCTempMessage(uint8_t * data, uint8_t length);
|
||||
bool _process_Version(uint8_t * data, uint8_t length);
|
||||
bool _process_SetPoints(uint8_t * data, uint8_t length);
|
||||
bool _process_EasyTemperature(uint8_t * data, uint8_t length);
|
||||
|
||||
const _EMS_Types EMS_Types[] = {
|
||||
|
||||
@@ -59,6 +60,7 @@ const _EMS_Types EMS_Types[] = {
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_RC20Temperature, "RC20Temperature", _process_RC20Temperature},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_EasyTemperature, "EasyTemperature", _process_EasyTemperature},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_RCTempMessage, "RCTempMessage", _process_RCTempMessage},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version},
|
||||
{EMS_ID_THERMOSTAT, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints}
|
||||
@@ -111,7 +113,7 @@ void ems_init() {
|
||||
EMS_Sys_Status.emsLogging = EMS_SYS_LOGGING_NONE; // Verbose logging is off
|
||||
|
||||
// thermostat
|
||||
EMS_Thermostat.type = EMS_ID_THERMOSTAT; // type, see ems.h
|
||||
EMS_Thermostat.type = EMS_ID_THERMOSTAT; // type, see my_config.h
|
||||
EMS_Thermostat.hour = 0;
|
||||
EMS_Thermostat.minute = 0;
|
||||
EMS_Thermostat.second = 0;
|
||||
@@ -206,12 +208,14 @@ void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
|
||||
if (loglevel <= EMS_SYS_LOGGING_VERBOSE) {
|
||||
EMS_Sys_Status.emsLogging = loglevel;
|
||||
myDebug("System Logging is set to ");
|
||||
if (loglevel == EMS_SYS_LOGGING_BASIC) {
|
||||
if (loglevel == EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("None\n");
|
||||
} else if (loglevel == EMS_SYS_LOGGING_BASIC) {
|
||||
myDebug("Basic\n");
|
||||
} else if (loglevel == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("Verbose\n");
|
||||
} else {
|
||||
myDebug("None\n");
|
||||
} else if (loglevel == EMS_SYS_LOGGING_THERMOSTAT) {
|
||||
myDebug("Thermostat only\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,10 +251,10 @@ uint16_t _toLong(uint8_t i, uint8_t * data) {
|
||||
|
||||
// debugging only - print out all handled types
|
||||
void ems_printAllTypes() {
|
||||
myDebug("These %d telegram type ids are recognized:\n", _EMS_Types_max);
|
||||
myDebug("These %d telegram type IDs are recognized:\n", _EMS_Types_max);
|
||||
|
||||
for (uint8_t i = 0; i < _EMS_Types_max; i++) {
|
||||
myDebug(" %s:\ttype %02x (%s)\n",
|
||||
myDebug(" %s:\ttype ID %02X (%s)\n",
|
||||
EMS_Types[i].src == EMS_ID_THERMOSTAT ? "Thermostat" : "Boiler",
|
||||
EMS_Types[i].type,
|
||||
EMS_Types[i].typeString);
|
||||
@@ -281,20 +285,26 @@ void _debugPrintTelegram(const char * prefix, uint8_t * data, uint8_t len, const
|
||||
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_VERBOSE)
|
||||
return;
|
||||
|
||||
myDebug("%s%s len=%d, telegram: ", color, prefix, len);
|
||||
myDebug("%s%s telegram: ", color, prefix);
|
||||
for (int i = 0; i < len; i++) {
|
||||
myDebug("%02x ", data[i]);
|
||||
myDebug("%02X ", data[i]);
|
||||
}
|
||||
myDebug("%s\n", COLOR_RESET);
|
||||
|
||||
myDebug("(len %d)%s\n", len, COLOR_RESET);
|
||||
}
|
||||
|
||||
// send the contents of the Tx buffer
|
||||
void _ems_sendTelegram() {
|
||||
// only send when Tx is not busy
|
||||
_debugPrintTelegram(((EMS_TxTelegram.action == EMS_TX_WRITE) ? "Sending write telegram:" : "Sending read telegram:"),
|
||||
EMS_TxTelegram.data,
|
||||
EMS_TxTelegram.length,
|
||||
COLOR_CYAN);
|
||||
char s[50];
|
||||
|
||||
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
|
||||
sprintf(s,
|
||||
"Sending %s to 0x%02X:",
|
||||
((EMS_TxTelegram.action == EMS_TX_WRITE) ? "write" : "read"),
|
||||
EMS_TxTelegram.dest & 0x7F);
|
||||
_debugPrintTelegram(s, EMS_TxTelegram.data, EMS_TxTelegram.length, COLOR_CYAN);
|
||||
}
|
||||
|
||||
EMS_Sys_Status.emsTxStatus = EMS_TX_ACTIVE;
|
||||
emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length);
|
||||
@@ -335,7 +345,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
|
||||
_initTxBuffer();
|
||||
} else {
|
||||
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Didn't receive acknowledgement from the 0x%02x, so resending (attempt #%d/%d)...\n",
|
||||
myDebug("Didn't receive acknowledgement from the 0x%X, so resending (attempt #%d/%d)...\n",
|
||||
EMS_TxTelegram.type,
|
||||
emsLastRxCount,
|
||||
RX_READ_TIMEOUT_COUNT);
|
||||
@@ -386,8 +396,8 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
|
||||
|
||||
// ignore anything that doesn't resemble a proper telegram package
|
||||
// minimal is 5 bytes, excluding CRC at the end
|
||||
if ((length < 5)) {
|
||||
_debugPrintTelegram("Noisy data:", telegram, length, COLOR_MAGENTA);
|
||||
if (length < 5) {
|
||||
_debugPrintTelegram("Noisy data:", telegram, length, COLOR_RED);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -410,7 +420,8 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
|
||||
*/
|
||||
void _processType(uint8_t * telegram, uint8_t length) {
|
||||
// extract the 4-byte header information
|
||||
uint8_t src = telegram[0] & 0x7F; // remove 8th bit as we deal with both reads and writes
|
||||
// removing 8th bit as we deal with both reads and writes
|
||||
uint8_t src = telegram[0] & 0x7F;
|
||||
|
||||
// if its an echo of ourselves from the master, ignore
|
||||
if (src == EMS_ID_ME) {
|
||||
@@ -419,7 +430,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
}
|
||||
|
||||
// header
|
||||
uint8_t dest = telegram[1];
|
||||
uint8_t dest = telegram[1] & 0x7F; // remove 8th bit
|
||||
uint8_t type = telegram[2];
|
||||
uint8_t * data = telegram + 4; // data block starts at position 5
|
||||
|
||||
@@ -432,6 +443,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
// we have a match
|
||||
typeFound = true;
|
||||
// call callback to fetch the values from the telegram
|
||||
// data block is sent, which starts with the 5th byte of the telegram
|
||||
// return value tells us if we need to force send values back to MQTT
|
||||
if ((EMS_Types[i].processType_cb) != (void *)NULL) {
|
||||
EMS_Sys_Status.emsRefreshed = EMS_Types[i].processType_cb(data, length);
|
||||
@@ -458,7 +470,16 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
}
|
||||
|
||||
// print debug messages
|
||||
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) {
|
||||
// special case for only thermostat
|
||||
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_THERMOSTAT) {
|
||||
if ((src == EMS_ID_THERMOSTAT) && (dest == EMS_ID_ME)) {
|
||||
myDebug("Thermostat -> me, type 0x%02X telegram: ", type);
|
||||
for (int i = 0; i < length; i++) {
|
||||
myDebug("%02X ", telegram[i]);
|
||||
}
|
||||
myDebug("\n");
|
||||
}
|
||||
} else if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
|
||||
char color_s[20];
|
||||
char src_s[20];
|
||||
char dest_s[20];
|
||||
@@ -466,32 +487,38 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
|
||||
// set source string
|
||||
if (src == EMS_ID_BOILER) {
|
||||
strcpy(src_s, "Boiler");
|
||||
strcpy(src_s, "Boiler -> ");
|
||||
} else if (src == EMS_ID_THERMOSTAT) {
|
||||
strcpy(src_s, "Thermostat");
|
||||
strcpy(src_s, "Thermostat -> ");
|
||||
} else {
|
||||
strcpy(src_s, "<unknown>");
|
||||
sprintf(src_s, "0x%02X -> ", src);
|
||||
}
|
||||
|
||||
// set destination string
|
||||
if (dest == EMS_ID_ME) {
|
||||
strcpy(dest_s, "telegram for us");
|
||||
strcpy(dest_s, "me");
|
||||
strcpy(color_s, COLOR_YELLOW);
|
||||
} else if (dest == EMS_ID_NONE) {
|
||||
// it's probably just a broadcast
|
||||
strcpy(dest_s, "broadcast");
|
||||
strcpy(dest_s, "all");
|
||||
strcpy(color_s, COLOR_GREEN);
|
||||
} else if (dest == EMS_ID_BOILER) {
|
||||
strcpy(dest_s, "Boiler");
|
||||
strcpy(color_s, COLOR_MAGENTA);
|
||||
} else if (dest == EMS_ID_THERMOSTAT) {
|
||||
strcpy(dest_s, "Thermostat");
|
||||
strcpy(color_s, COLOR_MAGENTA);
|
||||
} else {
|
||||
// for someone else
|
||||
strcpy(dest_s, "(not for us)");
|
||||
sprintf(dest_s, "0x%02X", dest);
|
||||
strcpy(color_s, COLOR_MAGENTA);
|
||||
}
|
||||
|
||||
|
||||
// and print
|
||||
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);
|
||||
if (typeFound) {
|
||||
myDebug("<--- %s(0x%02x) received\n", EMS_Types[i].typeString, type);
|
||||
myDebug("<--- %s(0x%02X) received\n", EMS_Types[i].typeString, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,13 +548,13 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
// look up the ID and fetch string
|
||||
int i = ems_findType(EMS_TxTelegram.type);
|
||||
if (i != -1) {
|
||||
myDebug("---> %s(0x%02x) sent with value %d at offset %d ",
|
||||
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);
|
||||
myDebug("---> ?(0x%02X) sent with value %d at offset %d ", type, EMS_TxTelegram.checkValue, offset);
|
||||
}
|
||||
|
||||
if (EMS_TxTelegram.checkValue == data[offset]) {
|
||||
@@ -545,7 +572,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
|
||||
bool _checkWriteQueueFull() {
|
||||
if (EMS_Sys_Status.emsTxStatus == EMS_TX_PENDING) { // send is already pending
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
myDebug("Delaying write command as there is already a telegram (type 0x%02x) in the queue\n",
|
||||
myDebug("Delaying write command as there is already a telegram (type 0x%02X) in the queue\n",
|
||||
EMS_TxTelegram.type);
|
||||
}
|
||||
return true; // something in queue
|
||||
@@ -656,6 +683,17 @@ bool _process_RC20StatusMessage(uint8_t * data, uint8_t length) {
|
||||
return true; // triggers a send the values back to Home Assistant via MQTT
|
||||
}
|
||||
|
||||
/*
|
||||
* EasyTemperature - type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long
|
||||
* The Easy has a digital precision of its floats to 2 decimal places, so values is divided by 100
|
||||
*/
|
||||
bool _process_EasyTemperature(uint8_t * data, uint8_t length) {
|
||||
EMS_Thermostat.curr_roomTemp = ((float)(((data[8] << 8) + data[9]))) / 100;
|
||||
EMS_Thermostat.setpoint_roomTemp = ((float)(((data[10] << 8) + data[11]))) / 100;
|
||||
|
||||
return true; // triggers a send the values back to Home Assistant via MQTT
|
||||
}
|
||||
|
||||
/*
|
||||
* RC20Temperature - type 0xa8 - for set temp value and mode from the RC20 thermostat (0x17)
|
||||
* received only after requested
|
||||
@@ -717,7 +755,7 @@ bool _process_SetPoints(uint8_t * data, uint8_t length) {
|
||||
uint8_t hk_power = data[1];
|
||||
uint8_t ww_power = data[2];
|
||||
|
||||
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) {
|
||||
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
|
||||
myDebug("UBASetPoint: SetPoint=%d, hk_power=%d ww_power=%d\n", setpoint, hk_power, ww_power);
|
||||
}
|
||||
|
||||
@@ -776,7 +814,9 @@ void _buildTxTelegram(uint8_t data_value) {
|
||||
*/
|
||||
void ems_getThermostatTemps() {
|
||||
if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_RC20) {
|
||||
ems_doReadCommand(EMS_TYPE_RC20Temperature);
|
||||
ems_doReadCommand(EMS_TYPE_RC20Temperature, EMS_ID_THERMOSTAT);
|
||||
} else if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_EASY) {
|
||||
ems_doReadCommand(EMS_TYPE_EasyTemperature, EMS_ID_THERMOSTAT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,21 +824,22 @@ void ems_getThermostatTemps() {
|
||||
* Send a command to UART Tx to Read from another device
|
||||
* Read commands when sent must respond by the destination (target) immediately (or within 10ms)
|
||||
*/
|
||||
void ems_doReadCommand(uint8_t type) {
|
||||
void ems_doReadCommand(uint8_t type, uint8_t dest) {
|
||||
if (type == EMS_TYPE_NONE)
|
||||
return; // not a valid type, quit
|
||||
|
||||
if (_checkWriteQueueFull())
|
||||
return; // check if there is already something in the queue
|
||||
|
||||
int i = ems_findType(type);
|
||||
uint8_t dest = (i == -1 ? EMS_ID_BOILER : EMS_Types[i].src); // default is Boiler
|
||||
// see if its a known type
|
||||
int i = ems_findType(type);
|
||||
// uint8_t dest = (i == -1 ? EMS_ID_BOILER : EMS_Types[i].src); // default is Boiler
|
||||
|
||||
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
|
||||
if ((ems_getLogging() == EMS_SYS_LOGGING_BASIC) || (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE)) {
|
||||
if (i == -1) {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,17 +859,33 @@ void ems_setThermostatTemp(float temperature) {
|
||||
if (_checkWriteQueueFull())
|
||||
return; // check if there is already something in the queue
|
||||
|
||||
myDebug("Setting new thermostat temperature\n");
|
||||
EMS_TxTelegram.action = EMS_TX_WRITE;
|
||||
EMS_TxTelegram.dest = EMS_ID_THERMOSTAT;
|
||||
|
||||
EMS_TxTelegram.action = EMS_TX_WRITE;
|
||||
EMS_TxTelegram.dest = EMS_ID_THERMOSTAT;
|
||||
EMS_TxTelegram.type = EMS_TYPE_RC20Temperature;
|
||||
EMS_TxTelegram.offset = EMS_OFFSET_RC20Temperature_temp;
|
||||
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
|
||||
EMS_TxTelegram.checkValue = (uint8_t)((float)temperature * (float)2); // value to compare against. must be a single int
|
||||
if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_RC20) {
|
||||
myDebug("Setting new thermostat temperature\n");
|
||||
|
||||
// RC20
|
||||
EMS_TxTelegram.type = EMS_TYPE_RC20Temperature;
|
||||
EMS_TxTelegram.offset = EMS_OFFSET_RC20Temperature_temp;
|
||||
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
|
||||
EMS_TxTelegram.checkValue =
|
||||
(uint8_t)((float)temperature * (float)2); // value to compare against. must be a single int
|
||||
|
||||
// post call is back to EMS_TYPE_RC20Temperature to fetch temps and send to HA
|
||||
EMS_TxTelegram.type_validate = EMS_OFFSET_RC20Temperature_temp;
|
||||
|
||||
} else if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_EASY) {
|
||||
myDebug("Setting new thermostat temperature on an Easy - not working\n");
|
||||
|
||||
EMS_TxTelegram.type = EMS_TYPE_EasyTemperature;
|
||||
EMS_TxTelegram.offset = 11;
|
||||
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
|
||||
EMS_TxTelegram.checkValue = 0;
|
||||
|
||||
EMS_TxTelegram.type_validate = EMS_ID_NONE;
|
||||
}
|
||||
|
||||
// post call is back to EMS_TYPE_RC20Temperature to fetch temps and send to HA
|
||||
EMS_TxTelegram.type_validate = EMS_OFFSET_RC20Temperature_temp;
|
||||
_buildTxTelegram(EMS_TxTelegram.checkValue);
|
||||
}
|
||||
|
||||
@@ -902,15 +959,34 @@ 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_READ; // read command
|
||||
EMS_TxTelegram.dest = EMS_ID_THERMOSTAT | 0x80; // set 7th bit to indicate a read
|
||||
EMS_TxTelegram.offset = 0; // 0 for all data
|
||||
EMS_TxTelegram.length = 8;
|
||||
EMS_TxTelegram.type = 0xF0;
|
||||
|
||||
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.data[0] = EMS_ID_ME; // src
|
||||
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; // dest
|
||||
EMS_TxTelegram.data[2] = EMS_TxTelegram.type; // type
|
||||
EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; //offset
|
||||
|
||||
EMS_TxTelegram.checkValue = value;
|
||||
_buildTxTelegram(value);
|
||||
// EMS Plus test
|
||||
// Sending read to 0x18: telegram: 0B 98 F0 00 01 B9 63 DB (len 8)
|
||||
|
||||
EMS_TxTelegram.data[0] = EMS_ID_ME; // src
|
||||
EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; // dest
|
||||
EMS_TxTelegram.data[2] = 0xF0; // marker
|
||||
EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; // offset
|
||||
EMS_TxTelegram.data[4] = 0x01; // hi byte
|
||||
EMS_TxTelegram.data[5] = 0xB9; // low byte
|
||||
|
||||
// data:
|
||||
EMS_TxTelegram.data[6] = 99; // max length
|
||||
|
||||
// crc:
|
||||
EMS_TxTelegram.data[7] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length);
|
||||
|
||||
EMS_Sys_Status.emsTxStatus = EMS_TX_PENDING; // armed and ready to send
|
||||
*/
|
||||
}
|
||||
|
||||
14
src/ems.h
14
src/ems.h
@@ -44,7 +44,8 @@
|
||||
#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_Version 0x02 // version of the UBA controller
|
||||
#define EMS_TYPE_EasyTemperature 0x0A // reading values on an Easy Thermostat
|
||||
#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler)
|
||||
|
||||
// Offsets for specific values in a telegram, per type, used for validation
|
||||
#define EMS_OFFSET_RC20Temperature_temp 0x1C // thermostat set temp
|
||||
@@ -77,8 +78,13 @@ typedef enum {
|
||||
EMS_TX_VALIDATE // do a validate after a write
|
||||
} _EMS_TX_ACTION;
|
||||
|
||||
/* EMS UART logging */
|
||||
typedef enum { EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC, EMS_SYS_LOGGING_VERBOSE } _EMS_SYS_LOGGING;
|
||||
/* EMS logging */
|
||||
typedef enum {
|
||||
EMS_SYS_LOGGING_NONE, // no messages
|
||||
EMS_SYS_LOGGING_BASIC, // only basic read/write messages
|
||||
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
|
||||
EMS_SYS_LOGGING_VERBOSE // everything
|
||||
} _EMS_SYS_LOGGING;
|
||||
|
||||
// status/counters since last power on
|
||||
typedef struct {
|
||||
@@ -194,7 +200,7 @@ typedef struct {
|
||||
// function definitions
|
||||
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
||||
void ems_init();
|
||||
void ems_doReadCommand(uint8_t type);
|
||||
void ems_doReadCommand(uint8_t type, uint8_t dest);
|
||||
|
||||
void ems_setThermostatTemp(float temp);
|
||||
void ems_setThermostatMode(uint8_t mode);
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#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_RC20 // your thermostat ID
|
||||
#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
|
||||
|
||||
Reference in New Issue
Block a user