mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
MQTT updates: added HA discovery, removed heartbeat - HomeAssistant Discovery #288
This commit is contained in:
@@ -30,7 +30,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
|||||||
value="enabled"
|
value="enabled"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Enable MQTT?"
|
label="Enable MQTT"
|
||||||
/>
|
/>
|
||||||
<TextValidator
|
<TextValidator
|
||||||
validators={['required', 'isIPOrHostname']}
|
validators={['required', 'isIPOrHostname']}
|
||||||
@@ -137,7 +137,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
|||||||
value="clean_session"
|
value="clean_session"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Clean Session?"
|
label="Clean Session"
|
||||||
/>
|
/>
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={
|
control={
|
||||||
@@ -149,16 +149,6 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
|
|||||||
}
|
}
|
||||||
label="Retain Flag"
|
label="Retain Flag"
|
||||||
/>
|
/>
|
||||||
<BlockFormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={data.system_heartbeat}
|
|
||||||
onChange={handleValueChange('system_heartbeat')}
|
|
||||||
value="system_heartbeat"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Heartbeat"
|
|
||||||
/>
|
|
||||||
<br></br>
|
<br></br>
|
||||||
<Typography variant="h6" color="primary" >
|
<Typography variant="h6" color="primary" >
|
||||||
Publish Intervals
|
Publish Intervals
|
||||||
|
|||||||
@@ -36,5 +36,4 @@ export interface MqttSettings {
|
|||||||
mqtt_format: number;
|
mqtt_format: number;
|
||||||
mqtt_qos: number;
|
mqtt_qos: number;
|
||||||
mqtt_retain: boolean;
|
mqtt_retain: boolean;
|
||||||
system_heartbeat: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
|||||||
root["max_topic_length"] = settings.maxTopicLength;
|
root["max_topic_length"] = settings.maxTopicLength;
|
||||||
|
|
||||||
// added by proddy for EMS-ESP
|
// added by proddy for EMS-ESP
|
||||||
root["system_heartbeat"] = settings.system_heartbeat;
|
|
||||||
root["publish_time_boiler"] = settings.publish_time_boiler;
|
root["publish_time_boiler"] = settings.publish_time_boiler;
|
||||||
root["publish_time_thermostat"] = settings.publish_time_thermostat;
|
root["publish_time_thermostat"] = settings.publish_time_thermostat;
|
||||||
root["publish_time_solar"] = settings.publish_time_solar;
|
root["publish_time_solar"] = settings.publish_time_solar;
|
||||||
@@ -209,7 +208,6 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
|||||||
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
|
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
|
||||||
newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
|
newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
|
||||||
|
|
||||||
newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT;
|
|
||||||
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||||
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||||
newSettings.publish_time_solar = root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
newSettings.publish_time_solar = root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||||
@@ -220,10 +218,6 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
|||||||
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
|
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
|
||||||
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
|
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
|
||||||
|
|
||||||
if (newSettings.system_heartbeat != settings.system_heartbeat) {
|
|
||||||
emsesp::EMSESP::system_.set_heartbeat(newSettings.system_heartbeat);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSettings.mqtt_qos != settings.mqtt_qos) {
|
if (newSettings.mqtt_qos != settings.mqtt_qos) {
|
||||||
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
|
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ static String generateClientId() {
|
|||||||
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
|
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define EMSESP_DEFAULT_SYSTEM_HEARTBEAT true
|
|
||||||
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested
|
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested
|
||||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||||
#define EMSESP_DEFAULT_MQTT_RETAIN false
|
#define EMSESP_DEFAULT_MQTT_RETAIN false
|
||||||
@@ -94,7 +93,6 @@ class MqttSettings {
|
|||||||
uint16_t publish_time_sensor;
|
uint16_t publish_time_sensor;
|
||||||
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
|
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
|
||||||
uint8_t mqtt_qos;
|
uint8_t mqtt_qos;
|
||||||
bool system_heartbeat;
|
|
||||||
bool mqtt_retain;
|
bool mqtt_retain;
|
||||||
|
|
||||||
static void read(MqttSettings & settings, JsonObject & root);
|
static void read(MqttSettings & settings, JsonObject & root);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class DummySettings {
|
|||||||
public:
|
public:
|
||||||
uint8_t tx_mode = 1;
|
uint8_t tx_mode = 1;
|
||||||
uint8_t ems_bus_id = 0x0B;
|
uint8_t ems_bus_id = 0x0B;
|
||||||
bool system_heartbeat = false;
|
|
||||||
int8_t syslog_level = 1; // uuid::log::Level
|
int8_t syslog_level = 1; // uuid::log::Level
|
||||||
uint32_t syslog_mark_interval = 0;
|
uint32_t syslog_mark_interval = 0;
|
||||||
String syslog_host = "192.168.1.4";
|
String syslog_host = "192.168.1.4";
|
||||||
|
|||||||
@@ -59,8 +59,6 @@
|
|||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
enum MQTT_format : uint8_t { SINGLE = 1, NESTED, HA, CUSTOM };
|
|
||||||
|
|
||||||
class EMSESPSettings {
|
class EMSESPSettings {
|
||||||
public:
|
public:
|
||||||
uint8_t tx_mode;
|
uint8_t tx_mode;
|
||||||
|
|||||||
@@ -75,34 +75,30 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
|||||||
return command_info(value, id, object);
|
return command_info(value, id, object);
|
||||||
});
|
});
|
||||||
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
|
||||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
register_mqtt_ha_config();
|
||||||
|
}
|
||||||
if (mqtt_format_ == MQTT_format::HA) {
|
|
||||||
register_mqtt_ha_config();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the config topic for Home Assistant MQTT Discovery
|
// create the config topics for Home Assistant MQTT Discovery
|
||||||
// homeassistant/sensor/ems-esp/boiler
|
// for each of the main elements
|
||||||
// state is /state
|
|
||||||
// config is /config
|
|
||||||
void Boiler::register_mqtt_ha_config() {
|
void Boiler::register_mqtt_ha_config() {
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
Mqtt::register_mqtt_ha_binary_sensor(F("Boiler DHW"), "tapwater_active");
|
||||||
|
Mqtt::register_mqtt_ha_binary_sensor(F("Boiler Heating"), "heating_active");
|
||||||
|
|
||||||
/*
|
Mqtt::register_mqtt_ha_sensor(F("Service Code"), this->device_type(), "serviceCode", "", "");
|
||||||
* not finished yet - see https://github.com/proddy/EMS-ESP/issues/288
|
Mqtt::register_mqtt_ha_sensor(F("Service Code number"), this->device_type(), "serviceCodeNumber", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Boiler WW Selected Temp"), this->device_type(), "wWSelTemp", "°C", "mdi:coolant-temperature");
|
||||||
doc["name"] = "boiler";
|
Mqtt::register_mqtt_ha_sensor(F("Selected flow temperature"), this->device_type(), "selFlowTemp", "°C", "mdi:coolant-temperature");
|
||||||
doc["uniq_id"] = "boiler";
|
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "curFlowTemp", "°C", "mdi:coolant-temperature");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Warm Water set temperature"), this->device_type(), "wWSetTemp", "°C", "mdi:coolant-temperature");
|
||||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
Mqtt::register_mqtt_ha_sensor(F("Warm Water current temperature (intern)"), this->device_type(), "wWCurTmp", "°C", "mdi:coolant-temperature");
|
||||||
Mqtt::publish("homeassistant/sensor/ems-esp/boiler/config", doc, true); // publish the config payload with retain flag
|
Mqtt::register_mqtt_ha_sensor(F("Warm Water current temperature (extern)"), this->device_type(), "wWCurTmp2", "°C", "mdi:coolant-temperature");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Pump modulation"), this->device_type(), "pumpMod", "%", "mdi:sine-wave");
|
||||||
*/
|
Mqtt::register_mqtt_ha_sensor(F("Heat Pump modulation"), this->device_type(), "pumpMod2", "%", "mdi:sine-wave");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send stuff to the Web UI
|
||||||
void Boiler::device_info_web(JsonArray & root) {
|
void Boiler::device_info_web(JsonArray & root) {
|
||||||
JsonObject dataElement;
|
JsonObject dataElement;
|
||||||
|
|
||||||
@@ -341,13 +337,14 @@ bool Boiler::export_values(JsonObject & output) {
|
|||||||
|
|
||||||
// publish values via MQTT
|
// publish values via MQTT
|
||||||
void Boiler::publish_values() {
|
void Boiler::publish_values() {
|
||||||
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
|
||||||
// DynamicJsonDocument doc(capacity);
|
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
||||||
JsonObject output = doc.to<JsonObject>();
|
JsonObject output = doc.to<JsonObject>();
|
||||||
if (export_values(output)) {
|
if (export_values(output)) {
|
||||||
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
|
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send out heating and tapwater status - even if there is no change (force = true)
|
||||||
|
check_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
// called after a process command is called, to check values and see if we need to force an MQTT publish
|
// called after a process command is called, to check values and see if we need to force an MQTT publish
|
||||||
@@ -491,6 +488,19 @@ void Boiler::show_values(uuid::console::Shell & shell) {
|
|||||||
* If a value has changed, post it immediately to MQTT so we get real time data
|
* If a value has changed, post it immediately to MQTT so we get real time data
|
||||||
*/
|
*/
|
||||||
void Boiler::check_active() {
|
void Boiler::check_active() {
|
||||||
|
if ((boilerState_ & 0x09) != (last_boilerState & 0x09)) {
|
||||||
|
char s[5];
|
||||||
|
Mqtt::publish(F("heating_active"), Helpers::render_boolean(s, ((boilerState_ & 0x09) == 0x09)));
|
||||||
|
}
|
||||||
|
if ((boilerState_ & 0x0A) != (last_boilerState & 0x0A)) {
|
||||||
|
char s[5];
|
||||||
|
Mqtt::publish(F("tapwater_active"), Helpers::render_boolean(s, ((boilerState_ & 0x0A) == 0x0A)));
|
||||||
|
EMSESP::tap_water_active((boilerState_ & 0x0A) == 0x0A);
|
||||||
|
}
|
||||||
|
last_boilerState = boilerState_;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
// hot tap water, using flow to check instead of the burner power
|
// hot tap water, using flow to check instead of the burner power
|
||||||
// send these values back to the main EMSESP, so other classes (e.g. Shower) can use it
|
// send these values back to the main EMSESP, so other classes (e.g. Shower) can use it
|
||||||
if (Helpers::hasValue(wWCurFlow_) && Helpers::hasValue(burnGas_) && (wWType_ > 0) && (wWType_ < 3)) {
|
if (Helpers::hasValue(wWCurFlow_) && Helpers::hasValue(burnGas_) && (wWType_ > 0) && (wWType_ < 3)) {
|
||||||
@@ -500,20 +510,21 @@ void Boiler::check_active() {
|
|||||||
|
|
||||||
// heating
|
// heating
|
||||||
// using a quick hack for checking the heating by looking at the Selected Flow Temp, but doesn't work for all boilers apparently
|
// using a quick hack for checking the heating by looking at the Selected Flow Temp, but doesn't work for all boilers apparently
|
||||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(selFlowTemp_) && Helpers::hasValue(burnGas_)) {
|
if (Helpers::hasValue(selFlowTemp_) && Helpers::hasValue(burnGas_)) {
|
||||||
heating_active_ = (!tap_water_active_ && ((selFlowTemp_ >= EMS_BOILER_SELFLOWTEMP_HEATING) && (burnGas_ != EMS_VALUE_BOOL_OFF)));
|
heating_active_ = (!tap_water_active_ && ((selFlowTemp_ >= EMS_BOILER_SELFLOWTEMP_HEATING) && (burnGas_ != EMS_VALUE_BOOL_OFF)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if the heating or hot tap water has changed, if so send
|
// see if the heating or hot tap water has changed, if so send
|
||||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) {
|
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
||||||
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
if (latest_boilerState != last_boilerState) {
|
||||||
if (latest_boilerState != last_boilerState) {
|
last_boilerState = latest_boilerState;
|
||||||
last_boilerState = latest_boilerState;
|
static char s[10];
|
||||||
Mqtt::publish(F("tapwater_active"), tap_water_active_);
|
Mqtt::publish(F("tapwater_active"), Helpers::render_boolean(s, tap_water_active_));
|
||||||
Mqtt::publish(F("heating_active"), heating_active_);
|
static char s2[10];
|
||||||
}
|
Mqtt::publish(F("heating_active"), Helpers::render_boolean(s2, heating_active_));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x33
|
// 0x33
|
||||||
@@ -533,6 +544,7 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
|
|||||||
changed_ |= telegram->read_value(curFlowTemp_, 1);
|
changed_ |= telegram->read_value(curFlowTemp_, 1);
|
||||||
changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
|
changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
|
||||||
changed_ |= telegram->read_value(curBurnPow_, 4);
|
changed_ |= telegram->read_value(curBurnPow_, 4);
|
||||||
|
changed_ |= telegram->read_value(boilerState_, 5);
|
||||||
|
|
||||||
changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
|
changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
|
||||||
changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
|
changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
|
||||||
|
|||||||
@@ -47,12 +47,13 @@ class Boiler : public EMSdevice {
|
|||||||
static uuid::log::Logger logger_;
|
static uuid::log::Logger logger_;
|
||||||
|
|
||||||
void register_mqtt_ha_config();
|
void register_mqtt_ha_config();
|
||||||
|
void register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const char * entity);
|
||||||
|
void register_mqtt_ha_sensor(const __FlashStringHelper * name, const char * entity, const char * uom, const char * icon);
|
||||||
void check_active();
|
void check_active();
|
||||||
bool export_values(JsonObject & doc);
|
bool export_values(JsonObject & doc);
|
||||||
|
|
||||||
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
|
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
|
||||||
uint8_t mqtt_format_; // single, nested or ha
|
bool changed_ = false;
|
||||||
bool changed_ = false;
|
|
||||||
|
|
||||||
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
|
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
|
||||||
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
|
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
|
||||||
@@ -95,6 +96,7 @@ class Boiler : public EMSdevice {
|
|||||||
uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure
|
uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure
|
||||||
char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code
|
char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code
|
||||||
uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code
|
uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code
|
||||||
|
uint8_t boilerState_ = EMS_VALUE_BOOL_NOTSET; // State flag, used on HT3
|
||||||
|
|
||||||
// UBAMonitorSlow - 0x19 on EMS1
|
// UBAMonitorSlow - 0x19 on EMS1
|
||||||
int16_t extTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature
|
int16_t extTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature
|
||||||
@@ -141,8 +143,8 @@ class Boiler : public EMSdevice {
|
|||||||
uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
|
uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
|
||||||
|
|
||||||
// other internal calculated params
|
// other internal calculated params
|
||||||
uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off
|
bool tap_water_active_ = false; // Hot tap water is on/off
|
||||||
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
|
bool heating_active_ = false; // Central heating is on/off
|
||||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||||
|
|
||||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||||
@@ -187,4 +189,4 @@ class Boiler : public EMSdevice {
|
|||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -62,16 +62,17 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
|||||||
|
|
||||||
// output json to web UI
|
// output json to web UI
|
||||||
void Mixing::device_info_web(JsonArray & root) {
|
void Mixing::device_info_web(JsonArray & root) {
|
||||||
if (type_ == Type::NONE) {
|
if (type() == Type::NONE) {
|
||||||
return; // don't have any values yet
|
return; // don't have any values yet
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type_ == Type::WWC) {
|
if (type() == Type::WWC) {
|
||||||
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
|
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
|
||||||
render_value_json(root, "", F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
render_value_json(root, "", F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
||||||
render_value_json(root, "", F("Current pump status"), pump_, nullptr);
|
render_value_json(root, "", F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||||
render_value_json(root, "", F("Current temperature status"), status_, nullptr);
|
render_value_json(root, "", F("Current temperature status"), status_, nullptr);
|
||||||
} else {
|
} else {
|
||||||
|
// HC
|
||||||
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
||||||
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||||
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||||
@@ -93,14 +94,14 @@ bool Mixing::updated_values() {
|
|||||||
void Mixing::show_values(uuid::console::Shell & shell) {
|
void Mixing::show_values(uuid::console::Shell & shell) {
|
||||||
EMSdevice::show_values(shell); // always call this to show header
|
EMSdevice::show_values(shell); // always call this to show header
|
||||||
|
|
||||||
if (type_ == Type::NONE) {
|
if (type() == Type::NONE) {
|
||||||
return; // don't have any values yet
|
return; // don't have any values yet
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type_ == Type::WWC) {
|
if (type() == Type::WWC) {
|
||||||
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
|
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
|
||||||
print_value(shell, 4, F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
print_value(shell, 4, F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
||||||
print_value(shell, 4, F("Current pump status"), pump_, nullptr);
|
print_value(shell, 4, F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||||
print_value(shell, 4, F("Current temperature status"), status_, nullptr);
|
print_value(shell, 4, F("Current temperature status"), status_, nullptr);
|
||||||
} else {
|
} else {
|
||||||
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
|
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
|
||||||
@@ -110,7 +111,6 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
|||||||
print_value(shell, 4, F("Current valve status"), status_, F_(percent));
|
print_value(shell, 4, F("Current valve status"), status_, F_(percent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
shell.println();
|
shell.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,13 +129,34 @@ void Mixing::publish_values() {
|
|||||||
strlcpy(topic, "mixing_data", 30);
|
strlcpy(topic, "mixing_data", 30);
|
||||||
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
||||||
Mqtt::publish(topic, doc.as<JsonObject>());
|
Mqtt::publish(topic, doc.as<JsonObject>());
|
||||||
|
|
||||||
|
// if we're using Home Assistant and haven't created the MQTT Discovery topics, do it now
|
||||||
|
if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_created_)) {
|
||||||
|
register_mqtt_ha_config();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publish config topic for HA MQTT Discovery
|
||||||
|
void Mixing::register_mqtt_ha_config() {
|
||||||
|
if (this->type() == Type::HC) {
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "flowTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Setpoint flow temperature"), this->device_type(), "flowSetTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current pump status"), this->device_type(), "pumpStatus", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current valve status"), this->device_type(), "valveStatus", "", "");
|
||||||
|
} else {
|
||||||
|
// WWC
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "wwTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current pump status"), this->device_type(), "pumpStatus", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Current temperature status"), this->device_type(), "tempStatus", "", "");
|
||||||
|
}
|
||||||
|
ha_created_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
// creates JSON doc from values
|
// creates JSON doc from values
|
||||||
// returns false if empty
|
// returns false if empty
|
||||||
bool Mixing::export_values(JsonObject & output) {
|
bool Mixing::export_values(JsonObject & output) {
|
||||||
switch (type_) {
|
switch (this->type()) {
|
||||||
case Type::HC:
|
case Type::HC:
|
||||||
output["type"] = "hc";
|
output["type"] = "hc";
|
||||||
if (Helpers::hasValue(flowTemp_)) {
|
if (Helpers::hasValue(flowTemp_)) {
|
||||||
@@ -159,7 +180,8 @@ bool Mixing::export_values(JsonObject & output) {
|
|||||||
output["wwTemp"] = (float)flowTemp_ / 10;
|
output["wwTemp"] = (float)flowTemp_ / 10;
|
||||||
}
|
}
|
||||||
if (Helpers::hasValue(pump_)) {
|
if (Helpers::hasValue(pump_)) {
|
||||||
output["pumpStatus"] = pump_;
|
char s[5]; // for formatting strings
|
||||||
|
output["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
|
||||||
}
|
}
|
||||||
if (Helpers::hasValue(status_)) {
|
if (Helpers::hasValue(status_)) {
|
||||||
output["tempStatus"] = status_;
|
output["tempStatus"] = status_;
|
||||||
@@ -179,11 +201,11 @@ bool Mixing::export_values(JsonObject & output) {
|
|||||||
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
|
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
|
||||||
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
|
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
|
||||||
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
|
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
|
||||||
type_ = Type::HC;
|
type(Type::HC);
|
||||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||||
changed_ |= telegram->read_value(pump_, 0);
|
changed_ |= telegram->read_bitvalue(pump_, 2, 0);
|
||||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,10 +213,10 @@ void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> tele
|
|||||||
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
|
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
|
||||||
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
|
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
|
||||||
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
|
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
|
||||||
type_ = Type::WWC;
|
type(Type::WWC);
|
||||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||||
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
|
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
|
||||||
changed_ |= telegram->read_value(pump_, 2);
|
changed_ |= telegram->read_bitvalue(pump_, 2, 0);
|
||||||
changed_ |= telegram->read_value(status_, 11); // temp status
|
changed_ |= telegram->read_value(status_, 11); // temp status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,18 +224,20 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tel
|
|||||||
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
|
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
|
||||||
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
|
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
|
||||||
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||||
type_ = Type::HC;
|
type(Type::HC);
|
||||||
hc_ = device_id() - 0x20 + 1;
|
hc_ = device_id() - 0x20 + 1;
|
||||||
uint8_t ismixed = 0;
|
uint8_t ismixed = 0;
|
||||||
changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
|
changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
|
||||||
if (ismixed == 0) {
|
if (ismixed == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ismixed == 2) { // we have a mixed circuit
|
if (ismixed == 2) { // we have a mixed circuit
|
||||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||||
}
|
}
|
||||||
|
|
||||||
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
|
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,14 +245,14 @@ void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram)
|
|||||||
// e.g. Mixing Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
|
// e.g. Mixing Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
|
||||||
// see also https://github.com/proddy/EMS-ESP/issues/386
|
// see also https://github.com/proddy/EMS-ESP/issues/386
|
||||||
void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||||
type_ = Type::HC;
|
type(Type::HC);
|
||||||
|
|
||||||
// the heating circuit is determine by which device_id it is, 0x20 - 0x23
|
// the heating circuit is determine by which device_id it is, 0x20 - 0x23
|
||||||
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
||||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||||
hc_ = device_id() - 0x20 + 1;
|
hc_ = device_id() - 0x20 + 1;
|
||||||
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
||||||
changed_ |= telegram->read_value(pump_, 3);
|
changed_ |= telegram->read_bitvalue(pump_, 3, 0);
|
||||||
changed_ |= telegram->read_value(flowSetTemp_, 0);
|
changed_ |= telegram->read_value(flowSetTemp_, 0);
|
||||||
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
|
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class Mixing : public EMSdevice {
|
|||||||
static uuid::log::Logger logger_;
|
static uuid::log::Logger logger_;
|
||||||
|
|
||||||
bool export_values(JsonObject & doc);
|
bool export_values(JsonObject & doc);
|
||||||
|
void register_mqtt_ha_config();
|
||||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||||
|
|
||||||
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
|
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
|
||||||
@@ -60,6 +61,14 @@ class Mixing : public EMSdevice {
|
|||||||
WWC // warm water circuit
|
WWC // warm water circuit
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Type type() const {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void type(Type new_type) {
|
||||||
|
type_ = new_type;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
|
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
|
||||||
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
|
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
|
||||||
@@ -68,7 +77,8 @@ class Mixing : public EMSdevice {
|
|||||||
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
||||||
Type type_ = Type::NONE;
|
Type type_ = Type::NONE;
|
||||||
|
|
||||||
bool changed_ = false;
|
bool changed_ = false;
|
||||||
|
bool ha_created_ = false; // for HA MQTT Discovery
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
|
|||||||
@@ -116,8 +116,33 @@ void Solar::publish_values() {
|
|||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
JsonObject output = doc.to<JsonObject>();
|
JsonObject output = doc.to<JsonObject>();
|
||||||
if (export_values(output)) {
|
if (export_values(output)) {
|
||||||
Mqtt::publish(F("sm_data"), doc.as<JsonObject>());
|
Mqtt::publish(F("solar_data"), doc.as<JsonObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we're using Home Assistant and haven't created the MQTT Discovery topics, do it now
|
||||||
|
if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_created_)) {
|
||||||
|
register_mqtt_ha_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish config topic for HA MQTT Discovery
|
||||||
|
void Solar::register_mqtt_ha_config() {
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Collector temperature (TS1)"), this->device_type(), "collectorTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Bottom temperature (TS2)"), this->device_type(), "tankBottomTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Bottom temperature (TS5)"), this->device_type(), "tankBottomTemp2", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Heat exchanger temperature (TS6)"), this->device_type(), "heatExchangerTemp", "°C", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Solar pump modulation (PS1)"), this->device_type(), "solarPumpModulation", "%", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Cylinder pump modulation (PS5)"), this->device_type(), "cylinderPumpModulation", "%", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Pump working time"), this->device_type(), "pumpWorkMin", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Energy last hour"), this->device_type(), "energyLastHour", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Energy today"), this->device_type(), "energyToday", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Energy total"), this->device_type(), "energyTotal", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Solar Pump (PS1) active"), this->device_type(), "solarPump", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Valve (VS2) status"), this->device_type(), "valveStatus", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Tank Heated"), this->device_type(), "tankHeated", "", "");
|
||||||
|
Mqtt::register_mqtt_ha_sensor(F("Collector shutdown"), this->device_type(), "collectorShutdown", "", "");
|
||||||
|
|
||||||
|
ha_created_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates JSON doc from values
|
// creates JSON doc from values
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class Solar : public EMSdevice {
|
|||||||
|
|
||||||
bool export_values(JsonObject & doc);
|
bool export_values(JsonObject & doc);
|
||||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||||
|
void register_mqtt_ha_config();
|
||||||
|
|
||||||
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
|
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
|
||||||
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
|
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
|
||||||
@@ -67,7 +68,8 @@ class Solar : public EMSdevice {
|
|||||||
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||||
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
|
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||||
|
|
||||||
bool changed_ = false;
|
bool changed_ = false;
|
||||||
|
bool ha_created_ = false; // for HA MQTT Discovery
|
||||||
|
|
||||||
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
|
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
|
||||||
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||||
|
|||||||
@@ -137,10 +137,6 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
|
||||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
|
||||||
});
|
|
||||||
|
|
||||||
if (actual_master_thermostat != device_id) {
|
if (actual_master_thermostat != device_id) {
|
||||||
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
|
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
|
||||||
return; // don't fetch data if more than 1 thermostat
|
return; // don't fetch data if more than 1 thermostat
|
||||||
@@ -236,7 +232,7 @@ bool Thermostat::updated_values() {
|
|||||||
// info API command
|
// info API command
|
||||||
// returns the same MQTT publish payload in Nested format
|
// returns the same MQTT publish payload in Nested format
|
||||||
bool Thermostat::command_info(const char * value, const int8_t id, JsonObject & output) {
|
bool Thermostat::command_info(const char * value, const int8_t id, JsonObject & output) {
|
||||||
return (export_values(MQTT_format::NESTED, output));
|
return (export_values(Mqtt::Format::NESTED, output));
|
||||||
}
|
}
|
||||||
|
|
||||||
// publish values via MQTT
|
// publish values via MQTT
|
||||||
@@ -247,9 +243,11 @@ void Thermostat::publish_values() {
|
|||||||
|
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
JsonObject output = doc.to<JsonObject>();
|
JsonObject output = doc.to<JsonObject>();
|
||||||
export_values(mqtt_format_, output);
|
|
||||||
|
|
||||||
if (mqtt_format_ == MQTT_format::NESTED) {
|
export_values(Mqtt::mqtt_format(), output);
|
||||||
|
|
||||||
|
// if we're in SINGLE mode the MQTT would have been published on the export_values() function for each hc
|
||||||
|
if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE) {
|
||||||
Mqtt::publish(F("thermostat_data"), output);
|
Mqtt::publish(F("thermostat_data"), output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +298,7 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send this specific data using the thermostat_data topic
|
// send this specific data using the thermostat_data topic
|
||||||
if (mqtt_format != MQTT_format::NESTED) {
|
if (mqtt_format != Mqtt::Format::NESTED) {
|
||||||
Mqtt::publish(F("thermostat_data"), rootThermostat);
|
Mqtt::publish(F("thermostat_data"), rootThermostat);
|
||||||
rootThermostat.clear(); // clear object
|
rootThermostat.clear(); // clear object
|
||||||
}
|
}
|
||||||
@@ -313,7 +311,7 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
|||||||
has_data = true;
|
has_data = true;
|
||||||
|
|
||||||
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
|
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
|
||||||
if ((mqtt_format == MQTT_format::NESTED) || (mqtt_format == MQTT_format::HA)) {
|
if ((mqtt_format == Mqtt::Format::NESTED) || (mqtt_format == Mqtt::Format::HA)) {
|
||||||
char hc_name[10]; // hc{1-4}
|
char hc_name[10]; // hc{1-4}
|
||||||
strlcpy(hc_name, "hc", 10);
|
strlcpy(hc_name, "hc", 10);
|
||||||
char s[3];
|
char s[3];
|
||||||
@@ -395,10 +393,10 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mode - always force showing this when in HA so not to break HA's climate component
|
// mode - always force showing this when in HA so not to break HA's climate component
|
||||||
if ((Helpers::hasValue(hc->mode)) || (mqtt_format == MQTT_format::HA)) {
|
if ((Helpers::hasValue(hc->mode)) || (mqtt_format == Mqtt::Format::HA)) {
|
||||||
uint8_t hc_mode = hc->get_mode(flags);
|
uint8_t hc_mode = hc->get_mode(flags);
|
||||||
// if we're sending to HA the only valid mode types are heat, auto and off
|
// if we're sending to HA the only valid mode types are heat, auto and off
|
||||||
if (mqtt_format == MQTT_format::HA) {
|
if (mqtt_format == Mqtt::Format::HA) {
|
||||||
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
|
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
|
||||||
hc_mode = HeatingCircuit::Mode::HEAT;
|
hc_mode = HeatingCircuit::Mode::HEAT;
|
||||||
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
|
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
|
||||||
@@ -422,23 +420,17 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
|||||||
|
|
||||||
// if format is single, send immediately and clear object for next hc
|
// if format is single, send immediately and clear object for next hc
|
||||||
// the topic will have the hc number appended
|
// the topic will have the hc number appended
|
||||||
if ((mqtt_format == MQTT_format::SINGLE) || (mqtt_format == MQTT_format::CUSTOM)) {
|
if ((mqtt_format == Mqtt::Format::SINGLE) || (mqtt_format == Mqtt::Format::CUSTOM)) {
|
||||||
char topic[30];
|
char topic[30];
|
||||||
char s[3];
|
char s[3];
|
||||||
strlcpy(topic, "thermostat_data", 30);
|
strlcpy(topic, "thermostat_data", 30);
|
||||||
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
||||||
Mqtt::publish(topic, rootThermostat);
|
Mqtt::publish(topic, rootThermostat);
|
||||||
rootThermostat.clear(); // clear object
|
rootThermostat.clear(); // clear object
|
||||||
} else if (mqtt_format == MQTT_format::HA) {
|
} else if ((mqtt_format == Mqtt::Format::HA) && (!hc->ha_registered())) {
|
||||||
// see if we have already registered this with HA MQTT Discovery, if not send the config
|
// see if we have already registered this with HA MQTT Discovery, if not send the config
|
||||||
if (!hc->ha_registered()) {
|
register_mqtt_ha_config(hc->hc_num());
|
||||||
register_mqtt_ha_config(hc->hc_num());
|
hc->ha_registered(true);
|
||||||
hc->ha_registered(true);
|
|
||||||
}
|
|
||||||
// send the thermostat topic and payload data
|
|
||||||
std::string topic(100, '\0');
|
|
||||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
|
|
||||||
Mqtt::publish(topic, rootThermostat);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,27 +519,24 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// publish config topic for HA MQTT Discovery
|
// publish config topic for HA MQTT Discovery
|
||||||
// homeassistant/climate/ems-esp/hc<num>
|
// homeassistant/climate/ems-esp/thermostat_hc1/config
|
||||||
// state is /state
|
|
||||||
// config is /config
|
|
||||||
void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
|
|
||||||
std::string hc_text(10, '\0');
|
std::string str1(40, '\0');
|
||||||
snprintf_P(&hc_text[0], hc_text.capacity() + 1, PSTR("hc%d"), hc_num);
|
snprintf_P(&str1[0], str1.capacity() + 1, PSTR("Thermostat hc%d"), hc_num);
|
||||||
doc["name"] = hc_text;
|
doc["name"] = str1;
|
||||||
doc["uniq_id"] = hc_text;
|
|
||||||
|
|
||||||
// topic root is homeassistant/climate/ems-esp/hc<1..n>/
|
std::string str2(40, '\0');
|
||||||
std::string root(100, '\0');
|
snprintf_P(&str2[0], str2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num);
|
||||||
snprintf_P(&root[0], root.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d"), hc_num);
|
doc["uniq_id"] = str2;
|
||||||
doc["~"] = root;
|
|
||||||
|
|
||||||
doc["mode_cmd_t"] = "~/cmd_mode";
|
doc["~"] = F("ems-esp");
|
||||||
doc["mode_stat_t"] = "~/state";
|
doc["mode_cmd_t"] = F("~/thermostat");
|
||||||
doc["temp_cmd_t"] = "~/cmd_temp";
|
doc["temp_cmd_t"] = F("~/thermostat");
|
||||||
doc["temp_stat_t"] = "~/state";
|
doc["mode_stat_t"] = F("~/thermostat_data");
|
||||||
doc["curr_temp_t"] = "~/state";
|
doc["temp_stat_t"] = F("~/thermostat_data");
|
||||||
|
doc["curr_temp_t"] = F("~/thermostat_data");
|
||||||
|
|
||||||
std::string mode_str(30, '\0');
|
std::string mode_str(30, '\0');
|
||||||
snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc_num);
|
snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc_num);
|
||||||
@@ -561,40 +550,60 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
|||||||
snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc_num);
|
snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc_num);
|
||||||
doc["curr_temp_tpl"] = currtemp_str;
|
doc["curr_temp_tpl"] = currtemp_str;
|
||||||
|
|
||||||
doc["min_temp"] = "5";
|
doc["min_temp"] = F("5");
|
||||||
doc["max_temp"] = "40";
|
doc["max_temp"] = F("40");
|
||||||
doc["temp_step"] = "0.5";
|
doc["temp_step"] = F("0.5");
|
||||||
|
|
||||||
JsonArray modes = doc.createNestedArray("modes");
|
JsonArray modes = doc.createNestedArray(F("modes"));
|
||||||
uint8_t flags = this->model();
|
uint8_t flags = this->model();
|
||||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
||||||
modes.add("night");
|
modes.add(F("night"));
|
||||||
modes.add("day");
|
modes.add(F("day"));
|
||||||
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||||
modes.add("eco");
|
modes.add(F("eco"));
|
||||||
modes.add("comfort");
|
modes.add(F("comfort"));
|
||||||
modes.add("auto");
|
modes.add(F("auto"));
|
||||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||||
modes.add("nofrost");
|
modes.add(F("nofrost"));
|
||||||
modes.add("eco");
|
modes.add(F("eco"));
|
||||||
modes.add("heat");
|
modes.add(F("heat"));
|
||||||
modes.add("auto");
|
modes.add(F("auto"));
|
||||||
} else { // default for all other thermostats
|
} else { // default for all other thermostats
|
||||||
modes.add("night");
|
modes.add(F("night"));
|
||||||
modes.add("day");
|
modes.add(F("day"));
|
||||||
modes.add("auto");
|
modes.add(F("auto"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonObject dev = doc.createNestedObject(F("dev"));
|
||||||
|
JsonArray ids = dev.createNestedArray(F("ids"));
|
||||||
|
ids.add(F("ems-esp"));
|
||||||
|
|
||||||
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
|
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
|
||||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
|
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/thermostat_hc%d/config"), hc_num);
|
||||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||||
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
||||||
|
|
||||||
// subscribe to the temp and mode commands
|
// enable the thermostat topic to take both mode strings and floats
|
||||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_temp"), hc_num);
|
register_mqtt_topic("thermostat", [&](const char * m) { return thermostat_ha_cmd(m); });
|
||||||
register_mqtt_topic(topic, [&](const char * m) { thermostat_cmd_temp(m); });
|
}
|
||||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_mode"), hc_num);
|
|
||||||
register_mqtt_topic(topic, [&](const char * m) { thermostat_cmd_mode(m); });
|
// for HA specifically when receiving over MQTT in the thermostat topic
|
||||||
|
// it could be either a 'mode' or a float value
|
||||||
|
// return true if it parses the message correctly
|
||||||
|
bool Thermostat::thermostat_ha_cmd(const char * message) {
|
||||||
|
// check if it's json. We know the message isn't empty
|
||||||
|
if (message[0] == '{') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for mode first
|
||||||
|
if (!set_mode(message, AUTO_HEATING_CIRCUIT)) {
|
||||||
|
// handle as a numerical temperature value
|
||||||
|
float f = strtof((char *)message, 0);
|
||||||
|
set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodes the thermostat mode for the heating circuit based on the thermostat type
|
// decodes the thermostat mode for the heating circuit based on the thermostat type
|
||||||
@@ -1463,7 +1472,6 @@ bool Thermostat::set_mode(const char * value, const int8_t id) {
|
|||||||
return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
|
return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1789,18 +1797,6 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for HA specifically when receiving over MQTT
|
|
||||||
bool Thermostat::thermostat_cmd_temp(const char * message) {
|
|
||||||
float f = strtof((char *)message, 0);
|
|
||||||
return set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for HA specifically when receiving over MQTT
|
|
||||||
// message payload holds the text name of the mode e.g. "auto"
|
|
||||||
bool Thermostat::thermostat_cmd_mode(const char * message) {
|
|
||||||
return set_mode(message, AUTO_HEATING_CIRCUIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) {
|
bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) {
|
||||||
float f = 0;
|
float f = 0;
|
||||||
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
|
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
|
||||||
@@ -1874,7 +1870,13 @@ void Thermostat::add_commands() {
|
|||||||
// common to all thermostats
|
// common to all thermostats
|
||||||
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
|
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
|
||||||
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); });
|
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); });
|
||||||
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); });
|
register_mqtt_cmd(F("mode"), [=](const char * value, const int8_t id) {
|
||||||
|
if (!set_mode(value, id)) {
|
||||||
|
LOG_WARNING(F("Invalid mode %s. Cannot set"), value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
uint8_t model = this->model();
|
uint8_t model = this->model();
|
||||||
switch (model) {
|
switch (model) {
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ class Thermostat : public EMSdevice {
|
|||||||
|
|
||||||
std::string datetime_; // date and time stamp
|
std::string datetime_; // date and time stamp
|
||||||
|
|
||||||
uint8_t mqtt_format_; // single, nested or ha
|
|
||||||
bool changed_ = false;
|
bool changed_ = false;
|
||||||
|
|
||||||
// Installation parameters
|
// Installation parameters
|
||||||
@@ -220,6 +219,8 @@ class Thermostat : public EMSdevice {
|
|||||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
|
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
|
||||||
|
|
||||||
void register_mqtt_ha_config(uint8_t hc_num);
|
void register_mqtt_ha_config(uint8_t hc_num);
|
||||||
|
bool thermostat_ha_cmd(const char * message);
|
||||||
|
|
||||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||||
|
|
||||||
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
|
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
|
||||||
@@ -252,10 +253,6 @@ class Thermostat : public EMSdevice {
|
|||||||
bool set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num);
|
bool set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num);
|
||||||
bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
|
bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
|
||||||
|
|
||||||
// for HA specifically. MQTT functions.
|
|
||||||
bool thermostat_cmd_temp(const char * message);
|
|
||||||
bool thermostat_cmd_mode(const char * message);
|
|
||||||
|
|
||||||
// set functions - these use the id/hc
|
// set functions - these use the id/hc
|
||||||
bool set_mode(const char * value, const int8_t id);
|
bool set_mode(const char * value, const int8_t id);
|
||||||
bool set_control(const char * value, const int8_t id);
|
bool set_control(const char * value, const int8_t id);
|
||||||
|
|||||||
@@ -296,7 +296,6 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
|
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
|
||||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X and type %s"), topic.c_str(), this->device_id_, this->device_type_name().c_str());
|
|
||||||
Mqtt::subscribe(this->device_type_, topic, f);
|
Mqtt::subscribe(this->device_type_, topic, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ void EMSESP::init_tx() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return status of bus: connected, connected but Tx is broken, disconnected
|
// return status of bus: connected (0), connected but Tx is broken (1), disconnected (2)
|
||||||
uint8_t EMSESP::bus_status() {
|
uint8_t EMSESP::bus_status() {
|
||||||
if (!rxservice_.bus_connected()) {
|
if (!rxservice_.bus_connected()) {
|
||||||
return BUS_STATUS_OFFLINE;
|
return BUS_STATUS_OFFLINE;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
uint8_t Helpers::bool_format_ = 1; // on/off
|
uint8_t Helpers::bool_format_ = BOOL_FORMAT_ONOFF; // on/off
|
||||||
|
|
||||||
// like itoa but for hex, and quicker
|
// like itoa but for hex, and quicker
|
||||||
char * Helpers::hextoa(char * result, const uint8_t value) {
|
char * Helpers::hextoa(char * result, const uint8_t value) {
|
||||||
@@ -124,9 +124,9 @@ char * Helpers::smallitoa(char * result, const uint16_t value) {
|
|||||||
|
|
||||||
// work out how to display booleans
|
// work out how to display booleans
|
||||||
char * Helpers::render_boolean(char * result, bool value) {
|
char * Helpers::render_boolean(char * result, bool value) {
|
||||||
if (bool_format() == 1) {
|
if (bool_format() == BOOL_FORMAT_ONOFF) {
|
||||||
strlcpy(result, value ? "on" : "off", 5);
|
strlcpy(result, value ? "on" : "off", 5);
|
||||||
} else if (bool_format() == 2) {
|
} else if (bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||||
strlcpy(result, value ? "true" : "false", 7);
|
strlcpy(result, value ? "true" : "false", 7);
|
||||||
} else {
|
} else {
|
||||||
strlcpy(result, value ? "1" : "0", 2);
|
strlcpy(result, value ? "1" : "0", 2);
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
|
|
||||||
#include "telegram.h" // for EMS_VALUE_* settings
|
#include "telegram.h" // for EMS_VALUE_* settings
|
||||||
|
|
||||||
|
#define BOOL_FORMAT_ONOFF 1
|
||||||
|
#define BOOL_FORMAT_TRUEFALSE 2
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
class Helpers {
|
class Helpers {
|
||||||
|
|||||||
264
src/mqtt.cpp
264
src/mqtt.cpp
@@ -34,6 +34,7 @@ uint32_t Mqtt::publish_time_solar_;
|
|||||||
uint32_t Mqtt::publish_time_mixing_;
|
uint32_t Mqtt::publish_time_mixing_;
|
||||||
uint32_t Mqtt::publish_time_other_;
|
uint32_t Mqtt::publish_time_other_;
|
||||||
uint32_t Mqtt::publish_time_sensor_;
|
uint32_t Mqtt::publish_time_sensor_;
|
||||||
|
uint8_t Mqtt::mqtt_format_;
|
||||||
|
|
||||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||||
|
|
||||||
@@ -50,13 +51,19 @@ uuid::log::Logger Mqtt::logger_{F_(mqtt), uuid::log::Facility::DAEMON};
|
|||||||
void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) {
|
void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb) {
|
||||||
// check if we already have the topic subscribed, if so don't add it again
|
// check if we already have the topic subscribed, if so don't add it again
|
||||||
if (!mqtt_subfunctions_.empty()) {
|
if (!mqtt_subfunctions_.empty()) {
|
||||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
for (auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||||
if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) {
|
if ((mqtt_subfunction.device_type_ == device_type) && (strcmp(mqtt_subfunction.topic_.c_str(), topic.c_str()) == 0)) {
|
||||||
|
// add the function, in case its not there
|
||||||
|
if (cb) {
|
||||||
|
mqtt_subfunction.mqtt_subfunction_ = cb;
|
||||||
|
}
|
||||||
return; // it exists, exit
|
return; // it exists, exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(F("Subscribing MQTT topic %s for device type %s"), topic.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
|
||||||
|
|
||||||
// add to MQTT queue as a subscribe operation
|
// add to MQTT queue as a subscribe operation
|
||||||
auto message = queue_subscribe_message(topic);
|
auto message = queue_subscribe_message(topic);
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ void Mqtt::register_command(const uint8_t device_type, const uint8_t device_id,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function
|
Mqtt::subscribe(device_type, cmd_topic, nullptr); // use an empty function handler to signal this is a command function
|
||||||
}
|
}
|
||||||
@@ -151,10 +159,7 @@ void Mqtt::loop() {
|
|||||||
void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||||
shell.printfln(F("MQTT is %s"), connected() ? uuid::read_flash_string(F_(connected)).c_str() : uuid::read_flash_string(F_(disconnected)).c_str());
|
shell.printfln(F("MQTT is %s"), connected() ? uuid::read_flash_string(F_(connected)).c_str() : uuid::read_flash_string(F_(disconnected)).c_str());
|
||||||
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { shell.printfln(F_(mqtt_format_fmt), settings.mqtt_format); });
|
||||||
shell.printfln(F_(mqtt_heartbeat_fmt), settings.system_heartbeat ? F_(enabled) : F_(disabled));
|
|
||||||
shell.printfln(F_(mqtt_format_fmt), settings.mqtt_format);
|
|
||||||
});
|
|
||||||
|
|
||||||
shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_);
|
shell.printfln(F("MQTT publish fails count: %lu"), mqtt_publish_fails_);
|
||||||
shell.println();
|
shell.println();
|
||||||
@@ -215,7 +220,7 @@ void Mqtt::incoming(const char * topic, const char * payload) {
|
|||||||
// received an MQTT message that we subscribed too
|
// received an MQTT message that we subscribed too
|
||||||
void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return;
|
return; // ignore empty payloads
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert payload to a null-terminated char string
|
// convert payload to a null-terminated char string
|
||||||
@@ -230,56 +235,59 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
|||||||
for (const auto & mf : mqtt_subfunctions_) {
|
for (const auto & mf : mqtt_subfunctions_) {
|
||||||
if (strcmp(topic, mf.full_topic_.c_str()) == 0) {
|
if (strcmp(topic, mf.full_topic_.c_str()) == 0) {
|
||||||
if (mf.mqtt_subfunction_) {
|
if (mf.mqtt_subfunction_) {
|
||||||
(mf.mqtt_subfunction_)(message); // matching function, call it
|
// matching function, call it. If it returns true keep quit
|
||||||
return;
|
if ((mf.mqtt_subfunction_)(message)) {
|
||||||
} else {
|
return; // function executed successfully
|
||||||
// empty function. It's a command then. Find the command from the json and call it directly.
|
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, message);
|
|
||||||
if (error) {
|
|
||||||
LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char * command = doc["cmd"];
|
|
||||||
if (command == nullptr) {
|
|
||||||
LOG_ERROR(F("MQTT error: invalid payload cmd format. message=%s"), message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for hc and id, and convert to int
|
|
||||||
int8_t n = -1; // no value
|
|
||||||
if (doc.containsKey("hc")) {
|
|
||||||
n = doc["hc"];
|
|
||||||
} else if (doc.containsKey("id")) {
|
|
||||||
n = doc["id"];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cmd_known = false;
|
|
||||||
JsonVariant data = doc["data"];
|
|
||||||
|
|
||||||
JsonObject output; // empty object
|
|
||||||
|
|
||||||
if (data.is<char *>()) {
|
|
||||||
cmd_known = Command::call(mf.device_type_, command, data.as<char *>(), n, output);
|
|
||||||
} else if (data.is<int>()) {
|
|
||||||
char data_str[10];
|
|
||||||
cmd_known = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n, output);
|
|
||||||
} else if (data.is<float>()) {
|
|
||||||
char data_str[10];
|
|
||||||
cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n, output);
|
|
||||||
} else if (data.isNull()) {
|
|
||||||
cmd_known = Command::call(mf.device_type_, command, "", n, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cmd_known) {
|
|
||||||
LOG_ERROR(F("MQTT: no matching cmd or invalid data: %s"), message);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty function. It's a command then. Find the command from the json and call it directly.
|
||||||
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||||
|
DeserializationError error = deserializeJson(doc, message);
|
||||||
|
if (error) {
|
||||||
|
LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char * command = doc["cmd"];
|
||||||
|
if (command == nullptr) {
|
||||||
|
LOG_ERROR(F("MQTT error: invalid payload cmd format. message=%s"), message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hc and id, and convert to int
|
||||||
|
int8_t n = -1; // no value
|
||||||
|
if (doc.containsKey("hc")) {
|
||||||
|
n = doc["hc"];
|
||||||
|
} else if (doc.containsKey("id")) {
|
||||||
|
n = doc["id"];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmd_known = false;
|
||||||
|
JsonVariant data = doc["data"];
|
||||||
|
|
||||||
|
JsonObject output; // empty object
|
||||||
|
|
||||||
|
if (data.is<char *>()) {
|
||||||
|
cmd_known = Command::call(mf.device_type_, command, data.as<char *>(), n, output);
|
||||||
|
} else if (data.is<int>()) {
|
||||||
|
char data_str[10];
|
||||||
|
cmd_known = Command::call(mf.device_type_, command, Helpers::itoa(data_str, (int16_t)data.as<int>()), n, output);
|
||||||
|
} else if (data.is<float>()) {
|
||||||
|
char data_str[10];
|
||||||
|
cmd_known = Command::call(mf.device_type_, command, Helpers::render_value(data_str, (float)data.as<float>(), 2), n, output);
|
||||||
|
} else if (data.isNull()) {
|
||||||
|
cmd_known = Command::call(mf.device_type_, command, "", n, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmd_known) {
|
||||||
|
LOG_ERROR(F("MQTT: no matching cmd (%s), invalid data or command failed"), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we got here we didn't find a topic match
|
// if we got here we didn't find a topic match
|
||||||
LOG_ERROR(F("No MQTT handler found for topic %s and payload %s"), topic, message);
|
LOG_ERROR(F("No MQTT handler found for topic %s and payload %s"), topic, message);
|
||||||
}
|
}
|
||||||
@@ -343,6 +351,7 @@ void Mqtt::start() {
|
|||||||
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
|
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
|
||||||
mqtt_qos_ = mqttSettings.mqtt_qos;
|
mqtt_qos_ = mqttSettings.mqtt_qos;
|
||||||
mqtt_retain_ = mqttSettings.mqtt_retain;
|
mqtt_retain_ = mqttSettings.mqtt_retain;
|
||||||
|
mqtt_format_ = mqttSettings.mqtt_format;
|
||||||
});
|
});
|
||||||
|
|
||||||
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
||||||
@@ -458,9 +467,36 @@ void Mqtt::on_connect() {
|
|||||||
|
|
||||||
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
|
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
|
||||||
|
|
||||||
|
if (mqtt_format() == Format::HA) {
|
||||||
|
ha_status(); // create a device in HA
|
||||||
|
}
|
||||||
|
|
||||||
LOG_INFO(F("MQTT connected"));
|
LOG_INFO(F("MQTT connected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Home Assistant Discovery
|
||||||
|
// homeassistant/sensor/ems-esp/status/config
|
||||||
|
void Mqtt::ha_status() {
|
||||||
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
|
doc["name"] = F("EMS-ESP status");
|
||||||
|
doc["uniq_id"] = F("status");
|
||||||
|
doc["avty_t"] = F("ems-esp/status");
|
||||||
|
doc["json_attr_t"] = F("ems-esp/heartbeat");
|
||||||
|
doc["stat_t"] = F("ems-esp/heartbeat");
|
||||||
|
doc["val_tpl"] = F("{{value_json['status']}}");
|
||||||
|
doc["ic"] = F("mdi:home-thermometer-outline");
|
||||||
|
|
||||||
|
JsonObject dev = doc.createNestedObject("dev");
|
||||||
|
dev["name"] = F("EMS-ESP");
|
||||||
|
dev["sw"] = EMSESP_APP_VERSION;
|
||||||
|
dev["mf"] = F("proddy");
|
||||||
|
dev["mdl"] = F("EMS-ESP");
|
||||||
|
JsonArray ids = dev.createNestedArray("ids");
|
||||||
|
ids.add("ems-esp");
|
||||||
|
|
||||||
|
Mqtt::publish_retain(F("homeassistant/sensor/ems-esp/status/config"), doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
||||||
|
}
|
||||||
|
|
||||||
// add sub or pub task to the queue.
|
// add sub or pub task to the queue.
|
||||||
// a fully-qualified topic is created by prefixing the hostname, unless it's HA
|
// a fully-qualified topic is created by prefixing the hostname, unless it's HA
|
||||||
// returns a pointer to the message created
|
// returns a pointer to the message created
|
||||||
@@ -478,8 +514,8 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// prefix the hostname
|
// prefix the hostname
|
||||||
std::string full_topic(50, '\0');
|
std::string full_topic(100, '\0');
|
||||||
snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), Mqtt::hostname_.c_str(), topic.c_str());
|
snprintf_P(&full_topic[0], full_topic.capacity() + 1, PSTR("%s/%s"), hostname_.c_str(), topic.c_str());
|
||||||
// message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain);
|
// message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain);
|
||||||
message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain);
|
message = std::make_shared<MqttMessage>(operation, full_topic, std::move(payload), retain);
|
||||||
}
|
}
|
||||||
@@ -508,6 +544,12 @@ void Mqtt::publish(const std::string & topic, const std::string & payload) {
|
|||||||
queue_publish_message(topic, payload, mqtt_retain_);
|
queue_publish_message(topic, payload, mqtt_retain_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MQTT Publish, using a user's retain flag - except for char * strings
|
||||||
|
void Mqtt::publish(const __FlashStringHelper * topic, const char * payload) {
|
||||||
|
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MQTT Publish, using a specific retain flag, topic is a flash string
|
// MQTT Publish, using a specific retain flag, topic is a flash string
|
||||||
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload) {
|
void Mqtt::publish(const __FlashStringHelper * topic, const std::string & payload) {
|
||||||
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_);
|
queue_publish_message(uuid::read_flash_string(topic), payload, mqtt_retain_);
|
||||||
@@ -517,25 +559,13 @@ void Mqtt::publish(const __FlashStringHelper * topic, const JsonObject & payload
|
|||||||
publish(uuid::read_flash_string(topic), payload);
|
publish(uuid::read_flash_string(topic), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
|
// publish json doc, only if its not empty
|
||||||
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
|
|
||||||
queue_publish_message(uuid::read_flash_string(topic), payload, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
|
|
||||||
std::string payload_text;
|
|
||||||
serializeJson(payload, payload_text); // convert json to string
|
|
||||||
queue_publish_message(topic, payload_text, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain) {
|
|
||||||
publish_retain(uuid::read_flash_string(topic), payload, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
|
void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
|
||||||
std::string payload_text;
|
if (payload.size()) {
|
||||||
serializeJson(payload, payload_text); // convert json to string
|
std::string payload_text;
|
||||||
queue_publish_message(topic, payload_text, mqtt_retain_);
|
serializeJson(payload, payload_text); // convert json to string
|
||||||
|
queue_publish_message(topic, payload_text, mqtt_retain_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for booleans, which get converted to string values 1 and 0
|
// for booleans, which get converted to string values 1 and 0
|
||||||
@@ -552,6 +582,24 @@ void Mqtt::publish(const std::string & topic) {
|
|||||||
queue_publish_message(topic, "", false);
|
queue_publish_message(topic, "", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
|
||||||
|
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
|
||||||
|
queue_publish_message(uuid::read_flash_string(topic), payload, retain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish json doc, only if its not empty, using the retain flag
|
||||||
|
void Mqtt::publish_retain(const std::string & topic, const JsonObject & payload, bool retain) {
|
||||||
|
if (payload.size()) {
|
||||||
|
std::string payload_text;
|
||||||
|
serializeJson(payload, payload_text); // convert json to string
|
||||||
|
queue_publish_message(topic, payload_text, retain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mqtt::publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain) {
|
||||||
|
publish_retain(uuid::read_flash_string(topic), payload, retain);
|
||||||
|
}
|
||||||
|
|
||||||
// take top from queue and perform the publish or subscribe action
|
// take top from queue and perform the publish or subscribe action
|
||||||
// assumes there is an MQTT connection
|
// assumes there is an MQTT connection
|
||||||
void Mqtt::process_queue() {
|
void Mqtt::process_queue() {
|
||||||
@@ -632,4 +680,80 @@ void Mqtt::process_queue() {
|
|||||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HA config for a binary_sensor
|
||||||
|
void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const char * entity) {
|
||||||
|
if (mqtt_format() != Format::HA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
|
|
||||||
|
doc["name"] = name;
|
||||||
|
doc["uniq_id"] = entity;
|
||||||
|
|
||||||
|
std::string state_t(50, '\0');
|
||||||
|
snprintf_P(&state_t[0], state_t.capacity() + 1, PSTR("%s/%s"), hostname_.c_str(), entity);
|
||||||
|
doc["stat_t"] = state_t;
|
||||||
|
|
||||||
|
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||||
|
if (settings.bool_format == BOOL_FORMAT_ONOFF) {
|
||||||
|
doc[F("payload_on")] = F("on");
|
||||||
|
doc[F("payload_off")] = F("off");
|
||||||
|
} else if (settings.bool_format == BOOL_FORMAT_TRUEFALSE) {
|
||||||
|
doc[F("payload_on")] = F("true");
|
||||||
|
doc[F("payload_off")] = F("false");
|
||||||
|
} else {
|
||||||
|
doc[F("payload_on")] = "1";
|
||||||
|
doc[F("payload_off")] = "0";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JsonObject dev = doc.createNestedObject(F("dev"));
|
||||||
|
JsonArray ids = dev.createNestedArray(F("ids"));
|
||||||
|
ids.add(F("ems-esp"));
|
||||||
|
|
||||||
|
std::string topic(100, '\0');
|
||||||
|
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity);
|
||||||
|
|
||||||
|
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// HA config for a normal sensor
|
||||||
|
void Mqtt::register_mqtt_ha_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity, const char * uom, const char * icon) {
|
||||||
|
if (mqtt_format() != Format::HA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||||
|
|
||||||
|
doc["name"] = name;
|
||||||
|
|
||||||
|
std::string uniq(50, '\0');
|
||||||
|
snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s"), entity);
|
||||||
|
|
||||||
|
doc["uniq_id"] = uniq;
|
||||||
|
doc["unit_of_meas"] = uom;
|
||||||
|
|
||||||
|
std::string state_t(50, '\0');
|
||||||
|
snprintf_P(&state_t[0], state_t.capacity() + 1, PSTR("%s/%s_data"), hostname_.c_str(), EMSdevice::device_type_2_device_name(device_type).c_str());
|
||||||
|
doc["stat_t"] = state_t;
|
||||||
|
|
||||||
|
std::string tpl(50, '\0');
|
||||||
|
snprintf_P(&tpl[0], tpl.capacity() + 1, PSTR("{{value_json.%s}}"), entity);
|
||||||
|
doc["val_tpl"] = tpl;
|
||||||
|
|
||||||
|
if (strlen(icon)) {
|
||||||
|
doc["ic"] = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject dev = doc.createNestedObject(F("dev"));
|
||||||
|
JsonArray ids = dev.createNestedArray(F("ids"));
|
||||||
|
ids.add(F("ems-esp"));
|
||||||
|
|
||||||
|
std::string topic(100, '\0');
|
||||||
|
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/%s/config"), entity);
|
||||||
|
|
||||||
|
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
|
|||||||
16
src/mqtt.h
16
src/mqtt.h
@@ -44,7 +44,7 @@ using uuid::console::Shell;
|
|||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
using mqtt_subfunction_p = std::function<void(const char * message)>;
|
using mqtt_subfunction_p = std::function<bool(const char * message)>;
|
||||||
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
|
using cmdfunction_p = std::function<bool(const char * data, const int8_t id)>;
|
||||||
|
|
||||||
struct MqttMessage {
|
struct MqttMessage {
|
||||||
@@ -81,6 +81,8 @@ class Mqtt {
|
|||||||
|
|
||||||
enum Operation { PUBLISH, SUBSCRIBE };
|
enum Operation { PUBLISH, SUBSCRIBE };
|
||||||
|
|
||||||
|
enum Format : uint8_t { SINGLE = 1, NESTED, HA, CUSTOM };
|
||||||
|
|
||||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
||||||
|
|
||||||
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb);
|
static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_subfunction_p cb);
|
||||||
@@ -90,6 +92,7 @@ class Mqtt {
|
|||||||
static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
|
static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb);
|
||||||
|
|
||||||
static void publish(const std::string & topic, const std::string & payload);
|
static void publish(const std::string & topic, const std::string & payload);
|
||||||
|
static void publish(const __FlashStringHelper * topic, const char * payload);
|
||||||
static void publish(const std::string & topic, const JsonObject & payload);
|
static void publish(const std::string & topic, const JsonObject & payload);
|
||||||
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
|
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
|
||||||
static void publish(const __FlashStringHelper * topic, const std::string & payload);
|
static void publish(const __FlashStringHelper * topic, const std::string & payload);
|
||||||
@@ -101,10 +104,14 @@ class Mqtt {
|
|||||||
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
|
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
|
||||||
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
|
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
|
||||||
|
|
||||||
|
static void register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const char * entity);
|
||||||
|
static void register_mqtt_ha_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity, const char * uom, const char * icon);
|
||||||
|
|
||||||
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
|
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
|
||||||
static void show_mqtt(uuid::console::Shell & shell);
|
static void show_mqtt(uuid::console::Shell & shell);
|
||||||
|
|
||||||
static void on_connect();
|
static void on_connect();
|
||||||
|
static void ha_status();
|
||||||
|
|
||||||
void disconnect() {
|
void disconnect() {
|
||||||
mqttClient_->disconnect();
|
mqttClient_->disconnect();
|
||||||
@@ -126,6 +133,10 @@ class Mqtt {
|
|||||||
mqtt_publish_fails_ = 0;
|
mqtt_publish_fails_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t mqtt_format() {
|
||||||
|
return mqtt_format_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uuid::log::Logger logger_;
|
static uuid::log::Logger logger_;
|
||||||
|
|
||||||
@@ -151,7 +162,7 @@ class Mqtt {
|
|||||||
static size_t maximum_mqtt_messages_;
|
static size_t maximum_mqtt_messages_;
|
||||||
static uint16_t mqtt_message_id_;
|
static uint16_t mqtt_message_id_;
|
||||||
|
|
||||||
static constexpr size_t MAX_MQTT_MESSAGES = 20; // size of queue
|
static constexpr size_t MAX_MQTT_MESSAGES = 30; // size of queue
|
||||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
|
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // delay between sending publishes, to account for large payloads
|
||||||
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
||||||
|
|
||||||
@@ -201,6 +212,7 @@ class Mqtt {
|
|||||||
static uint32_t publish_time_mixing_;
|
static uint32_t publish_time_mixing_;
|
||||||
static uint32_t publish_time_other_;
|
static uint32_t publish_time_other_;
|
||||||
static uint32_t publish_time_sensor_;
|
static uint32_t publish_time_sensor_;
|
||||||
|
static uint8_t mqtt_format_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace emsesp
|
} // namespace emsesp
|
||||||
|
|||||||
@@ -49,17 +49,12 @@ void Sensor::start() {
|
|||||||
|
|
||||||
// load the MQTT settings
|
// load the MQTT settings
|
||||||
void Sensor::reload() {
|
void Sensor::reload() {
|
||||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
|
||||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
|
||||||
});
|
|
||||||
|
|
||||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||||
dallas_gpio_ = settings.dallas_gpio;
|
dallas_gpio_ = settings.dallas_gpio;
|
||||||
parasite_ = settings.dallas_parasite;
|
parasite_ = settings.dallas_parasite;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mqtt_format_ == MQTT_format::HA) {
|
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
|
||||||
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
|
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
@@ -308,8 +303,10 @@ void Sensor::publish_values() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t mqtt_format_ = Mqtt::mqtt_format();
|
||||||
|
|
||||||
// single mode as e.g. ems-esp/sensor_28-EA41-9497-0E03-5F = {"temp":20.2}
|
// single mode as e.g. ems-esp/sensor_28-EA41-9497-0E03-5F = {"temp":20.2}
|
||||||
if (mqtt_format_ == MQTT_format::SINGLE) {
|
if (mqtt_format_ == Mqtt::Format::SINGLE) {
|
||||||
StaticJsonDocument<100> doc;
|
StaticJsonDocument<100> doc;
|
||||||
for (const auto & device : devices_) {
|
for (const auto & device : devices_) {
|
||||||
char topic[60];
|
char topic[60];
|
||||||
@@ -328,10 +325,10 @@ void Sensor::publish_values() {
|
|||||||
for (const auto & device : devices_) {
|
for (const auto & device : devices_) {
|
||||||
char s[7];
|
char s[7];
|
||||||
|
|
||||||
if (mqtt_format_ == MQTT_format::CUSTOM) {
|
if (mqtt_format_ == Mqtt::Format::CUSTOM) {
|
||||||
// e.g. sensor_data = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
|
// e.g. sensor_data = {28-EA41-9497-0E03-5F":23.30,"28-233D-9497-0C03-8B":24.0}
|
||||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
|
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 1);
|
||||||
} else if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
|
} else if ((mqtt_format_ == Mqtt::Format::NESTED) || (mqtt_format_ == Mqtt::Format::HA)) {
|
||||||
// e.g. sensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
|
// e.g. sensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.30"},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":"24.0"}}
|
||||||
char sensorID[20]; // sensor{1-n}
|
char sensorID[20]; // sensor{1-n}
|
||||||
strlcpy(sensorID, "sensor", 20);
|
strlcpy(sensorID, "sensor", 20);
|
||||||
@@ -342,34 +339,31 @@ void Sensor::publish_values() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// special for HA
|
// special for HA
|
||||||
if (mqtt_format_ == MQTT_format::HA) {
|
if (mqtt_format_ == Mqtt::Format::HA) {
|
||||||
std::string topic(100, '\0');
|
std::string topic(100, '\0');
|
||||||
// create the config if this hasn't already been done
|
// create the config if this hasn't already been done
|
||||||
/* e.g.
|
// to e.g. homeassistant/sensor/ems-esp/dallas_sensor1/config
|
||||||
{
|
|
||||||
"dev_cla": "temperature",
|
|
||||||
"stat_t": "homeassistant/sensor/ems-esp/state",
|
|
||||||
"unit_of_meas": "°C",
|
|
||||||
"val_tpl": "{{value_json.sensor2.temp}}",
|
|
||||||
"name": "ems-esp-sensor2",
|
|
||||||
"uniq_id": "ems-esp-sensor2"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!(registered_ha_[i])) {
|
if (!(registered_ha_[i])) {
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config;
|
||||||
config["dev_cla"] = "temperature";
|
config["dev_cla"] = F("temperature");
|
||||||
config["stat_t"] = "homeassistant/sensor/ems-esp/state";
|
config["stat_t"] = F("ems-esp/sensor_data");
|
||||||
config["unit_of_meas"] = "°C";
|
config["unit_of_meas"] = F("°C");
|
||||||
|
|
||||||
std::string str(50, '\0');
|
std::string str(50, '\0');
|
||||||
snprintf_P(&str[0], 50, PSTR("{{value_json.sensor%d.temp}}"), i);
|
snprintf_P(&str[0], 50, PSTR("{{value_json.sensor%d.temp}}"), i);
|
||||||
config["val_tpl"] = str;
|
config["val_tpl"] = str;
|
||||||
|
|
||||||
snprintf_P(&str[0], 50, PSTR("ems-esp-sensor%d"), i);
|
snprintf_P(&str[0], 50, PSTR("Dallas sensor%d"), i);
|
||||||
config["name"] = str;
|
config["name"] = str;
|
||||||
|
|
||||||
|
snprintf_P(&str[0], 50, PSTR("dalas_sensor%d"), i);
|
||||||
config["uniq_id"] = str;
|
config["uniq_id"] = str;
|
||||||
|
|
||||||
snprintf_P(&topic[0], 50, PSTR("homeassistant/sensor/ems-esp/sensor%d/config"), i);
|
JsonObject dev = config.createNestedObject("dev");
|
||||||
|
JsonArray ids = dev.createNestedArray("ids");
|
||||||
|
ids.add("ems-esp");
|
||||||
|
|
||||||
|
snprintf_P(&topic[0], 60, PSTR("homeassistant/sensor/ems-esp/dallas_sensor%d/config"), i);
|
||||||
Mqtt::publish_retain(topic, config.as<JsonObject>(), false); // publish the config payload with no retain flag
|
Mqtt::publish_retain(topic, config.as<JsonObject>(), false); // publish the config payload with no retain flag
|
||||||
|
|
||||||
registered_ha_[i] = true;
|
registered_ha_[i] = true;
|
||||||
@@ -378,10 +372,8 @@ void Sensor::publish_values() {
|
|||||||
i++; // increment sensor count
|
i++; // increment sensor count
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
if (mqtt_format_ != Mqtt::Format::SINGLE) {
|
||||||
Mqtt::publish(F("sensor_data"), doc.as<JsonObject>());
|
Mqtt::publish(F("sensor_data"), doc.as<JsonObject>());
|
||||||
} else if (mqtt_format_ == MQTT_format::HA) {
|
|
||||||
Mqtt::publish(F("homeassistant/sensor/ems-esp/state"), doc.as<JsonObject>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ class Sensor {
|
|||||||
|
|
||||||
bool registered_ha_[MAX_SENSORS];
|
bool registered_ha_[MAX_SENSORS];
|
||||||
|
|
||||||
uint8_t mqtt_format_;
|
|
||||||
uint8_t retrycnt_ = 0;
|
uint8_t retrycnt_ = 0;
|
||||||
uint8_t dallas_gpio_ = 0;
|
uint8_t dallas_gpio_ = 0;
|
||||||
bool parasite_ = false;
|
bool parasite_ = false;
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ void Shower::start() {
|
|||||||
shower_timer_ = settings.shower_timer;
|
shower_timer_ = settings.shower_timer;
|
||||||
shower_alert_ = settings.shower_alert;
|
shower_alert_ = settings.shower_alert;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
|
||||||
|
Mqtt::register_mqtt_ha_binary_sensor(F("Shower Active"), "shower_active");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shower::loop() {
|
void Shower::loop() {
|
||||||
@@ -115,11 +119,11 @@ void Shower::shower_alert_start() {
|
|||||||
// returns true if added to MQTT queue went ok
|
// returns true if added to MQTT queue went ok
|
||||||
void Shower::publish_values() {
|
void Shower::publish_values() {
|
||||||
StaticJsonDocument<90> doc;
|
StaticJsonDocument<90> doc;
|
||||||
doc["shower_timer"] = shower_timer_ ? "1" : "0";
|
char s[50];
|
||||||
doc["shower_alert"] = shower_alert_ ? "1" : "0";
|
doc["shower_timer"] = Helpers::render_boolean(s, shower_timer_);
|
||||||
|
doc["shower_alert"] = Helpers::render_boolean(s, shower_alert_);
|
||||||
|
|
||||||
// only publish shower duration if there is a value
|
// only publish shower duration if there is a value
|
||||||
char s[50];
|
|
||||||
if (duration_ > SHOWER_MIN_DURATION) {
|
if (duration_ > SHOWER_MIN_DURATION) {
|
||||||
char buffer[16] = {0};
|
char buffer[16] = {0};
|
||||||
strlcpy(s, Helpers::itoa(buffer, (uint8_t)((duration_ / (1000 * 60)) % 60), 10), 50);
|
strlcpy(s, Helpers::itoa(buffer, (uint8_t)((duration_ / (1000 * 60)) % 60), 10), 50);
|
||||||
|
|||||||
@@ -130,10 +130,6 @@ void System::syslog_init() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::set_heartbeat(bool system_heartbeat) {
|
|
||||||
system_heartbeat_ = system_heartbeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
// first call. Sets memory and starts up the UART Serial bridge
|
// first call. Sets memory and starts up the UART Serial bridge
|
||||||
void System::start() {
|
void System::start() {
|
||||||
// set the inital free mem
|
// set the inital free mem
|
||||||
@@ -145,9 +141,6 @@ void System::start() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch system heartbeat
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat_ = settings.system_heartbeat; });
|
|
||||||
|
|
||||||
// print boot message
|
// print boot message
|
||||||
EMSESP::esp8266React.getWiFiSettingsService()->read(
|
EMSESP::esp8266React.getWiFiSettingsService()->read(
|
||||||
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
|
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
|
||||||
@@ -216,9 +209,7 @@ void System::loop() {
|
|||||||
uint32_t currentMillis = uuid::get_uptime();
|
uint32_t currentMillis = uuid::get_uptime();
|
||||||
if (!last_heartbeat_ || (currentMillis - last_heartbeat_ > SYSTEM_HEARTBEAT_INTERVAL)) {
|
if (!last_heartbeat_ || (currentMillis - last_heartbeat_ > SYSTEM_HEARTBEAT_INTERVAL)) {
|
||||||
last_heartbeat_ = currentMillis;
|
last_heartbeat_ = currentMillis;
|
||||||
if (system_heartbeat_) {
|
|
||||||
send_heartbeat();
|
send_heartbeat();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
@@ -249,6 +240,16 @@ void System::send_heartbeat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||||
|
|
||||||
|
uint8_t ems_status = EMSESP::bus_status();
|
||||||
|
if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) {
|
||||||
|
doc["status"] = "txerror";
|
||||||
|
} else if (ems_status == EMSESP::BUS_STATUS_CONNECTED) {
|
||||||
|
doc["status"] = "connected";
|
||||||
|
} else {
|
||||||
|
doc["status"] = "disconnected";
|
||||||
|
}
|
||||||
|
|
||||||
doc["rssid"] = rssid;
|
doc["rssid"] = rssid;
|
||||||
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||||
doc["uptime_sec"] = uuid::get_uptime_sec();
|
doc["uptime_sec"] = uuid::get_uptime_sec();
|
||||||
@@ -725,7 +726,7 @@ bool System::check_upgrade() {
|
|||||||
EMSESP::esp8266React.getMqttSettingsService()->update(
|
EMSESP::esp8266React.getMqttSettingsService()->update(
|
||||||
[&](MqttSettings & mqttSettings) {
|
[&](MqttSettings & mqttSettings) {
|
||||||
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
|
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
|
||||||
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? MQTT_format::NESTED : MQTT_format::SINGLE);
|
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? Mqtt::Format::NESTED : Mqtt::Format::SINGLE);
|
||||||
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
|
mqttSettings.mqtt_qos = mqtt["qos"] | 0;
|
||||||
mqttSettings.mqtt_retain = mqtt["retain"];
|
mqttSettings.mqtt_retain = mqtt["retain"];
|
||||||
mqttSettings.username = mqtt["user"] | "";
|
mqttSettings.username = mqtt["user"] | "";
|
||||||
@@ -733,7 +734,6 @@ bool System::check_upgrade() {
|
|||||||
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
|
mqttSettings.port = mqtt["port"] | FACTORY_MQTT_PORT;
|
||||||
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
|
mqttSettings.clientId = FACTORY_MQTT_CLIENT_ID;
|
||||||
mqttSettings.enabled = mqtt["enabled"];
|
mqttSettings.enabled = mqtt["enabled"];
|
||||||
mqttSettings.system_heartbeat = mqtt["heartbeat"];
|
|
||||||
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
|
mqttSettings.keepAlive = FACTORY_MQTT_KEEP_ALIVE;
|
||||||
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
|
mqttSettings.cleanSession = FACTORY_MQTT_CLEAN_SESSION;
|
||||||
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
|
mqttSettings.maxTopicLength = FACTORY_MQTT_MAX_TOPIC_LENGTH;
|
||||||
@@ -865,7 +865,6 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & outp
|
|||||||
node["keep_alive"] = settings.keepAlive;
|
node["keep_alive"] = settings.keepAlive;
|
||||||
node["clean_session"] = Helpers::render_boolean(s, settings.cleanSession);
|
node["clean_session"] = Helpers::render_boolean(s, settings.cleanSession);
|
||||||
node["max_topic_length"] = settings.maxTopicLength;
|
node["max_topic_length"] = settings.maxTopicLength;
|
||||||
node["system_heartbeat"] = Helpers::render_boolean(s, settings.system_heartbeat);
|
|
||||||
node["publish_time_boiler"] = settings.publish_time_boiler;
|
node["publish_time_boiler"] = settings.publish_time_boiler;
|
||||||
node["publish_time_thermostat"] = settings.publish_time_thermostat;
|
node["publish_time_thermostat"] = settings.publish_time_thermostat;
|
||||||
node["publish_time_solar"] = settings.publish_time_solar;
|
node["publish_time_solar"] = settings.publish_time_solar;
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class System {
|
|||||||
|
|
||||||
bool check_upgrade();
|
bool check_upgrade();
|
||||||
void syslog_init();
|
void syslog_init();
|
||||||
void set_heartbeat(bool system_heartbeat);
|
|
||||||
void send_heartbeat();
|
void send_heartbeat();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -99,7 +98,6 @@ class System {
|
|||||||
static uint16_t analog_;
|
static uint16_t analog_;
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
bool system_heartbeat_;
|
|
||||||
static bool hide_led_;
|
static bool hide_led_;
|
||||||
uint8_t syslog_level_;
|
uint8_t syslog_level_;
|
||||||
uint32_t syslog_mark_interval_;
|
uint32_t syslog_mark_interval_;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace emsesp {
|
|||||||
// used with the 'test' command, under su/admin
|
// used with the 'test' command, under su/admin
|
||||||
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||||
if (command == "default") {
|
if (command == "default") {
|
||||||
run_test(shell, "cmd"); // add the default test case here
|
run_test(shell, "mqtt"); // add the default test case here
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.empty()) {
|
if (command.empty()) {
|
||||||
@@ -143,6 +143,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
|||||||
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
|
uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for boiler and controller on same product_id
|
||||||
if (command == "double") {
|
if (command == "double") {
|
||||||
// question: do we need to set the mask?
|
// question: do we need to set the mask?
|
||||||
std::string version("1.2.3");
|
std::string version("1.2.3");
|
||||||
@@ -572,22 +573,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
|||||||
if (command == "cmd") {
|
if (command == "cmd") {
|
||||||
shell.printfln(F("Testing Commands..."));
|
shell.printfln(F("Testing Commands..."));
|
||||||
|
|
||||||
// change MQTT format
|
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
|
|
||||||
mqttSettings.mqtt_format = MQTT_format::SINGLE;
|
|
||||||
// mqttSettings.mqtt_format = MQTT_format::NESTED;
|
|
||||||
// mqttSettings.mqtt_format = MQTT_format::HA;
|
|
||||||
return StateUpdateResult::CHANGED;
|
|
||||||
});
|
|
||||||
|
|
||||||
shell.invoke_command("su");
|
|
||||||
shell.invoke_command("call");
|
|
||||||
shell.invoke_command("call system info");
|
|
||||||
|
|
||||||
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
|
||||||
strcpy(system_topic, "ems-esp/system");
|
|
||||||
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"info\"}"); // this should fail
|
|
||||||
|
|
||||||
// add a thermostat with 3 HCs
|
// add a thermostat with 3 HCs
|
||||||
std::string version("1.2.3");
|
std::string version("1.2.3");
|
||||||
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
|
EMSESP::add_device(0x10, 192, version, EMSdevice::Brand::JUNKERS); // FW120
|
||||||
@@ -596,6 +581,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
|||||||
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
|
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x00, 0xCF, 0x22, 0x2F, 0x10, 0x00, 0x2E, 0x24,
|
||||||
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC2
|
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03}); // HC2
|
||||||
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); // HC3
|
uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); // HC3
|
||||||
|
|
||||||
|
shell.invoke_command("help");
|
||||||
|
shell.invoke_command("su");
|
||||||
|
shell.invoke_command("call");
|
||||||
|
shell.invoke_command("call system info");
|
||||||
|
|
||||||
|
char system_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
|
strcpy(system_topic, "ems-esp/system");
|
||||||
|
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"info\"}"); // this should fail
|
||||||
|
|
||||||
|
shell.invoke_command("call thermostat wwmode"); // should do nothing
|
||||||
|
shell.invoke_command("call thermostat mode auto 2"); // should error, no hc2
|
||||||
|
shell.invoke_command("call thermostat temp 22.56");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == "pin") {
|
if (command == "pin") {
|
||||||
@@ -609,10 +607,13 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
|||||||
if (command == "mqtt") {
|
if (command == "mqtt") {
|
||||||
shell.printfln(F("Testing MQTT..."));
|
shell.printfln(F("Testing MQTT..."));
|
||||||
|
|
||||||
// EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
|
// change MQTT format
|
||||||
// mqttSettings.mqtt_format = MQTT_format::SINGLE;
|
EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
|
||||||
// return StateUpdateResult::CHANGED;
|
// mqttSettings.mqtt_format = Mqtt::Format::SINGLE;
|
||||||
// });
|
// mqttSettings.mqtt_format = Mqtt::Format::NESTED;
|
||||||
|
mqttSettings.mqtt_format = Mqtt::Format::HA;
|
||||||
|
return StateUpdateResult::CHANGED;
|
||||||
|
});
|
||||||
|
|
||||||
// add a boiler
|
// add a boiler
|
||||||
// question: do we need to set the mask?
|
// question: do we need to set the mask?
|
||||||
@@ -636,46 +637,48 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
|||||||
|
|
||||||
// test publish and adding to queue
|
// test publish and adding to queue
|
||||||
EMSESP::txservice_.flush_tx_queue();
|
EMSESP::txservice_.flush_tx_queue();
|
||||||
|
|
||||||
EMSESP::EMSESP::mqtt_.publish("boiler", "test me");
|
EMSESP::EMSESP::mqtt_.publish("boiler", "test me");
|
||||||
Mqtt::show_mqtt(shell); // show queue
|
Mqtt::show_mqtt(shell); // show queue
|
||||||
|
|
||||||
strcpy(boiler_topic, "ems-esp/boiler");
|
strcpy(boiler_topic, "ems-esp/boiler");
|
||||||
strcpy(thermostat_topic, "ems-esp/thermostat");
|
strcpy(thermostat_topic, "ems-esp/thermostat");
|
||||||
strcpy(system_topic, "ems-esp/saystem");
|
strcpy(system_topic, "ems-esp/system");
|
||||||
|
|
||||||
|
EMSESP::mqtt_.incoming(boiler_topic, ""); // test if ignore empty payloads // invalid format
|
||||||
|
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format
|
EMSESP::mqtt_.incoming(boiler_topic, "12345"); // invalid format
|
||||||
EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic
|
EMSESP::mqtt_.incoming("bad_topic", "12345"); // no matching topic
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // should report error
|
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"garbage\",\"data\":22.52}"); // should report error
|
||||||
|
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"comfort\",\"data\":\"eco\"}");
|
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"comfort\",\"data\":\"eco\"}");
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}");
|
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":\"1\"}"); // with quotes
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":1}");
|
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"wwactivated\",\"data\":1}"); // without quotes
|
||||||
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"flowtemp\",\"data\":55}");
|
EMSESP::mqtt_.incoming(boiler_topic, "{\"cmd\":\"flowtemp\",\"data\":55}");
|
||||||
|
|
||||||
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}");
|
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"send\",\"data\":\"01 02 03 04 05\"}");
|
||||||
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"pin\",\"id\":12,\"data\":\"1\"}");
|
EMSESP::mqtt_.incoming(system_topic, "{\"cmd\":\"pin\",\"id\":12,\"data\":\"1\"}");
|
||||||
|
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"wwmode\",\"data\":\"auto\"}");
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":\"1\"}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":\"1\"}"); // RC35 only, should error
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"control\",\"data\":1}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"poep\",\"id\":2}"); // invalid mode
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"id\":2}");
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":2}"); // hc as number
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":19.5,\"hc\":1}"); // data as number
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":19.5,\"hc\":1}"); // data as number
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"mode\",\"data\":\"auto\",\"hc\":\"2\"}"); // hc as string. should error as no hc2
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22.56}");
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":22}");
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":\"22.56\"}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"data\":\"22.56\"}");
|
||||||
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"id\":2,\"data\":22}");
|
EMSESP::mqtt_.incoming(thermostat_topic, "{\"cmd\":\"temp\",\"id\":2,\"data\":22}");
|
||||||
|
|
||||||
|
// test single commands
|
||||||
|
EMSESP::mqtt_.incoming(thermostat_topic, "auto");
|
||||||
|
EMSESP::mqtt_.incoming(thermostat_topic, "heat");
|
||||||
|
EMSESP::mqtt_.incoming(thermostat_topic, "28.8");
|
||||||
|
|
||||||
// EMSESP::txservice_.show_tx_queue();
|
// EMSESP::txservice_.show_tx_queue();
|
||||||
// EMSESP::publish_all_values();
|
// EMSESP::publish_all_values();
|
||||||
|
|
||||||
shell.invoke_command("su");
|
|
||||||
shell.invoke_command("help");
|
|
||||||
shell.invoke_command("call");
|
|
||||||
shell.invoke_command("call thermostat wwmode");
|
|
||||||
shell.invoke_command("call thermostat mode auto 2");
|
|
||||||
shell.invoke_command("call thermostat temp 22.56");
|
|
||||||
|
|
||||||
Mqtt::resubscribe();
|
Mqtt::resubscribe();
|
||||||
Mqtt::show_mqtt(shell); // show queue
|
Mqtt::show_mqtt(shell); // show queue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user