mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 16:29:51 +03:00
changes to MQTT topics
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"}'
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
652
src/ems-esp.cpp
652
src/ems-esp.cpp
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
76
src/ems.cpp
76
src/ems.cpp
@@ -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) {
|
||||
|
||||
11
src/ems.h
11
src/ems.h
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user