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

@@ -72,7 +72,7 @@ Use the telnet client to inform you of all activity and errors real-time. This i
![Telnet](doc/telnet/telnet_example.jpg) ![Telnet](doc/telnet/telnet_example.jpg)
If you type 'v 2' and Enter, it will toggle verbose logging showing you more detailed messages. I use ANSI colors with white text for info messages, green for well formatted telegram packages (which have validated CRC checks), red for corrupt packages and yellow for send responses. If you type 'v 3' and Enter, it will toggle verbose logging showing you more detailed messages. I use ANSI colors with white text for info messages, green for well formatted telegram packages (which have validated CRC checks), red for corrupt packages and yellow for send responses.
![Telnet](doc/telnet/telnet_verbose.PNG) ![Telnet](doc/telnet/telnet_verbose.PNG)
@@ -81,15 +81,16 @@ To see the current values of the Boiler and its parameters type 's' and hit Ente
Commands can be issued directly to the EMS bus typing in a letter followed by an optional parameter and pressing Enter. Supported commands are: Commands can be issued directly to the EMS bus typing in a letter followed by an optional parameter and pressing Enter. Supported commands are:
- **r** to send a read command to all devices to fetch values. The 2nd parameter is the type. For example 'r 33' will request type UBAParameterWW and bring back the Warm Water temperatures (not the heating) from the Boiler. You can issue any type here. - **r** to send a read command to the boiler. The 2nd parameter is the type. For example 'b 33' will request type UBAParameterWW and bring back the Warm Water temperatures from the Boiler.
- **t** set the thermostat temperature to the given celsius value - **t** is similar, but to send a read command to the thermostat.
- **T** set the thermostat temperature to the given celsius value
- **w** to adjust the temperature of the warm water from the boiler - **w** to adjust the temperature of the warm water from the boiler
- **a** to turn the warm water on and off - **a** to turn the warm water on and off
- **h** to list all the recognized EMS types - **h** to list all the recognized EMS types
- **p** to toggle the Polling response on/off. It's not necessary to have Polling enabled to work. I use this for debugging purposes. - **p** to toggle the Polling response on/off (note it's not necessary to have Polling enabled to work)
- **m** to set the thermostat mode to manual or auto. - **m** to set the thermostat mode to manual or auto
- **T** to toggle thermostat readings on/off
- **S** to toggle the Shower Timer functionality on/off - **S** to toggle the Shower Timer functionality on/off
- **A** to toggle the Shower Timer Alert functionality on/off
**Disclaimer: be careful when sending values to the boiler. If in doubt you can always reset the boiler to its original factory settings by following the instructions in the user guide. On my **Nefit Trendline HRC30** that is done by holding down the Home and Menu buttons simultaneously for a few seconds, selecting factory settings from the scroll menu and lastly pressing the Reset button.** **Disclaimer: be careful when sending values to the boiler. If in doubt you can always reset the boiler to its original factory settings by following the instructions in the user guide. On my **Nefit Trendline HRC30** that is done by holding down the Home and Menu buttons simultaneously for a few seconds, selecting factory settings from the scroll menu and lastly pressing the Reset button.**
@@ -144,7 +145,7 @@ Each device has a unique ID.
The Boiler has an ID of 0x08 (type MC10) and also referred to as the Bus Master or UBA. The Boiler has an ID of 0x08 (type MC10) and also referred to as the Bus Master or UBA.
My thermostat, which is a* Moduline 300* uses the RC20 protocol and has an ID 0x17. If you're using an RC30 or RC35 type thermostat such as the newer Moduline 300s or 400s use 0x10 and make adjustments in the code as appropriate. bbqkees did a nice write-up on his github page [here](https://github.com/bbqkees/Nefit-Buderus-EMS-bus-Arduino-Domoticz/blob/master/README.md). My thermostat, which is a* Moduline 300* uses the RC30 protocol and has an ID 0x17. If you're using a RC35 type thermostat such as the newer Moduline 300s or 400s use 0x10 and make adjustments in the code as appropriate. bbqkees did a nice write-up on his github page [here](https://github.com/bbqkees/Nefit-Buderus-EMS-bus-Arduino-Domoticz/blob/master/README.md).
Our circuit acts as a service key and thus uses an ID 0x0B. This ID is reserved for special devices intended for installation engineers for maintenance work. Our circuit acts as a service key and thus uses an ID 0x0B. This ID is reserved for special devices intended for installation engineers for maintenance work.
@@ -183,7 +184,7 @@ Refer to the code in `ems.cpp` for further explanation on how to parse these mes
Telegram packets can only be sent after the Boiler sends a poll to the sending device. The response can be a read command to request data or a write command to send data. At the end of the transmission a poll response is sent from the client (`<ID> <BRK>`) to say we're all done and free up the bus for other clients. Telegram packets can only be sent after the Boiler sends a poll to the sending device. The response can be a read command to request data or a write command to send data. At the end of the transmission a poll response is sent from the client (`<ID> <BRK>`) to say we're all done and free up the bus for other clients.
When doing a request to read data the `[src]` is our device (0x0B) and the `[dest]` has it's 7-bit set. Say we were requesting data from the thermostat we would use `[dest] = 0x97` since RC20 has an ID of 0x17. When doing a request to read data the `[src]` is our device (0x0B) and the `[dest]` has it's 7-bit set. Say we were requesting data from the thermostat we would use `[dest] = 0x97` since RC30 has an ID of 0x17.
When doing a write request, the 7th bit is masked in the `[dest]`. After this write request the destination device will send either a single byte 0x01 for success or 0x04 for fail. When doing a write request, the 7th bit is masked in the `[dest]`. After this write request the destination device will send either a single byte 0x01 for success or 0x04 for fail.
@@ -218,23 +219,26 @@ The code is built on the Arduino framework and is dependent on these external li
| Boiler (0x08) | 0x14 | UBATotalUptimeMessage | | | Boiler (0x08) | 0x14 | UBATotalUptimeMessage | |
| Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | | | Boiler (0x08) | 0x15 | UBAMaintenanceSettingsMessage | |
| Boiler (0x08) | 0x16 | UBAParametersMessage | | | Boiler (0x08) | 0x16 | UBAParametersMessage | |
| Thermostat (0x17) | 0xA8 | RC20Temperature | sets operating modes | | Thermostat (0x17) | 0xA8 | RC20Temperature | sets operating modes for a RC20 & RC30 |
| Thermostat (0x17) | 0x02 | Version | reads Version major/minor | | Thermostat (0x17) | 0x02 | Version | reads Version major/minor |
| Thermostat (0x18) | 0x0A | EasyTemperature | thermostat monitor for an TC100/Easy |
In `boiler.ino` you can make calls to automatically send these read commands. See the function *regularUpdates()* In `boiler.ino` you can make calls to automatically send these read commands. See the function *regularUpdates()*
#### Supporting other Thermostats types #### Supporting other Thermostats types
The code is originally designed for a Moduline300 (RC20) thermostat. The code is originally designed for a Moduline300 (RC30) thermostat.
To adjust for a RC35 first change `EMS_ID_THERMOSTAT` in `ems.cpp`. A RC35 thermostat has 4 heating circuits and to read the values use different Monitor type IDs (e.g. 0x3E, 0x48, etc). The mode (0=night, 1=day, 2=holiday) is the first byte of the telegram and the temperature is the value of the 2nd byte divided by 2. Then to set temperature values use the Working Mode with type IDs (0x3D, 0x47,0x51 and 0x5B) respectively. Set the offset (byte 4 of the header) to determine which temperature you're changing; 1 for night, 2 for day and 3 for holiday. The data value is the desired temperature multiplied by 2 as a single byte. To adjust for a RC35 first change `EMS_ID_THERMOSTAT` in `ems.cpp`. A RC35 thermostat has 4 heating circuits and to read the values use different Monitor type IDs (e.g. 0x3E, 0x48, etc). The mode (0=night, 1=day, 2=holiday) is the first byte of the telegram and the temperature is the value of the 2nd byte divided by 2. Then to set temperature values use the Working Mode with type IDs (0x3D, 0x47,0x51 and 0x5B) respectively. Set the offset (byte 4 of the header) to determine which temperature you're changing; 1 for night, 2 for day and 3 for holiday. The data value is the desired temperature multiplied by 2 as a single byte.
I will add further support for the other thermostats (such as the Nefit Easy) as soon as I can get my hands on a physical device. I do however welcome contribtions to this code repository which is essentially the purpose of GitHub. By inspecting the telegram packets and looking up the codes in the German wiki (and with lots of trial and error) it is possible to easily extend the existing functions to support other EMS devices. There is limited support for an Nefit Easy TC100) thermostat. The current room temperature and setpoint temperature can be read. What still needs fixing is
- reading the mode (manual vs clock)
- setting the temperature
### Customizing The Code ### Customizing The Code
- To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can - To configure for your thermostat and specific boiler settings, modify `my_config.h`. Here you can
- set the thermostat type. The default ID is 0x17 for an RC20 Moduline 300. - set the thermostat type. The default ID is 0x17 for an RC30 Moduline 300.
- set flags for enabled/disabling functionality such as `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`. - set flags for enabled/disabling functionality such as `BOILER_THERMOSTAT_ENABLED`, `BOILER_SHOWER_ENABLED` and `BOILER_SHOWER_TIMER`.
- Set WIFI and MQTT settings, instead of doing this in `platformio.ini` - Set WIFI and MQTT settings, instead of doing this in `platformio.ini`
- To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h` - To add new handlers for EMS data types, first create a callback function and add to the `EMS_Types` array at the top of the file `ems.cpp` and modify `ems.h`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

@@ -455,7 +455,7 @@ void _boilerLoop() {
last_boilersend = millis(); last_boilersend = millis();
// get 0x33 WW values manually // get 0x33 WW values manually
ems_doReadCommand(EMS_TYPE_UBAParameterWW); ems_doReadCommand(EMS_TYPE_UBAParameterWW, EMS_ID_BOILER);
#if MQTT_SUPPORT #if MQTT_SUPPORT
// send MQTT // send MQTT

View File

@@ -666,7 +666,7 @@ void ESPHelper::consoleHandle() {
// Set callback of sketch function to process project messages // Set callback of sketch function to process project messages
void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) { void ESPHelper::consoleSetCallBackProjectCmds(command_t * cmds, uint8_t count, void (*callback)()) {
_helpProjectCmds = cmds; // command list _helpProjectCmds = cmds; // command list
_helpProjectCmds_count = count; // numiber of commands _helpProjectCmds_count = count; // number of commands
_consoleCallbackProjectCmds = callback; // external function to handle commands _consoleCallbackProjectCmds = callback; // external function to handle commands
} }
@@ -742,26 +742,29 @@ size_t ESPHelper::write(uint8_t character) {
// Show help of commands // Show help of commands
void ESPHelper::consoleShowHelp() { void ESPHelper::consoleShowHelp() {
String help = "********************************\n\r* Remote Telnet Command Center " String help = "**********************************************\n\r* Remote Telnet Command Center & Log Monitor "
"*\n\r********************************\n\r"; "*\n\r**********************************************\n\r";
help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString() help += "* Device hostname: " + WiFi.hostname() + "\tIP: " + WiFi.localIP().toString()
+ "\tMAC address: " + WiFi.macAddress() + "\n\r"; + "\tMAC address: " + WiFi.macAddress() + "\n\r";
help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r"; help += "* Connected to WiFi AP: " + WiFi.SSID() + "\n\r";
help += "* Boot time: "; help += "* Boot time: ";
help.concat(_boottime); help.concat(_boottime);
help += "\n\r* Free Heap RAM: "; help += "\n\r* Free RAM: ";
help.concat(ESP.getFreeHeap()); help.concat(ESP.getFreeHeap());
help += " bytes\n\r"; 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"; "notifications\n\r";
char s[100];
// print custom commands if available // print custom commands if available
if (_consoleCallbackProjectCmds) { if (_consoleCallbackProjectCmds) {
for (uint8_t i = 0; i < _helpProjectCmds_count; i++) { for (uint8_t i = 0; i < _helpProjectCmds_count; i++) {
//for (uint8_t i = 0; i < 5; i++) {
help += FPSTR("* "); help += FPSTR("* ");
help += FPSTR(_helpProjectCmds[i].key); 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(_helpProjectCmds[i].description);
help += FPSTR("\n\r"); help += FPSTR("\n\r");
} }
@@ -812,7 +815,7 @@ void ESPHelper::consoleProcessCommand() {
telnetClient.println("* Closing telnet connection..."); telnetClient.println("* Closing telnet connection...");
telnetClient.stop(); telnetClient.stop();
} else if (cmd == '$') { } else if (cmd == '$') {
telnetClient.print("* Free Heap RAM (bytes): "); telnetClient.print("* Free RAM (bytes): ");
telnetClient.println(ESP.getFreeHeap()); telnetClient.println(ESP.getFreeHeap());
} else if (cmd == '!') { } else if (cmd == '!') {
resetESP(); resetESP();

View File

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

View File

@@ -21,18 +21,28 @@
// timers, all values are in seconds // timers, all values are in seconds
#define PUBLISHVALUES_TIME 300 // every 5 mins post HA values #define PUBLISHVALUES_TIME 300 // every 5 mins post HA values
#define SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests Ticker publishValuesTimer;
#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 SYSTEMCHECK_TIME 10 // every 10 seconds check if Boiler is online and execute other requests
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates) 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; Ticker showerColdShotStopTimer;
uint8_t regularUpdatesCount = 0; uint8_t regularUpdatesCount = 0;
#define MAX_MANUAL_CALLS 2 // number of ems reads we do during the fetch cycle (in regularUpdates)
// GPIOs // GPIOs
#define LED_HEARTBEAT LED_BUILTIN // onboard LED #define LED_HEARTBEAT LED_BUILTIN // onboard LED
@@ -108,20 +118,24 @@ netInfo homeNet = {.mqttHost = MQTT_IP,
ESPHelper myESP(&homeNet); ESPHelper myESP(&homeNet);
command_t PROGMEM project_cmds[] = {{"s", "show statistics"}, command_t PROGMEM project_cmds[] = {
{"h", "list EMS telegram type ids with supported logic"},
{"P", "publish all stat to MQTT"}, {"v [n]", "set logging (0=none, 1=basic, 2=thermostat only, 3=verbose)"},
{"v", "[n] set logging (0=none, 1=basic, 2=verbose)"}, {"s", "show statistics"},
{"p", "toggle EMS Poll response on/off"}, {"h", "list supported EMS telegram type IDs"},
{"T", "toggle Thermostat monitoring on/off"}, {"P", "publish all stat to MQTT"},
{"S", "toggle Shower timer on/off"}, {"p", "toggle EMS Poll response on/off"},
{"A", "toggle shower Alert on/off"}, {"S", "toggle Shower timer on/off"},
{"r", "[n] send EMS request (n=any telegram type id. Use 'h' for suppported types)"}, {"A", "toggle shower Alert on/off"},
{"t", "[n] set thermostat temperature"}, {"b [xx]", "boiler request (xx=telegram type ID)"},
{"m", "[n] set thermostat mode (1=manual, 2=auto)"}, {"w [nn]", "set boiler warm water temperature (min 30)"},
{"w", "[n] set boiler warm water temperature (min 30)"}, {"a [n]", "boiler warm water (1=on, 2=off)"},
{"a", "[n] boiler warm water (1=on, 2=off)"}, {"t [xx]", "thermostat request (xx=telegram type ID)"},
{"x", "[n] experimental (warning: for debugging only!)"}}; {"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 // calculates size of an 2d array at compile time
template <typename T, size_t N> template <typename T, size_t N>
@@ -244,12 +258,14 @@ void showInfo() {
// General stats from EMS bus // General stats from EMS bus
myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug("%sEMS-ESP-Boiler system stats:%s\n", COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug(" System Logging is set to "); myDebug(" System logging is set to ");
_EMS_SYS_LOGGING sysLog = ems_getLogging(); _EMS_SYS_LOGGING sysLog = ems_getLogging();
if (sysLog == EMS_SYS_LOGGING_BASIC) { if (sysLog == EMS_SYS_LOGGING_BASIC) {
myDebug("Basic"); myDebug("Basic");
} else if (sysLog == EMS_SYS_LOGGING_VERBOSE) { } else if (sysLog == EMS_SYS_LOGGING_VERBOSE) {
myDebug("Verbose"); myDebug("Verbose");
} else if (sysLog == EMS_SYS_LOGGING_THERMOSTAT) {
myDebug("Thermostat only");
} else { } else {
myDebug("None"); myDebug("None");
} }
@@ -432,7 +448,7 @@ void publishValues(bool force) {
if ((previousBoilerPublishCRC != checksum) || force) { if ((previousBoilerPublishCRC != checksum) || force) {
previousBoilerPublishCRC = checksum; previousBoilerPublishCRC = checksum;
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
myDebug("Publishing boiler data via MQTT\n"); 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 // 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 // last_boilerActive stores heating in bit 1 and tap water in bit 2
if (last_boilerActive != ((EMS_Boiler.tapwaterActive << 1) + EMS_Boiler.heatingActive)) { 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"); myDebug("Publishing hot water and heating state via MQTT\n");
} }
myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0"); myESP.publish(TOPIC_BOILER_TAPWATER_ACTIVE, EMS_Boiler.tapwaterActive == 1 ? "1" : "0");
@@ -486,7 +502,7 @@ void publishValues(bool force) {
if ((previousThermostatPublishCRC != checksum) || force) { if ((previousThermostatPublishCRC != checksum) || force) {
previousThermostatPublishCRC = checksum; previousThermostatPublishCRC = checksum;
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { if (ems_getLogging() == EMS_SYS_LOGGING_VERBOSE) {
myDebug("Publishing thermostat data via MQTT\n"); myDebug("Publishing thermostat data via MQTT\n");
} }
@@ -512,30 +528,50 @@ void set_showerAlert() {
// extra commands options for telnet debug window // extra commands options for telnet debug window
void myDebugCallback() { void myDebugCallback() {
char * cmd = myESP.consoleGetLastCommand(); char * cmd = myESP.consoleGetLastCommand();
bool b; 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]) { switch (cmd[0]) {
case 's': case 'T': // set thermostat temp
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
ems_setThermostatTemp(strtof(&cmd[2], 0)); ems_setThermostatTemp(strtof(&cmd[2], 0));
break; break;
case 'h': // show type handlers
ems_printAllTypes();
break;
case 'm': // set thermostat mode case 'm': // set thermostat mode
if ((cmd[2] - '0') == 1) if ((cmd[2] - '0') == 1)
ems_setThermostatMode(1); ems_setThermostatMode(1);
@@ -555,26 +591,31 @@ void myDebugCallback() {
else if ((cmd[2] - '0') == 0) else if ((cmd[2] - '0') == 0)
ems_setWarmWaterActivated(false); ems_setWarmWaterActivated(false);
break; break;
case 'x': // experimental code for debugging - use with caution! 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 ems_setExperimental((uint8_t)strtol(&cmd[2], 0, 16)); // takes HEX param
break; break;
case 'T': // toggle Thermostat case 'U': // thermostat scan
b = !ems_getThermostatEnabled(); myDebug("Doing a scan on thermostat IDs\n");
ems_setThermostatEnabled(b); ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT);
Boiler_Status.thermostat_enabled = b; publishValuesTimer.detach();
break; systemCheckTimer.detach();
case 'S': // toggle Shower timer support regularUpdatesTimer.detach();
Boiler_Status.shower_timer = !Boiler_Status.shower_timer; scanThermostat_count = (uint8_t)strtol(&cmd[2], 0, 16);
myESP.publish(TOPIC_SHOWER_TIMER, Boiler_Status.shower_timer ? "1" : "0"); scanThermostat.attach(SCANTHERMOSTAT_TIME, do_scanThermostat);
break;
case 'A': // toggle Shower alert
Boiler_Status.shower_alert = !Boiler_Status.shower_alert;
myESP.publish(TOPIC_SHOWER_ALERT, Boiler_Status.shower_alert ? "1" : "0");
break; break;
default: default:
myDebug("Unknown command '%c'. Use ? for help.\n", cmd[0]); myDebug("Unknown command. Use ? for help.\n");
break; break;
} }
return;
} }
// MQTT Callback to handle incoming/outgoing changes // 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 // do a healthcheck every now and then to see if we connections
void do_systemCheck() { void do_systemCheck() {
// first do a system check to see if there is still a connection to the EMS // 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 // force get the thermostat data which are not usually automatically broadcasted
ems_getThermostatTemps(); ems_getThermostatTemps();
} else if (cycle == 1) { } 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 #ifndef NO_TX
if (Boiler_Status.boiler_online) { if (Boiler_Status.boiler_online) {
// now that we're connected lets get some data from the EMS // 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 ems_setWarmWaterActivated(true); // make sure warm water if activated, in case it got stuck with the shower alert
} else { } else {
myDebugLog("Boot: can't connect to EMS."); myDebugLog("Boot: can't connect to EMS.");
@@ -911,20 +959,27 @@ void loop() {
Boiler_Shower.doingColdShot = false; Boiler_Shower.doingColdShot = false;
Boiler_Shower.duration = 0; Boiler_Shower.duration = 0;
Boiler_Shower.showerOn = false; Boiler_Shower.showerOn = false;
#ifdef SHOWER_TEST
myDebugLog("Shower: hot water on..."); myDebugLog("Shower: hot water on...");
#endif
} else { } else {
// hot water has been on for a while // 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 // 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) { if (!Boiler_Shower.showerOn && (timestamp - Boiler_Shower.timerStart) > SHOWER_MIN_DURATION) {
Boiler_Shower.showerOn = true; Boiler_Shower.showerOn = true;
#ifdef SHOWER_TEST
myDebugLog("Shower: hot water still running, starting shower timer"); myDebugLog("Shower: hot water still running, starting shower timer");
#endif
} }
// check if the shower has been on too long // check if the shower has been on too long
else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION) else if ((((timestamp - Boiler_Shower.timerStart) > SHOWER_MAX_DURATION)
&& !Boiler_Shower.doingColdShot) && !Boiler_Shower.doingColdShot)
&& Boiler_Status.shower_alert) { && Boiler_Status.shower_alert) {
myESP.sendHACommand(TOPIC_SHOWER_ALARM); myESP.sendHACommand(TOPIC_SHOWER_ALARM);
#ifdef SHOWER_TEST
myDebugLog("Shower: exceeded max shower time"); myDebugLog("Shower: exceeded max shower time");
#endif
_showerColdShotStart(); _showerColdShotStart();
} }
} }
@@ -932,7 +987,9 @@ void loop() {
// if it just turned off, record the time as it could be a short pause // if it just turned off, record the time as it could be a short pause
if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) { if ((Boiler_Shower.timerStart != 0) && (Boiler_Shower.timerPause == 0)) {
Boiler_Shower.timerPause = timestamp; Boiler_Shower.timerPause = timestamp;
#ifdef SHOWER_TEST
myDebugLog("Shower: hot water turned off"); myDebugLog("Shower: hot water turned off");
#endif
} }
// if shower has been off for longer than the wait time // if shower has been off for longer than the wait time
@@ -964,8 +1021,10 @@ void loop() {
} }
} }
#ifdef SHOWER_TEST
// reset everything // reset everything
myDebugLog("Shower: resetting timers"); myDebugLog("Shower: resetting timers");
#endif
Boiler_Shower.timerStart = 0; Boiler_Shower.timerStart = 0;
Boiler_Shower.timerPause = 0; Boiler_Shower.timerPause = 0;
Boiler_Shower.showerOn = false; 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_RCTempMessage(uint8_t * data, uint8_t length);
bool _process_Version(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_SetPoints(uint8_t * data, uint8_t length);
bool _process_EasyTemperature(uint8_t * data, uint8_t length);
const _EMS_Types EMS_Types[] = { 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_RC20StatusMessage, "RC20StatusMessage", _process_RC20StatusMessage},
{EMS_ID_THERMOSTAT, EMS_TYPE_RCTime, "RCTime", _process_RCTime}, {EMS_ID_THERMOSTAT, EMS_TYPE_RCTime, "RCTime", _process_RCTime},
{EMS_ID_THERMOSTAT, EMS_TYPE_RC20Temperature, "RC20Temperature", _process_RC20Temperature}, {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_RCTempMessage, "RCTempMessage", _process_RCTempMessage},
{EMS_ID_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version}, {EMS_ID_THERMOSTAT, EMS_TYPE_Version, "Version", _process_Version},
{EMS_ID_THERMOSTAT, EMS_TYPE_UBASetPoints, "UBASetPoints", _process_SetPoints} {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 EMS_Sys_Status.emsLogging = EMS_SYS_LOGGING_NONE; // Verbose logging is off
// thermostat // 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.hour = 0;
EMS_Thermostat.minute = 0; EMS_Thermostat.minute = 0;
EMS_Thermostat.second = 0; EMS_Thermostat.second = 0;
@@ -206,12 +208,14 @@ void ems_setLogging(_EMS_SYS_LOGGING loglevel) {
if (loglevel <= EMS_SYS_LOGGING_VERBOSE) { if (loglevel <= EMS_SYS_LOGGING_VERBOSE) {
EMS_Sys_Status.emsLogging = loglevel; EMS_Sys_Status.emsLogging = loglevel;
myDebug("System Logging is set to "); 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"); myDebug("Basic\n");
} else if (loglevel == EMS_SYS_LOGGING_VERBOSE) { } else if (loglevel == EMS_SYS_LOGGING_VERBOSE) {
myDebug("Verbose\n"); myDebug("Verbose\n");
} else { } else if (loglevel == EMS_SYS_LOGGING_THERMOSTAT) {
myDebug("None\n"); myDebug("Thermostat only\n");
} }
} }
} }
@@ -247,10 +251,10 @@ uint16_t _toLong(uint8_t i, uint8_t * data) {
// debugging only - print out all handled types // debugging only - print out all handled types
void ems_printAllTypes() { 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++) { 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].src == EMS_ID_THERMOSTAT ? "Thermostat" : "Boiler",
EMS_Types[i].type, EMS_Types[i].type,
EMS_Types[i].typeString); 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) if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_VERBOSE)
return; return;
myDebug("%s%s len=%d, telegram: ", color, prefix, len); myDebug("%s%s telegram: ", color, prefix);
for (int i = 0; i < len; i++) { 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 // send the contents of the Tx buffer
void _ems_sendTelegram() { void _ems_sendTelegram() {
// only send when Tx is not busy // only send when Tx is not busy
_debugPrintTelegram(((EMS_TxTelegram.action == EMS_TX_WRITE) ? "Sending write telegram:" : "Sending read telegram:"), char s[50];
EMS_TxTelegram.data,
EMS_TxTelegram.length, if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) {
COLOR_CYAN); 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; EMS_Sys_Status.emsTxStatus = EMS_TX_ACTIVE;
emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length); emsuart_tx_buffer(EMS_TxTelegram.data, EMS_TxTelegram.length);
@@ -335,7 +345,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
_initTxBuffer(); _initTxBuffer();
} else { } else {
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) { 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, EMS_TxTelegram.type,
emsLastRxCount, emsLastRxCount,
RX_READ_TIMEOUT_COUNT); 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 // ignore anything that doesn't resemble a proper telegram package
// minimal is 5 bytes, excluding CRC at the end // minimal is 5 bytes, excluding CRC at the end
if ((length < 5)) { if (length < 5) {
_debugPrintTelegram("Noisy data:", telegram, length, COLOR_MAGENTA); _debugPrintTelegram("Noisy data:", telegram, length, COLOR_RED);
return; return;
} }
@@ -410,7 +420,8 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
*/ */
void _processType(uint8_t * telegram, uint8_t length) { void _processType(uint8_t * telegram, uint8_t length) {
// extract the 4-byte header information // 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 its an echo of ourselves from the master, ignore
if (src == EMS_ID_ME) { if (src == EMS_ID_ME) {
@@ -419,7 +430,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
} }
// header // header
uint8_t dest = telegram[1]; uint8_t dest = telegram[1] & 0x7F; // remove 8th bit
uint8_t type = telegram[2]; uint8_t type = telegram[2];
uint8_t * data = telegram + 4; // data block starts at position 5 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 // we have a match
typeFound = true; typeFound = true;
// call callback to fetch the values from the telegram // 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 // return value tells us if we need to force send values back to MQTT
if ((EMS_Types[i].processType_cb) != (void *)NULL) { if ((EMS_Types[i].processType_cb) != (void *)NULL) {
EMS_Sys_Status.emsRefreshed = EMS_Types[i].processType_cb(data, length); 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 // 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 color_s[20];
char src_s[20]; char src_s[20];
char dest_s[20]; char dest_s[20];
@@ -466,32 +487,38 @@ void _processType(uint8_t * telegram, uint8_t length) {
// set source string // set source string
if (src == EMS_ID_BOILER) { if (src == EMS_ID_BOILER) {
strcpy(src_s, "Boiler"); strcpy(src_s, "Boiler -> ");
} else if (src == EMS_ID_THERMOSTAT) { } else if (src == EMS_ID_THERMOSTAT) {
strcpy(src_s, "Thermostat"); strcpy(src_s, "Thermostat -> ");
} else { } else {
strcpy(src_s, "<unknown>"); sprintf(src_s, "0x%02X -> ", src);
} }
// set destination string // set destination string
if (dest == EMS_ID_ME) { if (dest == EMS_ID_ME) {
strcpy(dest_s, "telegram for us"); strcpy(dest_s, "me");
strcpy(color_s, COLOR_YELLOW); strcpy(color_s, COLOR_YELLOW);
} else if (dest == EMS_ID_NONE) { } else if (dest == EMS_ID_NONE) {
// it's probably just a broadcast // it's probably just a broadcast
strcpy(dest_s, "broadcast"); strcpy(dest_s, "all");
strcpy(color_s, COLOR_GREEN); 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 { } else {
// for someone else sprintf(dest_s, "0x%02X", dest);
strcpy(dest_s, "(not for us)");
strcpy(color_s, COLOR_MAGENTA); strcpy(color_s, COLOR_MAGENTA);
} }
// and print // 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); _debugPrintTelegram(s, telegram, length, color_s);
if (typeFound) { if (typeFound) {
myDebug("<--- %s(0x%02x) received\n", EMS_Types[i].typeString, type); myDebug("<--- %s(0x%02X) received\n", EMS_Types[i].typeString, type);
} }
} }
@@ -521,13 +548,13 @@ void _processType(uint8_t * telegram, uint8_t length) {
// look up the ID and fetch string // look up the ID and fetch string
int i = ems_findType(EMS_TxTelegram.type); int i = ems_findType(EMS_TxTelegram.type);
if (i != -1) { 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, EMS_Types[i].typeString,
type, type,
EMS_TxTelegram.checkValue, EMS_TxTelegram.checkValue,
offset); offset);
} else { } 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]) { if (EMS_TxTelegram.checkValue == data[offset]) {
@@ -545,7 +572,7 @@ void _processType(uint8_t * telegram, uint8_t length) {
bool _checkWriteQueueFull() { bool _checkWriteQueueFull() {
if (EMS_Sys_Status.emsTxStatus == EMS_TX_PENDING) { // send is already pending if (EMS_Sys_Status.emsTxStatus == EMS_TX_PENDING) { // send is already pending
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) { 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); EMS_TxTelegram.type);
} }
return true; // something in queue 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 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) * RC20Temperature - type 0xa8 - for set temp value and mode from the RC20 thermostat (0x17)
* received only after requested * 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 hk_power = data[1];
uint8_t ww_power = data[2]; 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); 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() { void ems_getThermostatTemps() {
if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_RC20) { 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 * 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) * 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) if (type == EMS_TYPE_NONE)
return; // not a valid type, quit return; // not a valid type, quit
if (_checkWriteQueueFull()) if (_checkWriteQueueFull())
return; // check if there is already something in the queue return; // check if there is already something in the queue
int i = ems_findType(type); // see if its a known type
uint8_t dest = (i == -1 ? EMS_ID_BOILER : EMS_Types[i].src); // default is Boiler 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) { 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 { } 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()) if (_checkWriteQueueFull())
return; // check if there is already something in the queue 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; if (EMS_Thermostat.type == EMS_ID_THERMOSTAT_RC20) {
EMS_TxTelegram.dest = EMS_ID_THERMOSTAT; myDebug("Setting new thermostat temperature\n");
EMS_TxTelegram.type = EMS_TYPE_RC20Temperature;
EMS_TxTelegram.offset = EMS_OFFSET_RC20Temperature_temp; // RC20
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; EMS_TxTelegram.type = EMS_TYPE_RC20Temperature;
EMS_TxTelegram.checkValue = (uint8_t)((float)temperature * (float)2); // value to compare against. must be a single int 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); _buildTxTelegram(EMS_TxTelegram.checkValue);
} }
@@ -902,15 +959,34 @@ void ems_setExperimental(uint8_t value) {
if (_checkWriteQueueFull()) if (_checkWriteQueueFull())
return; // check if there is already something in the queue 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.data[0] = EMS_ID_ME; // src
EMS_TxTelegram.dest = EMS_ID_BOILER; EMS_TxTelegram.data[1] = EMS_TxTelegram.dest; // dest
EMS_TxTelegram.type = EMS_TYPE_UBAParameterWW; EMS_TxTelegram.data[2] = EMS_TxTelegram.type; // type
EMS_TxTelegram.offset = 6; EMS_TxTelegram.data[3] = EMS_TxTelegram.offset; //offset
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
EMS_TxTelegram.type_validate = EMS_ID_NONE; // don't force a send to check the value but do it during next broadcast
EMS_TxTelegram.checkValue = value; // EMS Plus test
_buildTxTelegram(value); // 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_RCTime 0x06 // is an automatic thermostat broadcast
#define EMS_TYPE_RCTempMessage 0xA3 // is an automatic thermostat broadcast #define EMS_TYPE_RCTempMessage 0xA3 // is an automatic thermostat broadcast
#define EMS_TYPE_RC20Temperature 0xA8 #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 // 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_temp 0x1C // thermostat set temp
@@ -77,8 +78,13 @@ typedef enum {
EMS_TX_VALIDATE // do a validate after a write EMS_TX_VALIDATE // do a validate after a write
} _EMS_TX_ACTION; } _EMS_TX_ACTION;
/* EMS UART logging */ /* EMS logging */
typedef enum { EMS_SYS_LOGGING_NONE, EMS_SYS_LOGGING_BASIC, EMS_SYS_LOGGING_VERBOSE } _EMS_SYS_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 // status/counters since last power on
typedef struct { typedef struct {
@@ -194,7 +200,7 @@ typedef struct {
// function definitions // function definitions
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len); extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
void ems_init(); 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_setThermostatTemp(float temp);
void ems_setThermostatMode(uint8_t mode); void ems_setThermostatMode(uint8_t mode);

View File

@@ -26,7 +26,8 @@
#define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded #define BOILER_SHOWER_ALERT 0 // send alert if showetime exceeded
// define here the Thermostat type. see ems.h for options // 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 // trigger settings to determine if hot tap water or the heating is active
#define EMS_BOILER_BURNPOWER_TAPWATER 100 #define EMS_BOILER_BURNPOWER_TAPWATER 100