small improvements

This commit is contained in:
proddy
2018-12-11 22:39:06 +01:00
parent b01a411dd3
commit 13603d63c6
11 changed files with 295 additions and 146 deletions

View File

@@ -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();

View File

@@ -74,7 +74,7 @@ typedef struct {
} subscription;
typedef struct {
char key[5];
char key[10];
char description[400];
} command_t;

View File

@@ -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;

View File

@@ -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
*/
}

View File

@@ -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);

View File

@@ -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