changes to MQTT topics

This commit is contained in:
Paul
2019-10-12 13:12:00 +02:00
parent be593d7654
commit d3009495c7
9 changed files with 377 additions and 540 deletions

View File

@@ -8,14 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.9.2 beta]
#### Important! This build has breaking changes:
- the Thermostat MQTT topics are all always suffixed with the heat controller number, e.g. `thermostat_data1`
- the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm i` from within the `tools/webfilesbuilder` folder.
- MQTT topics have changed. Use `mqttlog` to see the names of the subscriptions and the format of the payload data
- the web builder has been upgraded to use Gulp 4. Remove `tools/webfilesbuilder/node_modules` and re-install the libraries using `npm ci` from within the `tools/webfilesbuilder` folder.
### Added
- Handling of MM100 Status Messages (thanks @kstaniek)
- Retrieve/display mode for Junkers FW100/120 thermostats (thanks @Neonox31)
- Added error fall-back for MQTT publishes that fail
- Added sending of Mixer Module data via MQTT (thanks @peclik)
- Reporting of MQTT publish and subscribe errors
### Fixed
@@ -27,11 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- External dallas sensor values sent in MQTT payload as float values and not strings
- All MQTT topics for the Thermostat have the Heating Circuit appended (e.g. `thermostat_data1`). This includes the commands.
- Shower timer and shower alert and not MQTT published at boot up
- Heating Active logic change to use Selected Flow Temp of min 30 instead of 70 (https://github.com/proddy/EMS-ESP/issues/193)
### Removed
- Removed telnet command `shower timer` and `shower alert` to toggle the switches
- Removed the heatingcircuit key/value from the MQTT Thermostat topic payload
## [1.9.1] 2019-10-05
@@ -40,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for multiple Heating Circuits - https://github.com/proddy/EMS-ESP/issues/162
- new `mqttlog` command also shows which MQTT topics it is subscribed too
- Optimized event log loading in web and added integrity checks on all config and log files during boot
- `autodetect quick`
- `autodetect quick` for detecting known devices from our database list
- `log_events` option, now optional to save the log events to SPIFFS
### Fixed

View File

@@ -1,37 +1,39 @@
- platform: mqtt
name: Thermostat
modes:
- "auto"
- "heat"
- "off"
- platform: mqtt
name: Thermostat
modes:
- "auto"
- "heat"
- "off"
mode_command_topic: "home/ems-esp/thermostat_cmd_mode1"
temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1"
mode_state_topic: "home/ems-esp/thermostat_data"
current_temperature_topic: "home/ems-esp/thermostat_data"
temperature_state_topic: "home/ems-esp/thermostat_data"
mode_state_topic: "home/ems-esp/thermostat_data1"
current_temperature_topic: "home/ems-esp/thermostat_data1"
temperature_state_topic: "home/ems-esp/thermostat_data1"
mode_state_template: "{{ value_json.hc1.mode }}"
current_temperature_template: "{{ value_json.hc1.currtemp }}"
temperature_state_template: "{{ value_json.hc1.seltemp }}"
temp_step: 0.5
- platform: mqtt
name: boiler
modes:
- "auto"
- "off"
min_temp: 40
max_temp: 60
temp_step: 1
current_temperature_topic: "home/ems-esp/boiler_data"
temperature_state_topic: "home/ems-esp/boiler_data"
mode_state_topic: "home/ems-esp/boiler_data"
temperature_command_topic: "home/ems-esp/thermostat_cmd_temp1"
mode_command_topic: "home/ems-esp/thermostat_cmd_mode1"
current_temperature_template: "{{ value_json.wWCurTmp }}"
temperature_state_template: "{{ value_json.wWSelTemp }}"
mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}"
mode_state_template: "{{ value_json.thermostat_mode }}"
current_temperature_template: "{{ value_json.thermostat_currtemp }}"
temperature_state_template: "{{ value_json.thermostat_seltemp }}"
temp_step: 0.5
- platform: mqtt
name: boiler
modes:
- "auto"
- "off"
min_temp: 40
max_temp: 60
temp_step: 1
current_temperature_topic: "home/ems-esp/boiler_data"
temperature_state_topic: "home/ems-esp/boiler_data"
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
current_temperature_template: "{{ value_json.wWCurTmp }}"
temperature_state_template: "{{ value_json.wWSelTemp }}"
mode_state_template: "{% if value_json.wWActivated == 'off' %} off {% else %} auto {% endif %}"
mode_state_topic: "home/ems-esp/boiler_data"
mode_command_topic: "home/ems-esp/wwactivated"
temperature_command_topic: "home/ems-esp/boiler_cmd_wwtemp"
mode_command_topic: "home/ems-esp/boiler_cmd_wwactivated"

View File

@@ -1,7 +1,8 @@
# ems-esp
shower_coldshot:
sequence:
- service: mqtt.publish
data_template:
topic: 'home/ems-esp/shower_coldshot'
payload: '1'
topic: 'home/ems-esp/generic_cmd'
payload: '{cmd:"coldshot"}'

View File

@@ -1,20 +1,20 @@
# EMS-ESP
- platform: mqtt
name: "Shower Timer"
state_topic: "home/ems-esp/shower_timer"
command_topic: "home/ems-esp/shower_timer"
payload_on: "1"
payload_off: "0"
optimistic: false
qos: 1
retain: false
state_topic: "home/ems-esp/shower_data"
value_template: "{{ value_json.timer }}"
command_topic: "home/ems-esp/shower_data"
payload_on: '{"timer":"1"}'
payload_off: '{"timer":"0"}'
state_on: "1"
state_off: "0"
- platform: mqtt
name: "Long Shower Alert"
state_topic: "home/ems-esp/shower_alert"
command_topic: "home/ems-esp/shower_alert"
payload_on: "1"
payload_off: "0"
optimistic: false
qos: 1
retain: false
state_topic: "home/ems-esp/shower_data"
value_template: "{{ value_json.alert }}"
command_topic: "home/ems-esp/shower_data"
payload_on: '{"alert":"1"}'
payload_off: '{"alert":"0"}'
state_on: "1"
state_off: "0"

View File

@@ -56,7 +56,7 @@ views:
- sensor.current_room_temperature
- sensor.dark_sky_temperature
- type: thermostat
entity: climate.thermostat
entity: climate.thermostat_hc1
- type: thermostat
name: WarmWater
entity: climate.boiler

View File

@@ -11,6 +11,7 @@
#include "MyESP.h"
#include "ems.h"
#include "ems_devices.h"
#include "ems_utils.h"
#include "emsuart.h"
#include "my_config.h"
#include "version.h"
@@ -32,10 +33,6 @@ DS18 ds18;
#define APP_URL "https://github.com/proddy/EMS-ESP"
#define APP_UPDATEURL "https://api.github.com/repos/proddy/EMS-ESP/releases/latest"
// macros for easy debugging
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1.
#define EMSESP_DELAY 0 // initially set to 0 for no delay. Change to 1 if getting WDT resets from wifi
@@ -160,231 +157,6 @@ void myDebugLog(const char * s) {
}
}
// convert float to char
char * _float_to_char(char * a, float f, uint8_t precision = 2) {
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
char * ret = a;
long whole = (long)f;
itoa(whole, a, 10);
while (*a != '\0')
a++;
*a++ = '.';
long decimal = abs((long)((f - whole) * p[precision]));
itoa(decimal, a, 10);
return ret;
}
// convert bool to text. bools are stored as bytes
char * _bool_to_char(char * s, uint8_t value) {
if (value == EMS_VALUE_INT_ON) {
strlcpy(s, "on", sizeof(s));
} else if (value == EMS_VALUE_INT_OFF) {
strlcpy(s, "off", sizeof(s));
} else {
strlcpy(s, "?", sizeof(s));
}
return s;
}
// convert short (two bytes) to text string
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c)
char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1) {
// remove errors or invalid values
if (value == EMS_VALUE_SHORT_NOTSET) {
strlcpy(s, "?", 10);
return (s);
}
// just print
if (decimals == 0) {
ltoa(value, s, 10);
return (s);
}
// do floating point
char s2[10] = {0};
// check for negative values
if (value < 0) {
strlcpy(s, "-", 10);
value *= -1; // convert to positive
}
if (decimals == 2) {
// divide by 2
strlcpy(s, ltoa(value / 2, s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
} else {
strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
}
return s;
}
// convert short (two bytes) to text string
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals = 1) {
// remove errors or invalid values
if (value == EMS_VALUE_USHORT_NOTSET) {
strlcpy(s, "?", 10);
return (s);
}
// just print
if (decimals == 0) {
ltoa(value, s, 10);
return (s);
}
// do floating point
char s2[10] = {0};
if (decimals == 2) {
// divide by 2
strlcpy(s, ltoa(value / 2, s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
} else {
strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10);
strlcat(s, ".", 10);
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
}
return s;
}
// takes a signed short value (2 bytes), converts to a fraction
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a unsigned short value (2 bytes), converts to a fraction
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _ushort_to_char(s, value, decimals), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// convert int (single byte) to text value
char * _int_to_char(char * s, uint8_t value, uint8_t div = 1) {
if (value == EMS_VALUE_INT_NOTSET) {
strlcpy(s, "?", sizeof(s));
return (s);
}
static char s2[5] = {0};
switch (div) {
case 1:
itoa(value, s, 10);
break;
case 2:
strlcpy(s, itoa(value >> 1, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, ((value & 0x01) ? "5" : "0"), 5);
break;
case 10:
strlcpy(s, itoa(value / 10, s2, 10), 5);
strlcat(s, ".", sizeof(s));
strlcat(s, itoa(value % 10, s2, 10), 5);
break;
default:
itoa(value, s, 10);
break;
}
return s;
}
// takes an int value (1 byte), converts to a fraction
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer));
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a long value at prints it to debug log
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value) {
static char buffer[200] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
if (value == EMS_VALUE_LONG_NOTSET) {
strlcat(buffer, "?", sizeof(buffer));
} else {
char s[20] = {0};
strlcat(buffer, ltoa(value, s, 10), sizeof(buffer));
}
if (postfix != nullptr) {
strlcat(buffer, " ", sizeof(buffer));
strlcat(buffer, postfix, sizeof(buffer));
}
myDebug(buffer);
}
// takes a bool value at prints it to debug log
void _renderBoolValue(const char * prefix, uint8_t value) {
static char buffer[200] = {0};
static char s[20] = {0};
strlcpy(buffer, " ", sizeof(buffer));
strlcat(buffer, prefix, sizeof(buffer));
strlcat(buffer, ": ", sizeof(buffer));
strlcat(buffer, _bool_to_char(s, value), sizeof(buffer));
myDebug(buffer);
}
// figures out the thermostat mode
// returns 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day
// hc_num is 1 to 4
@@ -657,9 +429,9 @@ void showInfo() {
}
// Render Termostat Mode, if we have a mode
uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day
uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day
if (thermoMode == 0) {
myDebug_P(PSTR(" Mode is set to low"));
myDebug_P(PSTR(" Mode is set to off"));
} else if (thermoMode == 1) {
myDebug_P(PSTR(" Mode is set to manual"));
} else if (thermoMode == 2) {
@@ -764,11 +536,11 @@ void publishValues(bool force) {
uint32_t fchecksum;
uint8_t jsonSize;
static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values
static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values
static uint32_t previousThermostatPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for thermostat values
static uint32_t previousMixingPublishCRC[EMS_THERMOSTAT_MAXHC]; // CRC check for mixing values
static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10)
static uint32_t previousSMPublishCRC = 0; // CRC check for Solar Module values (e.g. SM10)
JsonObject rootBoiler = doc.to<JsonObject>();
@@ -904,53 +676,57 @@ void publishValues(bool force) {
doc.clear();
JsonObject rootThermostat = doc.to<JsonObject>();
// rootThermostat[THERMOSTAT_HC] = _int_to_char(s, thermostat->hc); // heating circuit 1..4
// hc{1-4}
char hc[10];
strncpy(hc, THERMOSTAT_HC, sizeof(hc));
strncat(hc, _int_to_char(s, thermostat->hc), sizeof(hc));
JsonObject dataThermostat = rootThermostat.createNestedObject(hc);
// different logic depending on thermostat types
if (ems_getThermostatModel() == EMS_MODEL_EASY) {
if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100;
dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 100;
if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100;
dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 100;
} else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100)
|| (ems_getThermostatModel() == EMS_MODEL_FW120)) {
if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10;
dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 10;
if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10;
dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10;
} else {
if (thermostat->setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2;
dataThermostat[THERMOSTAT_SELTEMP] = (double)thermostat->setpoint_roomTemp / 2;
if (thermostat->curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
rootThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10;
dataThermostat[THERMOSTAT_CURRTEMP] = (double)thermostat->curr_roomTemp / 10;
if (thermostat->daytemp != EMS_VALUE_INT_NOTSET)
rootThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2;
dataThermostat[THERMOSTAT_DAYTEMP] = (double)thermostat->daytemp / 2;
if (thermostat->nighttemp != EMS_VALUE_INT_NOTSET)
rootThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2;
dataThermostat[THERMOSTAT_NIGHTTEMP] = (double)thermostat->nighttemp / 2;
if (thermostat->holidaytemp != EMS_VALUE_INT_NOTSET)
rootThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2;
dataThermostat[THERMOSTAT_HOLIDAYTEMP] = (double)thermostat->holidaytemp / 2;
if (thermostat->heatingtype != EMS_VALUE_INT_NOTSET)
rootThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype;
dataThermostat[THERMOSTAT_HEATINGTYPE] = thermostat->heatingtype;
if (thermostat->circuitcalctemp != EMS_VALUE_INT_NOTSET)
rootThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp;
dataThermostat[THERMOSTAT_CIRCUITCALCTEMP] = thermostat->circuitcalctemp;
}
uint8_t thermoMode = _getThermostatMode(hc_v); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day
// Termostat Mode
if (thermoMode == 0) {
rootThermostat[THERMOSTAT_MODE] = "low";
dataThermostat[THERMOSTAT_MODE] = "off";
} else if (thermoMode == 1) {
rootThermostat[THERMOSTAT_MODE] = "manual";
dataThermostat[THERMOSTAT_MODE] = "heat";
} else if (thermoMode == 2) {
rootThermostat[THERMOSTAT_MODE] = "auto";
dataThermostat[THERMOSTAT_MODE] = "auto";
} else if (thermoMode == 3) {
rootThermostat[THERMOSTAT_MODE] = "night";
dataThermostat[THERMOSTAT_MODE] = "night";
} else if (thermoMode == 4) {
rootThermostat[THERMOSTAT_MODE] = "day";
dataThermostat[THERMOSTAT_MODE] = "day";
}
data[0] = '\0'; // reset data for next package
@@ -967,13 +743,8 @@ void publishValues(bool force) {
fchecksum = crc.finalize();
if ((previousThermostatPublishCRC[hc_v - 1] != fchecksum) || force) {
previousThermostatPublishCRC[hc_v - 1] = fchecksum;
char thermostat_topicname[20];
char buffer[4];
// "thermostat_data" + Heating Cicruit #
strlcpy(thermostat_topicname, TOPIC_THERMOSTAT_DATA, sizeof(thermostat_topicname));
strlcat(thermostat_topicname, itoa(hc_v, buffer, 10), sizeof(thermostat_topicname));
myDebugLog("Publishing thermostat data via MQTT");
myESP.mqttPublish(thermostat_topicname, data);
myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data);
}
}
}
@@ -1103,20 +874,6 @@ void publishValues(bool force) {
}
}
// sets the shower timer on/off
void set_showerTimer() {
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled");
}
}
// sets the shower alert on/off
void set_showerAlert() {
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled");
}
}
// used to read the next string from an input buffer and convert to an 8 bit int
uint8_t _readIntNumber() {
char * numTextPtr = strtok(nullptr, ", \n");
@@ -1332,6 +1089,33 @@ bool LoadSaveCallback(MYESP_FSACTION action, JsonObject settings) {
return false;
}
// Publish shower data
bool do_publishShowerData() {
StaticJsonDocument<200> doc;
JsonObject rootShower = doc.to<JsonObject>();
rootShower[TOPIC_SHOWER_TIMER] = EMSESP_Settings.shower_timer ? "1" : "0";
rootShower[TOPIC_SHOWER_ALERT] = EMSESP_Settings.shower_alert ? "1" : "0";
char s[50] = {0};
if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) {
char buffer[16] = {0};
strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s));
strlcat(s, " minutes and ", sizeof(s));
strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s));
strlcat(s, " seconds", sizeof(s));
} else {
strlcpy(s, "n/a", sizeof(s));
}
rootShower[TOPIC_SHOWER_DURATION] = s;
char data[300] = {0};
serializeJson(doc, data, sizeof(data));
myDebugLog("Publishing shower data via MQTT");
return (myESP.mqttPublish(TOPIC_SHOWER_DATA, data));
}
// callback for custom settings when showing Stored Settings with the 'set' command
// wc is number of arguments after the 'set' command
// returns true if the setting was recognized and changed and should be saved back to SPIFFs
@@ -1403,12 +1187,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
if (strcmp(value, "on") == 0) {
EMSESP_Settings.shower_timer = true;
myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0");
ok = true;
ok = do_publishShowerData();
} else if (strcmp(value, "off") == 0) {
EMSESP_Settings.shower_timer = false;
myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0");
ok = true;
ok = do_publishShowerData();
} else {
myDebug_P(PSTR("Error. Usage: set shower_timer <on | off>"));
}
@@ -1418,12 +1200,10 @@ bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, co
if ((strcmp(setting, "shower_alert") == 0) && (wc == 2)) {
if (strcmp(value, "on") == 0) {
EMSESP_Settings.shower_alert = true;
myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0");
ok = true;
ok = do_publishShowerData();
} else if (strcmp(value, "off") == 0) {
EMSESP_Settings.shower_alert = false;
myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0");
ok = true;
ok = do_publishShowerData();
} else {
myDebug_P(PSTR("Error. Usage: set shower_alert <on | off>"));
}
@@ -1524,6 +1304,7 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) {
if (strcmp(first_cmd, "publish") == 0) {
do_publishValues();
do_publishSensorValues();
do_publishShowerData();
ok = true;
}
@@ -1690,11 +1471,11 @@ void OTACallback_post() {
// used to identify a heating circuit
// returns HC number 1 - 4
// or the default (1) is no suffix can be found
uint8_t _hasHCspecified(const char * topic, const char * input) {
int orig_len = strlen(topic); // original length of the topic we're comparing too
uint8_t _hasHCspecified(const char * key, const char * input) {
int orig_len = strlen(key); // original length of the topic we're comparing too
// check if the strings match ignoring any suffix
if (strncmp(input, topic, orig_len) == 0) {
if (strncmp(input, key, orig_len) == 0) {
// see if we have additional chars at the end, we want none or 1
uint8_t diff = (strlen(input) - orig_len);
if (diff > 1) {
@@ -1716,11 +1497,7 @@ uint8_t _hasHCspecified(const char * topic, const char * input) {
void MQTTCallback(unsigned int type, const char * topic, const char * message) {
// we're connected. lets subscribe to some topics
if (type == MQTT_CONNECT_EVENT) {
myESP.mqttSubscribe(TOPIC_SHOWER_TIMER);
myESP.mqttSubscribe(TOPIC_SHOWER_ALERT);
myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT);
// subscribe to the 4 heating circuits
// subscribe to the 4 heating circuits for receiving setpoint temperature and modes
char topic_s[50];
char buffer[4];
for (uint8_t hc = 1; hc <= EMS_THERMOSTAT_MAXHC; hc++) {
@@ -1731,158 +1508,229 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_MODE, sizeof(topic_s));
strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s));
myESP.mqttSubscribe(topic_s);
strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_DAYTEMP, sizeof(topic_s));
strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s));
myESP.mqttSubscribe(topic_s);
strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_NIGHTTEMP, sizeof(topic_s));
strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s));
myESP.mqttSubscribe(topic_s);
strlcpy(topic_s, TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, sizeof(topic_s));
strlcat(topic_s, itoa(hc, buffer, 10), sizeof(topic_s));
myESP.mqttSubscribe(topic_s);
}
// generic incoming MQTT command for Thermostat
// this is used for example for setting daytemp, nighttemp, holidaytemp
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD);
// generic incoming MQTT command for Boiler
// this is used for example for comfort, flowtemp
myESP.mqttSubscribe(TOPIC_BOILER_CMD);
// these two need to be unqiue topics
myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWACTIVATED);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP);
// publish the status of the Shower parameters
// myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Settings.shower_timer ? "1" : "0");
// myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Settings.shower_alert ? "1" : "0");
// generic incoming MQTT command for EMS-ESP
// this is used for example for shower_coldshot
myESP.mqttSubscribe(TOPIC_GENERIC_CMD);
// shower data
// for receiving shower_Timer and shower_alert switches
myESP.mqttSubscribe(TOPIC_SHOWER_DATA);
return;
}
// handle incoming MQTT publish events
if (type == MQTT_MESSAGE_EVENT) {
uint8_t hc;
// thermostat temp changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic);
if (hc) {
float f = strtof((char *)message, 0);
char s[10] = {0};
myDebug_P(PSTR("MQTT topic: thermostat HC%d temperature value %s"), hc, _float_to_char(s, f));
ems_setThermostatTemp(f, hc);
publishValues(true); // publish back immediately
if (type != MQTT_MESSAGE_EVENT) {
return;
}
// check first for generic commands
if (strcmp(topic, TOPIC_GENERIC_CMD) == 0) {
// convert JSON and get the command
StaticJsonDocument<100> doc;
JsonObject root = doc.to<JsonObject>(); // create empty object
DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document
if (error) {
myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str());
return;
}
const char * command = doc["cmd"];
// Check whatever the command is and act accordingly
if (strcmp(command, TOPIC_SHOWER_COLDSHOT) == 0) {
_showerColdShotStart();
return;
}
// thermostat mode changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic);
if (hc) {
myDebug_P(PSTR("MQTT topic: thermostat HC%d mode value %s"), hc, message);
if (strcmp((char *)message, "auto") == 0) {
ems_setThermostatMode(2, hc);
} else if (strcmp((char *)message, "day") == 0 || (strcmp((char *)message, "manual") == 0) || (strcmp((char *)message, "heat") == 0)) {
ems_setThermostatMode(1, hc);
} else if (strcmp((char *)message, "night") == 0 || strcmp((char *)message, "off") == 0) {
ems_setThermostatMode(0, hc);
return; // no match for generic commands
}
// check for shower commands
if (strcmp(topic, TOPIC_SHOWER_DATA) == 0) {
StaticJsonDocument<100> doc;
JsonObject root = doc.to<JsonObject>(); // create empty object
DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document
if (error) {
myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str());
return;
}
// assumes payload is "1" or "0"
const char * shower_alert = doc[TOPIC_SHOWER_ALERT];
if (shower_alert) {
if ((shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_alert) {
EMSESP_Settings.shower_alert = shower_alert[0] - MYESP_MQTT_PAYLOAD_OFF;
myDebug_P(PSTR("Shower alert has been set to %s"), EMSESP_Settings.shower_alert ? "enabled" : "disabled");
}
return;
}
// set night temp value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, topic);
if (hc) {
float f = strtof((char *)message, 0);
char s[10] = {0};
myDebug_P(PSTR("MQTT topic: new thermostat HC%d night temperature value %s"), hc, _float_to_char(s, f));
ems_setThermostatTemp(f, hc, 1); // night
return;
}
// set daytemp value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, topic);
if (hc) {
float f = strtof((char *)message, 0);
char s[10] = {0};
myDebug_P(PSTR("MQTT topic: new thermostat HC%d day temperature value %s"), hc, _float_to_char(s, f));
ems_setThermostatTemp(f, hc, 2); // day
return;
}
// set holiday value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, topic);
if (hc) {
float f = strtof((char *)message, 0);
char s[10] = {0};
myDebug_P(PSTR("MQTT topic: new thermostat HC%d holiday temperature value %s"), hc, _float_to_char(s, f));
ems_setThermostatTemp(f, hc, 3); // holiday
return;
}
// wwActivated
if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) {
if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) {
ems_setWarmWaterActivated(true);
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
ems_setWarmWaterActivated(false);
// assumes payload is "1" or "0"
const char * shower_timer = doc[TOPIC_SHOWER_TIMER];
if (shower_timer) {
if ((shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF) != EMSESP_Settings.shower_timer) {
EMSESP_Settings.shower_timer = shower_timer[0] - MYESP_MQTT_PAYLOAD_OFF;
myDebug_P(PSTR("Shower timer has been set to %s"), EMSESP_Settings.shower_timer ? "enabled" : "disabled");
}
return;
}
// boiler wwtemp changes
if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) {
uint8_t t = atoi((char *)message);
myDebug_P(PSTR("MQTT topic: boiler warm water temperature value %d"), t);
ems_setWarmWaterTemp(t);
publishValues(true); // publish back immediately, can't remember why I do this?!
return;
}
// check for boiler commands
if (strcmp(topic, TOPIC_BOILER_CMD) == 0) {
// convert JSON and get the command
StaticJsonDocument<100> doc;
JsonObject root = doc.to<JsonObject>(); // create empty object
DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document
if (error) {
myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str());
return;
}
const char * command = doc["cmd"];
// boiler ww comfort setting
if (strcmp(topic, TOPIC_BOILER_CMD_COMFORT) == 0) {
myDebug_P(PSTR("MQTT topic: boiler warm water comfort value is %s"), message);
if (strcmp((char *)message, "hot") == 0) {
if (strcmp(command, TOPIC_BOILER_CMD_COMFORT) == 0) {
const char * data = doc["data"];
if (strcmp((char *)data, "hot") == 0) {
ems_setWarmWaterModeComfort(1);
} else if (strcmp((char *)message, "comfort") == 0) {
} else if (strcmp((char *)data, "comfort") == 0) {
ems_setWarmWaterModeComfort(2);
} else if (strcmp((char *)message, "intelligent") == 0) {
} else if (strcmp((char *)data, "intelligent") == 0) {
ems_setWarmWaterModeComfort(3);
}
return;
}
// boiler flowtemp setting
if (strcmp(topic, TOPIC_BOILER_CMD_FLOWTEMP) == 0) {
uint8_t t = atoi((char *)message);
myDebug_P(PSTR("MQTT topic: boiler flowtemp value %d"), t);
if (strcmp(command, TOPIC_BOILER_CMD_FLOWTEMP) == 0) {
uint8_t t = doc["data"];
ems_setFlowTemp(t);
return;
}
// shower timer
if (strcmp(topic, TOPIC_SHOWER_TIMER) == 0) {
if (message[0] == '1') {
EMSESP_Settings.shower_timer = true;
} else if (message[0] == '0') {
EMSESP_Settings.shower_timer = false;
}
set_showerTimer();
return; // unknown boiler command
}
// check for unique boiler commands
// wwActivated
if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) {
if ((message[0] == MYESP_MQTT_PAYLOAD_ON || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) {
ems_setWarmWaterActivated(true);
} else if (message[0] == MYESP_MQTT_PAYLOAD_OFF || strcmp(message, "off") == 0) {
ems_setWarmWaterActivated(false);
}
return;
}
// boiler wwtemp changes
if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) {
uint8_t t = atoi((char *)message);
ems_setWarmWaterTemp(t);
publishValues(true);
return;
}
uint8_t hc;
// thermostat temp changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, topic);
if (hc) {
float f = strtof((char *)message, 0);
ems_setThermostatTemp(f, hc);
publishValues(true); // publish back immediately
return;
}
// thermostat mode changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, topic);
if (hc) {
if (strncmp(message, "auto", 4) == 0) {
ems_setThermostatMode(2, hc);
} else if ((strncmp(message, "day", 4) == 0) || (strncmp(message, "manual", 6) == 0) || (strncmp(message, "heat", 4) == 0)) {
ems_setThermostatMode(1, hc);
} else if ((strncmp(message, "night", 5) == 0) || (strncmp(message, "off", 3) == 0)) {
ems_setThermostatMode(0, hc);
}
return;
}
// check for generic thermostat commands
if (strcmp(topic, TOPIC_THERMOSTAT_CMD) == 0) {
// convert JSON and get the command
StaticJsonDocument<100> doc;
JsonObject root = doc.to<JsonObject>(); // create empty object
DeserializationError error = deserializeJson(doc, message); // Deserialize the JSON document
if (error) {
myDebug_P(PSTR("[MQTT] Invalid command from topic %s, payload %s, error %s"), topic, message, error.c_str());
return;
}
const char * command = doc["cmd"];
uint8_t hc;
// thermostat temp changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_TEMP, command);
if (hc) {
float f = doc["data"];
ems_setThermostatTemp(f, hc);
publishValues(true); // publish back immediately
return;
}
// shower alert
if (strcmp(topic, TOPIC_SHOWER_ALERT) == 0) {
if (message[0] == '1') {
EMSESP_Settings.shower_alert = true;
} else if (message[0] == '0') {
EMSESP_Settings.shower_alert = false;
// thermostat mode changes
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_MODE, command);
if (hc) {
const char * data = doc["data"];
if (strcmp(data, "auto") == 0) {
ems_setThermostatMode(2, hc);
} else if ((strcmp(data, "day") == 0) || (strcmp(data, "manual") == 0) || (strcmp(data, "heat") == 0)) {
ems_setThermostatMode(1, hc);
} else if ((strcmp(data, "night") == 0) || (strcmp(data, "off") == 0)) {
ems_setThermostatMode(0, hc);
}
set_showerAlert();
return;
}
// shower cold shot
if (strcmp(topic, TOPIC_SHOWER_COLDSHOT) == 0) {
_showerColdShotStart();
// set night temp value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_NIGHTTEMP, command);
if (hc) {
float f = doc["data"];
ems_setThermostatTemp(f, hc, 1); // night
return;
}
// set daytemp value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_DAYTEMP, command);
if (hc) {
float f = doc["data"];
ems_setThermostatTemp(f, hc, 2); // day
return;
}
// set holiday value
hc = _hasHCspecified(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP, command);
if (hc) {
float f = doc["data"];
ems_setThermostatTemp(f, hc, 3); // holiday
return;
}
}
}
// Init callback, which is used to set functions and call methods after a wifi connection has been established
void WIFICallback() {
// This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals
@@ -1967,11 +1815,11 @@ void WebCallback(JsonObject root) {
}
// Render Termostat Mode, if we have a mode
uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day
uint8_t thermoMode = _getThermostatMode(hc_num); // 0xFF=unknown, 0=off, 1=manual, 2=auto, 3=night, 4=day
if (thermoMode == 0) {
thermostat["tmode"] = "low";
thermostat["tmode"] = "off";
} else if (thermoMode == 1) {
thermostat["tmode"] = "manual";
thermostat["tmode"] = "heat";
} else if (thermoMode == 2) {
thermostat["tmode"] = "auto";
} else if (thermoMode == 3) {
@@ -2131,16 +1979,10 @@ void showerCheck() {
if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) {
EMSESP_Shower.duration = (EMSESP_Shower.timerPause - EMSESP_Shower.timerStart - SHOWER_OFFSET_TIME);
if (EMSESP_Shower.duration > SHOWER_MIN_DURATION) {
char s[50] = {0};
char buffer[16] = {0};
strlcpy(s, itoa((uint8_t)((EMSESP_Shower.duration / (1000 * 60)) % 60), buffer, 10), sizeof(s));
strlcat(s, " minutes and ", sizeof(s));
strlcat(s, itoa((uint8_t)((EMSESP_Shower.duration / 1000) % 60), buffer, 10), sizeof(s));
strlcat(s, " seconds", sizeof(s));
if (ems_getLogging() != EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("[Shower] finished with duration %s"), s);
myDebug_P(PSTR("[Shower] finished with duration %d"), EMSESP_Shower.duration);
}
myESP.mqttPublish(TOPIC_SHOWERTIME, s); // publish to MQTT
do_publishShowerData(); // publish to MQTT
}
}

View File

@@ -7,6 +7,7 @@
*/
#include "ems.h"
#include "ems_utils.h"
#include "MyESP.h"
#include "ems_devices.h"
#include "emsuart.h"
@@ -17,10 +18,6 @@
uint8_t _TEST_DATA_max = ArraySize(TEST_DATA);
#endif
// MyESP class for logging to telnet and serial
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
_EMS_Sys_Status EMS_Sys_Status; // EMS Status
CircularBuffer<_EMS_TxTelegram, EMS_TX_TELEGRAM_QUEUE_MAX> EMS_TxQueue; // FIFO queue for Tx send buffer
@@ -231,7 +228,6 @@ const uint8_t TX_WRITE_TIMEOUT_COUNT = 2; // 3 retries before timeout
const uint32_t EMS_BUS_TIMEOUT = 15000; // timeout in ms before recognizing the ems bus is offline (15 seconds)
const uint32_t EMS_POLL_TIMEOUT = 5000000; // timeout in microseconds before recognizing the ems bus is offline (5 seconds)
// init stats and counters and buffers
void ems_init() {
ems_clearDeviceList(); // init the device map
@@ -496,36 +492,6 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) {
return crc;
}
// like itoa but for hex, and quicker
char * _hextoa(uint8_t value, char * buffer) {
char * p = buffer;
byte nib1 = (value >> 4) & 0x0F;
byte nib2 = (value >> 0) & 0x0F;
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
*p = '\0'; // null terminate just in case
return buffer;
}
// for decimals 0 to 99, printed as a 2 char string
char * _smallitoa(uint8_t value, char * buffer) {
buffer[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
buffer[1] = (value % 10) + '0';
buffer[2] = '\0';
return buffer;
}
/* for decimals 0 to 999, printed as a string
* From @nomis
*/
char * _smallitoa3(uint16_t value, char * buffer) {
buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
buffer[2] = (value % 10) + '0';
buffer[3] = '\0';
return buffer;
}
/**
* Find the pointer to the EMS_Types array for a given type ID
* or -1 if not found
@@ -630,11 +596,11 @@ void _ems_sendTelegram() {
EMS_TxTelegram.data[EMS_TxTelegram.length - 1] = _crcCalculator(EMS_TxTelegram.data, EMS_TxTelegram.length); // add the CRC
if (EMS_Sys_Status.emsLogging != EMS_SYS_LOGGING_NONE) {
_EMS_RxTelegram EMS_RxTelegram; // create new Rx object
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram
EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.data_length = 0; // ignore #data=
EMS_RxTelegram.timestamp = millis(); // now
_EMS_RxTelegram EMS_RxTelegram; // create new Rx object
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram
EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.data_length = 0; // ignore #data=
EMS_RxTelegram.timestamp = millis(); // now
_debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true);
}
@@ -1160,7 +1126,7 @@ void _processType(_EMS_RxTelegram * EMS_RxTelegram) {
// release the lock on the TxQueue
EMS_Sys_Status.emsTxStatus = EMS_TX_STATUS_IDLE;
// at this point we can assume TxStatus is EMS_TX_STATUS_WAIT as we just sent a read or validate
// at this point we can assume TxStatus was EMS_TX_STATUS_WAIT as we just sent a read or validate telegram
// for READ or VALIDATE the dest (telegram[1]) is always us, so check for this
// and if not we probably didn't get any response so remove the last Tx from the queue and process the telegram anyway
if ((telegram[1] & 0x7F) != EMS_ID_ME) {
@@ -2820,7 +2786,11 @@ void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype)
EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE;
EMS_TxTelegram.dest = device_id;
myDebug_P(PSTR("Setting new thermostat temperature for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"), hc_num, temptype);
char s[10] = {0};
myDebug_P(PSTR("Setting new thermostat temperature to %s for heating circuit %d type %d (0=auto,1=night,2=day,3=holiday)"),
_float_to_char(s, temperature),
hc_num,
temptype);
if (model_id == EMS_MODEL_RC20) {
EMS_TxTelegram.type = EMS_TYPE_RC20Set;
@@ -2922,17 +2892,31 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) {
uint8_t model_id = EMS_Thermostat.model_id;
uint8_t device_id = EMS_Thermostat.device_id;
uint8_t set_mode;
// RC300/1000/3000 have different settings
if (model_id == EMS_MODEL_RC300) {
if (mode == 1) {
mode = 0; // manual
set_mode = 0; // manual/heat
} else {
mode = 0xFF; // auto
set_mode = 0xFF; // auto
}
} else {
set_mode = mode;
}
myDebug_P(PSTR("Setting thermostat mode to %d for heating circuit %d"), mode, hc_num);
// 0=off, 1=manual, 2=auto, 3=night, 4=day
if (mode == 0) {
myDebug_P(PSTR("Setting thermostat mode to off for heating circuit %d"), hc_num);
} else if (set_mode == 1) {
myDebug_P(PSTR("Setting thermostat mode to manual for heating circuit %d"), hc_num);
} else if (set_mode == 2) {
myDebug_P(PSTR("Setting thermostat mode to auto for heating circuit %d"), hc_num);
} else if (set_mode == 3) {
myDebug_P(PSTR("Setting thermostat mode to night for heating circuit %d"), hc_num);
} else if (set_mode == 4) {
myDebug_P(PSTR("Setting thermostat mode to day for heating circuit %d"), hc_num);
}
_EMS_TxTelegram EMS_TxTelegram = EMS_TX_TELEGRAM_NEW; // create new Tx
EMS_TxTelegram.timestamp = millis(); // set timestamp
@@ -2941,7 +2925,7 @@ void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) {
EMS_TxTelegram.action = EMS_TX_TELEGRAM_WRITE;
EMS_TxTelegram.dest = device_id;
EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH;
EMS_TxTelegram.dataValue = mode;
EMS_TxTelegram.dataValue = set_mode;
// handle different thermostat types
if (model_id == EMS_MODEL_RC20) {

View File

@@ -87,10 +87,10 @@
#define EMS_ID_GATEWAY 0x48 // KM200 Web Gateway
// Product IDs
#define EMS_PRODUCTID_SM10 73 // SM10 solar module
#define EMS_PRODUCTID_SM50 162 // SM50 solar module
#define EMS_PRODUCTID_SM100 163 // SM100 solar module
#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module
#define EMS_PRODUCTID_SM10 73 // SM10 solar module
#define EMS_PRODUCTID_SM50 162 // SM50 solar module
#define EMS_PRODUCTID_SM100 163 // SM100 solar module
#define EMS_PRODUCTID_ISM1 101 // Junkers ISM1 solar module
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
#define EMS_MAX_TELEGRAM_LENGTH 32 // max length of a telegram, including CRC, for Rx and Tx.
@@ -111,7 +111,7 @@
// trigger settings to determine if hot tap water or the heating is active
#define EMS_BOILER_BURNPOWER_TAPWATER 100
#define EMS_BOILER_SELFLOWTEMP_HEATING 70
#define EMS_BOILER_SELFLOWTEMP_HEATING 30 // was 70, changed to 30 for https://github.com/proddy/EMS-ESP/issues/193
// define maximum setable tapwater temperature
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
@@ -503,7 +503,6 @@ bool ems_getTxCapable();
uint32_t ems_getPollFrequency();
bool ems_getTxDisabled();
// private functions
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
void _processType(_EMS_RxTelegram * EMS_RxTelegram);

View File

@@ -12,36 +12,42 @@
// TOPICS with _CMD_ are used for receiving commands from an MQTT Broker
// EMS-ESP will subscribe to these topics
#define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT
// MQTT for thermostat
// these topics can be suffixed with a Heating Circuit number, e.g. thermostat_cmd_temp1 and thermostat_data1
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // temp changes via MQTT
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // mode changes via MQTT
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // holiday temp (RC35 specific)
#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
#define THERMOSTAT_HC "thermostat_hc" // which heating circuit number
#define THERMOSTAT_MODE "thermostat_mode" // mode
#define THERMOSTAT_DAYTEMP "thermostat_daytemp" // RC35 specific
#define THERMOSTAT_NIGHTTEMP "thermostat_nighttemp" // RC35 specific
#define THERMOSTAT_HOLIDAYTEMP "thermostat_holidayttemp" // RC35 specific
#define THERMOSTAT_HEATINGTYPE "thermostat_heatingtype" // RC35 specific (3=floorheating)
#define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
#define TOPIC_THERMOSTAT_CMD "thermostat_cmd" // for receiving thermostat commands via MQTT
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // temp changes via MQTT
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // mode changes via MQTT
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific)
#define THERMOSTAT_CURRTEMP "currtemp" // current temperature
#define THERMOSTAT_SELTEMP "seltemp" // selected temperature
#define THERMOSTAT_HC "hc" // which heating circuit number
#define THERMOSTAT_MODE "mode" // mode
#define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific
#define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific
#define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific
#define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating)
#define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific
// MQTT for boiler
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT
#define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT
#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // flowtemp value via MQTT
#define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT
// MQTT for mixing device
#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT
#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT
// MQTT for SM10/SM100 Solar Module
#define TOPIC_SM_DATA "sm_data" // topic name
@@ -60,10 +66,12 @@
#define HP_PUMPSPEED "pumpspeed" // pump speed
// shower time
#define TOPIC_SHOWERTIME "showertime" // for sending shower time results
#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic
#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic
#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command
#define TOPIC_SHOWER_DATA "shower_data" // for sending shower time results
#define TOPIC_SHOWER_TIMER "timer" // toggle switch for enabling the shower logic
#define TOPIC_SHOWER_ALERT "alert" // toggle switch for enabling the shower alarm logic
#define TOPIC_SHOWER_COLDSHOT "coldshot" // used to trigger a coldshot from an MQTT command
#define TOPIC_SHOWER_DURATION "duration" // duration of the last shower
// MQTT for EXTERNAL SENSORS
#define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT