mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
alpha v4
This commit is contained in:
@@ -83,6 +83,7 @@ MAKE_PSTR_WORD(disconnect)
|
||||
MAKE_PSTR_WORD(debug)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(format)
|
||||
|
||||
// context menus
|
||||
MAKE_PSTR_WORD(mqtt)
|
||||
|
||||
@@ -187,7 +187,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
||||
}
|
||||
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) {
|
||||
DEBUG_LOG(F("Registering new MQTT topic for device ID %02X"), this->device_id_);
|
||||
DEBUG_LOG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
}
|
||||
|
||||
|
||||
@@ -570,7 +570,8 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
|
||||
// are we waiting for a response from a recent Tx Read or Write?
|
||||
if (EMSbus::tx_waiting()) {
|
||||
// if it's a single byte 1 or 4 then its a response from the last write
|
||||
// if it's a single byte 1 or 4 then its maybe a response from the last write action
|
||||
EMSbus::tx_waiting(false); // reset Tx wait state
|
||||
if (length == 1) {
|
||||
if (first_value == TxService::TX_WRITE_SUCCESS) {
|
||||
DEBUG_LOG(F("Last Tx write successful. Sending read request."));
|
||||
@@ -581,13 +582,12 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
DEBUG_LOG(F("Last Tx write rejected by host"));
|
||||
txservice_.send_poll(); // close the bus
|
||||
} else {
|
||||
#ifdef EMSESP_DEBUG
|
||||
logger_.err(F("Expecting Tx ACK (1/4) but got 0x%02X. Tx:%s"), first_value, txservice_.last_tx_to_string().c_str());
|
||||
#endif
|
||||
// ignore it, it's probably a poll and we can wait for the next one
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// got a telegram. See if the src/dest matches that from the last one we sent
|
||||
// got a telegram with data in it. See if the src/dest matches that from the last one we sent
|
||||
// and continue to process it
|
||||
uint8_t src = data[0];
|
||||
uint8_t dest = data[1];
|
||||
if (txservice_.is_last_tx(src, dest)) {
|
||||
@@ -605,18 +605,21 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
}
|
||||
}
|
||||
EMSbus::tx_waiting(false); // reset Tx wait state
|
||||
}
|
||||
|
||||
// check for poll, if so, send Tx from the queue immediately
|
||||
// if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80)
|
||||
if ((length == 1) && ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id())) {
|
||||
EMSbus::last_bus_activity(millis()); // set the flag indication the EMS bus is active
|
||||
txservice_.send();
|
||||
// check for poll
|
||||
if (length == 1) {
|
||||
// check for poll to us, if so send top message from Tx queue immediately and quit
|
||||
// if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80)
|
||||
if ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id()) {
|
||||
EMSbus::last_bus_activity(millis()); // set the flag indication the EMS bus is active
|
||||
txservice_.send();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// add to RxQueue, what ever it is.
|
||||
rxservice_.add(data, length);
|
||||
}
|
||||
|
||||
// add to RxQueue
|
||||
rxservice_.add(data, length);
|
||||
}
|
||||
|
||||
// sends raw data of bytes along the Tx line
|
||||
|
||||
@@ -145,10 +145,15 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tel
|
||||
}
|
||||
|
||||
// Mixing on a MM10 - 0xAB
|
||||
// We assume MM10 is on HC2 and WM10 is using HC1 - https://github.com/proddy/EMS-ESP/issues/270
|
||||
// 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
|
||||
void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = 1; // fixed to circuit 1
|
||||
|
||||
// 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
|
||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||
hc_ = 0x22 - device_id();
|
||||
telegram->read_value(flowTemp_, 1); // is * 10
|
||||
telegram->read_value(pumpMod_, 3);
|
||||
telegram->read_value(flowSetTemp_, 0);
|
||||
|
||||
102
src/mqtt.cpp
102
src/mqtt.cpp
@@ -24,7 +24,9 @@ MAKE_PSTR_WORD(qos)
|
||||
MAKE_PSTR_WORD(base)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
MAKE_PSTR_WORD(ip)
|
||||
MAKE_PSTR_WORD(nested_json)
|
||||
MAKE_PSTR_WORD(nested)
|
||||
MAKE_PSTR_WORD(single)
|
||||
MAKE_PSTR_WORD(ha)
|
||||
MAKE_PSTR_WORD(publish_time)
|
||||
MAKE_PSTR_WORD(publish)
|
||||
MAKE_PSTR_WORD(connected)
|
||||
@@ -38,7 +40,7 @@ MAKE_PSTR(mqtt_enabled_fmt, "MQTT is %s")
|
||||
MAKE_PSTR(mqtt_base_fmt, "Base = %s")
|
||||
MAKE_PSTR(mqtt_qos_fmt, "QOS = %ld")
|
||||
MAKE_PSTR(mqtt_retain_fmt, "Retain Flag = %s")
|
||||
MAKE_PSTR(mqtt_nestedjson_fmt, "Use nested JSON = %s")
|
||||
MAKE_PSTR(mqtt_format_fmt, "JSON format = %s")
|
||||
MAKE_PSTR(mqtt_heartbeat_fmt, "Heartbeat = %s")
|
||||
MAKE_PSTR(mqtt_publish_time_fmt, "Publish time = %d seconds")
|
||||
|
||||
@@ -54,6 +56,7 @@ std::vector<Mqtt::MQTTFunction> Mqtt::mqtt_functions_;
|
||||
bool Mqtt::mqtt_retain_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
std::string Mqtt::mqtt_hostname_; // copy of hostname
|
||||
uint8_t Mqtt::mqtt_format_;
|
||||
std::string Mqtt::mqtt_base_;
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
@@ -71,9 +74,8 @@ Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMess
|
||||
packet_id_ = 0;
|
||||
}
|
||||
|
||||
MqttMessage::MqttMessage(uint64_t uptime_ms, uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
: uptime_ms(uptime_ms)
|
||||
, operation(operation)
|
||||
MqttMessage::MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
: operation(operation)
|
||||
, topic(topic)
|
||||
, payload(payload)
|
||||
, retain(retain) {
|
||||
@@ -107,6 +109,7 @@ void Mqtt::start() {
|
||||
mqtt_hostname_ = settings.hostname();
|
||||
mqtt_base_ = settings.mqtt_base();
|
||||
mqtt_qos_ = settings.mqtt_qos();
|
||||
mqtt_format_ = settings.mqtt_format();
|
||||
mqtt_retain_ = settings.mqtt_retain();
|
||||
mqtt_heartbeat_ = settings.mqtt_heartbeat();
|
||||
mqtt_publish_time_ = settings.mqtt_publish_time() * 1000; // convert to seconds
|
||||
@@ -196,8 +199,12 @@ Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic,
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb) {
|
||||
mqtt_functions_.emplace_back(device_id, std::move(topic), cb); // register a call back function for a specific telegram type
|
||||
queue_subscribe_message(topic); // add subscription to queue
|
||||
// We don't want to store the whole topic string in our lookup, just the last cmd, as this is wasteful.
|
||||
// strip out everything until the last /
|
||||
size_t found = topic.find_last_of("/"); // returns npos which is -1
|
||||
mqtt_functions_.emplace_back(device_id, std::move(topic.substr(found + 1)), cb); // register a call back function for a specific telegram type
|
||||
|
||||
queue_subscribe_message(topic); // add subscription to queue
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a device
|
||||
@@ -224,7 +231,7 @@ void Mqtt::loop() {
|
||||
force_publish_ = false;
|
||||
send_heartbeat(); // create a heartbeat payload
|
||||
EMSESP::publish_all_values(); // add sensors and mqtt to queue
|
||||
publish_all_queue(); // publish everything on queue
|
||||
process_all_queue(); // publish everything on queue
|
||||
}
|
||||
|
||||
// send out heartbeat
|
||||
@@ -243,7 +250,7 @@ void Mqtt::loop() {
|
||||
// publish top item from MQTT queue to stop flooding
|
||||
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
|
||||
last_mqtt_poll_ = currentMillis;
|
||||
publish_queue();
|
||||
process_queue();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -325,7 +332,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
return;
|
||||
}
|
||||
|
||||
// convert payload to a null-terminate char string
|
||||
// convert payload to a null-terminated char string
|
||||
char message[len + 2];
|
||||
strlcpy(message, payload, len + 1);
|
||||
|
||||
@@ -333,7 +340,8 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
DEBUG_LOG(F("Received %s => %s (length %d)"), topic, message, len);
|
||||
#endif
|
||||
|
||||
char * topic_magnitude = strrchr(topic, '/'); // strip out everything until last /
|
||||
// strip out everything until the last /
|
||||
char * topic_magnitude = strrchr(topic, '/');
|
||||
if (topic_magnitude != nullptr) {
|
||||
topic = topic_magnitude + 1;
|
||||
}
|
||||
@@ -341,7 +349,7 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
// Send message event to custom service
|
||||
// this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail
|
||||
for (const auto & mf : mqtt_functions_) {
|
||||
if (topic == mf.topic_) {
|
||||
if (strcmp(topic, mf.topic_.c_str()) == 0) {
|
||||
(mf.mqtt_function_)(message);
|
||||
return;
|
||||
}
|
||||
@@ -470,7 +478,7 @@ void Mqtt::queue_publish_message(const char * topic, const JsonDocument & payloa
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(uuid::get_uptime_ms(), Operation::PUBLISH, topic, payload_text, retain);
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, topic, payload_text, retain);
|
||||
|
||||
// DEBUG_LOG(F("Adding JSON publish message created with topic %s, message %s"), topic, payload_text.c_str());
|
||||
|
||||
@@ -484,11 +492,12 @@ void Mqtt::queue_publish_message(const char * topic, const JsonDocument & payloa
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
void Mqtt::queue_publish_message(const char * topic, const std::string & payload, const bool retain) {
|
||||
// can't have bogus topics, but empty payloads are ok
|
||||
if (strlen(topic) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(uuid::get_uptime_ms(), Operation::PUBLISH, topic, payload, retain);
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, topic, payload, retain);
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
@@ -498,13 +507,13 @@ void Mqtt::queue_publish_message(const char * topic, const std::string & payload
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
// add MQTT subscribe message to queue
|
||||
void Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(uuid::get_uptime_ms(), Operation::SUBSCRIBE, topic, "", false);
|
||||
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
|
||||
DEBUG_LOG(F("Adding a subscription for %s"), topic.c_str());
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
@@ -537,16 +546,21 @@ void Mqtt::publish(const char * topic, const bool value) {
|
||||
queue_publish_message(topic, value ? "1" : "0", mqtt_retain_);
|
||||
}
|
||||
|
||||
// no payload
|
||||
void Mqtt::publish(const char * topic) {
|
||||
queue_publish_message(topic, "", mqtt_retain_);
|
||||
}
|
||||
|
||||
// publish all queued messages to MQTT
|
||||
void Mqtt::publish_all_queue() {
|
||||
void Mqtt::process_all_queue() {
|
||||
while (!mqtt_messages_.empty()) {
|
||||
publish_queue();
|
||||
process_queue();
|
||||
}
|
||||
}
|
||||
|
||||
// take top from queue and try and publish it
|
||||
// assumes there is an MQTT connection
|
||||
void Mqtt::publish_queue() {
|
||||
void Mqtt::process_queue() {
|
||||
if (mqtt_messages_.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -554,8 +568,16 @@ void Mqtt::publish_queue() {
|
||||
// fetch first from queue and create the full topic name
|
||||
auto mqtt_message = mqtt_messages_.front();
|
||||
auto message = mqtt_message.content_;
|
||||
|
||||
// append the hostname and base to the topic, unless we're doing native HA which has a different format
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
make_topic(full_topic, message->topic);
|
||||
|
||||
// if the topic starts with "homeassistant" we leave it untouched, otherwise append ho st and base
|
||||
if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) {
|
||||
strcpy(full_topic, message->topic.c_str());
|
||||
} else {
|
||||
make_topic(full_topic, message->topic);
|
||||
}
|
||||
|
||||
// if we're subscribing...
|
||||
if (message->operation == Operation::SUBSCRIBE) {
|
||||
@@ -586,7 +608,7 @@ void Mqtt::publish_queue() {
|
||||
#else
|
||||
uint16_t packet_id = 1;
|
||||
#endif
|
||||
DEBUG_LOG(F("Published topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
|
||||
DEBUG_LOG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
|
||||
|
||||
if (packet_id == 0) {
|
||||
// it failed. if we retried n times, give up. remove from queue
|
||||
@@ -612,7 +634,7 @@ void Mqtt::publish_queue() {
|
||||
}
|
||||
|
||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||
}
|
||||
} // namespace emsesp
|
||||
|
||||
// add console commands
|
||||
void Mqtt::console_commands() {
|
||||
@@ -641,25 +663,27 @@ void Mqtt::console_commands() {
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::MQTT,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(nested_json)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
flash_string_vector{F_(set), F_(format)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
Settings settings;
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
settings.mqtt_nestedjson(true);
|
||||
settings.commit();
|
||||
shell.println(F("Please restart EMS-ESP"));
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
settings.mqtt_nestedjson(false);
|
||||
settings.commit();
|
||||
shell.println(F("Please restart EMS-ESP"));
|
||||
uint8_t value;
|
||||
if (arguments[0] == read_flash_string(F_(single))) {
|
||||
value = Settings::MQTT_format::SINGLE;
|
||||
} else if (arguments[0] == read_flash_string(F_(nested))) {
|
||||
value = Settings::MQTT_format::NESTED;
|
||||
} else if (arguments[0] == read_flash_string(F_(ha))) {
|
||||
value = Settings::MQTT_format::HA;
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
shell.println(F("Must be single, nested or ha"));
|
||||
return;
|
||||
}
|
||||
settings.mqtt_format(value);
|
||||
settings.commit();
|
||||
shell.println(F("Please restart EMS-ESP"));
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
return std::vector<std::string>{read_flash_string(F_(single)), read_flash_string(F_(nested)), read_flash_string(F_(ha))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::MQTT,
|
||||
@@ -818,11 +842,17 @@ void Mqtt::console_commands() {
|
||||
shell.printfln(F_(mqtt_base_fmt), settings.mqtt_base().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.mqtt_base().c_str());
|
||||
shell.printfln(F_(mqtt_qos_fmt), settings.mqtt_qos());
|
||||
shell.printfln(F_(mqtt_retain_fmt), settings.mqtt_retain() ? F_(enabled) : F_(disabled));
|
||||
shell.printfln(F_(mqtt_nestedjson_fmt), settings.mqtt_nestedjson() ? F_(enabled) : F_(disabled));
|
||||
if (settings.mqtt_format() == Settings::MQTT_format::SINGLE) {
|
||||
shell.printfln(F_(mqtt_format_fmt), F_(single));
|
||||
} else if (settings.mqtt_format() == Settings::MQTT_format::NESTED) {
|
||||
shell.printfln(F_(mqtt_format_fmt), F_(nested));
|
||||
} else if (settings.mqtt_format() == Settings::MQTT_format::HA) {
|
||||
shell.printfln(F_(mqtt_format_fmt), F_(ha));
|
||||
}
|
||||
shell.printfln(F_(mqtt_heartbeat_fmt), settings.mqtt_heartbeat() ? F_(enabled) : F_(disabled));
|
||||
shell.printfln(F_(mqtt_publish_time_fmt), settings.mqtt_publish_time());
|
||||
shell.println();
|
||||
});
|
||||
}
|
||||
} // namespace emsesp
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -49,10 +49,9 @@ using mqtt_function_p = std::function<void(const char * message)>;
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
struct MqttMessage {
|
||||
MqttMessage(uint64_t uptime_ms, uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
~MqttMessage() = default;
|
||||
|
||||
const uint64_t uptime_ms;
|
||||
const uint8_t operation;
|
||||
const std::string topic;
|
||||
const std::string payload;
|
||||
@@ -75,6 +74,7 @@ class Mqtt {
|
||||
static void publish(const char * topic, const JsonDocument & payload);
|
||||
static void publish(const char * topic, const JsonDocument & payload, bool retain);
|
||||
static void publish(const char * topic, const bool value);
|
||||
static void publish(const char * topic);
|
||||
|
||||
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id);
|
||||
|
||||
@@ -139,8 +139,8 @@ class Mqtt {
|
||||
void on_message(char * topic, char * payload, size_t len);
|
||||
void on_connect();
|
||||
static char * make_topic(char * result, const std::string & topic);
|
||||
void publish_queue();
|
||||
void publish_all_queue();
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
void send_start_topic();
|
||||
static void reconnect();
|
||||
void init();
|
||||
@@ -177,6 +177,7 @@ class Mqtt {
|
||||
static std::string mqtt_hostname_;
|
||||
static std::string mqtt_base_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint8_t mqtt_format_;
|
||||
std::string mqtt_ip_;
|
||||
std::string mqtt_user_;
|
||||
std::string mqtt_password_;
|
||||
|
||||
@@ -28,7 +28,7 @@ uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON}
|
||||
|
||||
void Sensors::start() {
|
||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
||||
mqtt_nestedjson_ = Settings().mqtt_nestedjson();
|
||||
mqtt_format_ = Settings().mqtt_format();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
bus_.begin(SENSOR_GPIO);
|
||||
@@ -39,34 +39,36 @@ void Sensors::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (state_ == State::IDLE) {
|
||||
if (millis() - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// DEBUG_LOG(F("Read sensor temperature"));
|
||||
// DEBUG_LOG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// logger_.err(F("Bus reset failed"));
|
||||
// no sensors found
|
||||
// logger_.err(F("Bus reset failed")); // uncomment for debug
|
||||
devices_.clear(); // remove all know devices incase we have a disconnect
|
||||
}
|
||||
last_activity_ = millis();
|
||||
}
|
||||
} else if (state_ == State::READING) {
|
||||
if (temperature_convert_complete()) {
|
||||
// DEBUG_LOG(F("Scanning for sensors"));
|
||||
// DEBUG_LOG(F("Scanning for sensors")); // uncomment for debug
|
||||
bus_.reset_search();
|
||||
found_.clear();
|
||||
|
||||
state_ = State::SCANNING;
|
||||
last_activity_ = millis();
|
||||
} else if (millis() - last_activity_ > READ_TIMEOUT_MS) {
|
||||
// logger_.err(F("Sensor read timeout"));
|
||||
logger_.err(F("Sensor read timeout"));
|
||||
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
}
|
||||
} else if (state_ == State::SCANNING) {
|
||||
if (millis() - last_activity_ > SCAN_TIMEOUT_MS) {
|
||||
// logger_.err(F("Sensor scan timeout"));
|
||||
logger_.err(F("Sensor scan timeout"));
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
} else {
|
||||
@@ -84,8 +86,13 @@ void Sensors::loop() {
|
||||
found_.emplace_back(addr);
|
||||
found_.back().temperature_c_ = get_temperature_c(addr);
|
||||
|
||||
// char result[10];
|
||||
// DEBUG_LOG(F("Temperature of %s = %s"), found_.back().to_string().c_str(), Helpers::render_value(result, found_.back().temperature_c_, 2));
|
||||
/*
|
||||
// comment out for debugging
|
||||
char result[10];
|
||||
DEBUG_LOG(F("Temp of %s = %s"),
|
||||
found_.back().to_string().c_str(),
|
||||
Helpers::render_value(result, found_.back().temperature_c_, 2));
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -99,7 +106,7 @@ void Sensors::loop() {
|
||||
bus_.depower();
|
||||
devices_ = std::move(found_);
|
||||
found_.clear();
|
||||
// DEBUG_LOG(F("Found %zu sensor(s)"), devices_.size());
|
||||
// DEBUG_LOG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = millis();
|
||||
}
|
||||
@@ -220,14 +227,14 @@ void Sensors::publish_values() {
|
||||
// if we're not using nested JSON, send each sensor out seperately
|
||||
// sensor1, sensor2 etc...
|
||||
// e.g. sensor_1 = {"temp":20.2}
|
||||
if (!mqtt_nestedjson_) {
|
||||
StaticJsonDocument<20> doc;
|
||||
if (mqtt_format_ != Settings::MQTT_format::NESTED) {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
char topic[60]; // sensors{1-n}
|
||||
strlcpy(topic, "sensor_", 50); // create topic
|
||||
strlcat(topic, device.to_string().c_str(), 50);
|
||||
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
|
||||
strlcat(topic, device.to_string().c_str(), 60);
|
||||
Mqtt::publish(topic, doc);
|
||||
doc.clear(); // clear json doc so we can reuse the buffer again
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class Sensors {
|
||||
static constexpr size_t SCRATCHPAD_TEMP_LSB = 0;
|
||||
static constexpr size_t SCRATCHPAD_CONFIG = 4;
|
||||
|
||||
// chips
|
||||
// dallas chips
|
||||
static constexpr uint8_t TYPE_DS18B20 = 0x28;
|
||||
static constexpr uint8_t TYPE_DS18S20 = 0x10;
|
||||
static constexpr uint8_t TYPE_DS1822 = 0x22;
|
||||
@@ -102,7 +102,7 @@ class Sensors {
|
||||
std::vector<Device> found_;
|
||||
std::vector<Device> devices_;
|
||||
|
||||
bool mqtt_nestedjson_;
|
||||
uint8_t mqtt_format_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace emsesp {
|
||||
EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_base, "", (), EMSESP_DEFAULT_MQTT_BASE) \
|
||||
EMSESP_SETTINGS_SIMPLE(uint8_t, "", mqtt_qos, "", (), EMSESP_DEFAULT_MQTT_QOS) \
|
||||
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_retain, "", (), EMSESP_DEFAULT_MQTT_RETAIN) \
|
||||
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_nestedjson, "", (), EMSESP_DEFAULT_MQTT_NESTEDJSON) \
|
||||
EMSESP_SETTINGS_SIMPLE(uint8_t, "", mqtt_format, "", (), EMSESP_DEFAULT_MQTT_FORMAT) \
|
||||
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_heartbeat, "", (), EMSESP_DEFAULT_MQTT_HEARTBEAT)
|
||||
|
||||
#define EMSESP_SETTINGS_SIMPLE EMSESP_SETTINGS_GENERIC
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
#define EMSESP_DEFAULT_MQTT_PORT 1883
|
||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||
#define EMSESP_DEFAULT_MQTT_RETAIN false
|
||||
#define EMSESP_DEFAULT_MQTT_NESTEDJSON true
|
||||
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested
|
||||
#define EMSESP_DEFAULT_MQTT_HEARTBEAT true
|
||||
#define EMSESP_DEFAULT_EMS_READ_ONLY false
|
||||
#define EMSESP_DEFAULT_SHOWER_TIMER false
|
||||
@@ -136,9 +136,6 @@ class Settings {
|
||||
bool mqtt_retain() const;
|
||||
void mqtt_retain(const bool & mqtt_retain);
|
||||
|
||||
bool mqtt_nestedjson() const;
|
||||
void mqtt_nestedjson(const bool & mqtt_nestedjson);
|
||||
|
||||
bool mqtt_heartbeat() const;
|
||||
void mqtt_heartbeat(const bool & mqtt_heartbeat);
|
||||
|
||||
@@ -151,6 +148,10 @@ class Settings {
|
||||
uint8_t master_thermostat() const;
|
||||
void master_thermostat(const uint8_t & master_thermostat);
|
||||
|
||||
enum MQTT_format : uint8_t { SINGLE = 1, NESTED, HA };
|
||||
uint8_t mqtt_format() const;
|
||||
void mqtt_format(const uint8_t & mqtt_format);
|
||||
|
||||
private:
|
||||
static constexpr size_t BUFFER_SIZE = 2048; // max size for the settings file
|
||||
|
||||
@@ -194,9 +195,9 @@ class Settings {
|
||||
static std::string mqtt_base_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static bool mqtt_retain_;
|
||||
static bool mqtt_nestedjson_;
|
||||
static bool mqtt_heartbeat_;
|
||||
static uint16_t mqtt_publish_time_; // frequency of MQTT publish in seconds
|
||||
static uint16_t mqtt_publish_time_; // seconds
|
||||
static uint8_t mqtt_format_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -25,7 +25,6 @@ MAKE_PSTR_WORD(mark)
|
||||
MAKE_PSTR_WORD(level)
|
||||
MAKE_PSTR_WORD(host)
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
|
||||
@@ -189,9 +189,8 @@ void Telegram::read_value8(int16_t & param, const uint8_t index) const {
|
||||
param = message_data[pos];
|
||||
}
|
||||
|
||||
RxService::QueuedRxTelegram::QueuedRxTelegram(uint16_t id, uint32_t timestamp, std::shared_ptr<Telegram> && telegram)
|
||||
RxService::QueuedRxTelegram::QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram)
|
||||
: id_(id)
|
||||
, timestamp_(timestamp)
|
||||
, telegram_(std::move(telegram)) {
|
||||
}
|
||||
|
||||
@@ -238,11 +237,6 @@ void RxService::loop() {
|
||||
// data is the whole telegram, assuming last byte holds the CRC
|
||||
// for EMS+ the type_id has the value + 256. We look for these type of telegrams with F7, F9 and FF in 3rd byte
|
||||
void RxService::add(uint8_t * data, uint8_t length) {
|
||||
// ignore any telegrams which are 1 byte
|
||||
if (length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the CRC
|
||||
uint8_t crc = calculate_crc(data, length - 1);
|
||||
if (data[length - 1] != crc) {
|
||||
@@ -313,8 +307,8 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
}
|
||||
|
||||
// add to queue, with timestamp
|
||||
DEBUG_LOG(F("New Rx [%d] telegram added, length %d"), rx_telegram_id_, message_length);
|
||||
rx_telegrams_.emplace_back(rx_telegram_id_++, millis(), std::move(telegram));
|
||||
DEBUG_LOG(F("New Rx [#%d] telegram added, length %d"), rx_telegram_id_, message_length);
|
||||
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -187,11 +187,10 @@ class RxService : public EMSbus {
|
||||
|
||||
class QueuedRxTelegram {
|
||||
public:
|
||||
QueuedRxTelegram(uint16_t id, uint32_t timestamp, std::shared_ptr<Telegram> && telegram);
|
||||
QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram);
|
||||
~QueuedRxTelegram() = default;
|
||||
|
||||
uint16_t id_; // sequential identifier
|
||||
uint32_t timestamp_; // time it was received
|
||||
uint16_t id_; // sequential identifier
|
||||
const std::shared_ptr<const Telegram> telegram_;
|
||||
};
|
||||
|
||||
|
||||
@@ -114,24 +114,76 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
uint8_t master_thermostat = settings.master_thermostat(); // what the user has defined
|
||||
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
|
||||
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
|
||||
mqtt_nested_json_ = settings.mqtt_nestedjson();
|
||||
mqtt_format_ = settings.mqtt_format(); // single, nested or ha
|
||||
|
||||
// if we're on auto mode, register this first one we find as we may find multiple
|
||||
// if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17
|
||||
// or if its the master thermostat we defined
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
|
||||
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17)))
|
||||
|| (master_thermostat == device_id)) {
|
||||
EMSESP::actual_master_thermostat(device_id);
|
||||
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X (as the master)"), device_id);
|
||||
|
||||
// MQTT callbacks
|
||||
register_mqtt_topic("thermostat_cmd", std::bind(&Thermostat::thermostat_cmd, this, _1));
|
||||
register_mqtt_topic("thermostat_cmd_temp", std::bind(&Thermostat::thermostat_cmd_temp, this, _1));
|
||||
register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
|
||||
|
||||
init_mqtt();
|
||||
} else {
|
||||
DEBUG_LOG(F("Registering new thermostat with device ID 0x%02X"), device_id);
|
||||
}
|
||||
}
|
||||
|
||||
// for the master thermostat initialize the MQTT subscribes
|
||||
void Thermostat::init_mqtt() {
|
||||
register_mqtt_topic("thermostat_cmd", std::bind(&Thermostat::thermostat_cmd, this, _1)); // generic commands
|
||||
|
||||
// if the MQTT format type is ha then send the config to HA (via the mqtt discovery service)
|
||||
// for each of the heating circuits
|
||||
if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
for (uint8_t hc = 0; hc < monitor_typeids.size(); hc++) {
|
||||
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/config"), hc + 1);
|
||||
|
||||
// Mqtt::publish(topic.c_str()); // empty payload, this remove any previous config sent to HA
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
std::string payload(100, '\0');
|
||||
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("thermostat_hc%d"), hc + 1);
|
||||
|
||||
doc["name"] = payload; // "name": "thermostat_hc1"
|
||||
doc["unique_id"] = payload; // "unique_id": "thermostat_hc1"
|
||||
|
||||
snprintf_P(&payload[0], payload.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat"), hc + 1);
|
||||
doc["~"] = payload; // "homeassistant/climate/hc1/thermostat"
|
||||
|
||||
doc["mode_cmd_t"] = "~/cmd_mode";
|
||||
doc["mode_stat_t"] = "~/state";
|
||||
doc["mode_stat_tpl"] = "{{value_json.mode}}";
|
||||
doc["temp_cmd_t"] = "~/cmd_temp";
|
||||
doc["temp_stat_t"] = "~/state";
|
||||
doc["temp_stat_tpl"] = "{{value_json.seltemp}}";
|
||||
doc["curr_temp_t"] = "~/state";
|
||||
doc["curr_temp_tpl"] = "{{value_json.currtemp}}";
|
||||
doc["min_temp"] = "5";
|
||||
doc["max_temp"] = "40";
|
||||
doc["temp_step"] = "0.5";
|
||||
|
||||
JsonArray modes = doc.createNestedArray("modes");
|
||||
modes.add("off");
|
||||
modes.add("heat");
|
||||
modes.add("auto");
|
||||
|
||||
Mqtt::publish(topic.c_str(), doc, true); // publish the config payload with retain flag
|
||||
|
||||
// subscribe to the temp and mode commands
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/cmd_temp"), hc + 1);
|
||||
register_mqtt_topic(topic, std::bind(&Thermostat::thermostat_cmd_temp, this, _1));
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/cmd_mode"), hc + 1);
|
||||
register_mqtt_topic(topic, std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
|
||||
}
|
||||
} else {
|
||||
// these will be prefixed with hostname and base
|
||||
register_mqtt_topic("thermostat_cmd_temp", std::bind(&Thermostat::thermostat_cmd_temp, this, _1));
|
||||
register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
|
||||
}
|
||||
}
|
||||
|
||||
// only add the menu for the master thermostat
|
||||
void Thermostat::add_context_menu() {
|
||||
if (device_id() != EMSESP::actual_master_thermostat()) {
|
||||
@@ -292,8 +344,8 @@ void Thermostat::publish_values() {
|
||||
JsonObject rootThermostat = doc.to<JsonObject>();
|
||||
JsonObject dataThermostat;
|
||||
|
||||
// optional, add external temp
|
||||
if (flags == EMS_DEVICE_FLAG_RC35) {
|
||||
// optional, add external temp. I don't think anyone actually is interested in this
|
||||
if ((flags == EMS_DEVICE_FLAG_RC35) && (mqtt_format_ == Settings::MQTT_format::SINGLE)) {
|
||||
if (dampedoutdoortemp != EMS_VALUE_INT_NOTSET) {
|
||||
doc["dampedtemp"] = dampedoutdoortemp;
|
||||
}
|
||||
@@ -312,7 +364,7 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
has_data = true;
|
||||
if (mqtt_nested_json_) {
|
||||
if (mqtt_format_ == Settings::MQTT_format::NESTED) {
|
||||
// create nested json for each HC
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
@@ -371,25 +423,54 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
if (hc->mode != EMS_VALUE_UINT_NOTSET) {
|
||||
dataThermostat["mode"] = mode_tostring(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 (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
|
||||
hc_mode = HeatingCircuit::Mode::HEAT;
|
||||
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
|
||||
hc_mode = HeatingCircuit::Mode::OFF;
|
||||
} else {
|
||||
hc_mode = HeatingCircuit::Mode::AUTO;
|
||||
}
|
||||
}
|
||||
dataThermostat["mode"] = mode_tostring(hc_mode);
|
||||
}
|
||||
if (hc->mode_type != EMS_VALUE_UINT_NOTSET) {
|
||||
|
||||
// special handling of mode type, for the RC35 replace with summer/holiday
|
||||
// https://github.com/proddy/EMS-ESP/issues/373#issuecomment-619810209
|
||||
if ((flags & 0x0F) == EMS_DEVICE_FLAG_RC35) {
|
||||
if (hc->holiday_mode != EMS_VALUE_UINT_NOTSET) {
|
||||
dataThermostat["modetype"] = F("holiday");
|
||||
} else if (hc->summer_mode != EMS_VALUE_UINT_NOTSET) {
|
||||
dataThermostat["modetype"] = F("summer");
|
||||
} else if (hc->mode_type != EMS_VALUE_UINT_NOTSET) {
|
||||
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
|
||||
}
|
||||
} else if (hc->mode_type != EMS_VALUE_UINT_NOTSET) {
|
||||
dataThermostat["modetype"] = mode_tostring(hc->get_mode_type(flags));
|
||||
}
|
||||
|
||||
// if its not nested, send immediately
|
||||
if (!mqtt_nested_json_) {
|
||||
|
||||
// if format is single, send immediately
|
||||
// if its HA send it to the special topic
|
||||
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
|
||||
char topic[30];
|
||||
char s[3]; // for formatting strings
|
||||
strlcpy(topic, "thermostat_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
return;
|
||||
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
|
||||
std::string topic(100, '\0');
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/hc%d/thermostat/state"), hc->hc_num());
|
||||
Mqtt::publish(topic.c_str(), doc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're using nested json, send all in one go
|
||||
if (mqtt_nested_json_ && has_data) {
|
||||
if ((mqtt_format_ == Settings::MQTT_format::NESTED) && has_data) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
}
|
||||
}
|
||||
@@ -408,7 +489,7 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const ui
|
||||
}
|
||||
|
||||
// determine which heating circuit the type ID is referring too
|
||||
// returns pointer to the HeatingCircuit
|
||||
// returns pointer to the HeatingCircuit or nullptr if it can't be found
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::shared_ptr<const Telegram> telegram) {
|
||||
// look through the Monitor and Set arrays to see if there is a match
|
||||
uint8_t hc_num = 0;
|
||||
@@ -430,9 +511,9 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
||||
}
|
||||
}
|
||||
|
||||
// still didn't recognize it, assume its hc 1
|
||||
// still didn't recognize it, ignore it
|
||||
if (hc_num == 0) {
|
||||
hc_num = DEFAULT_HEATING_CIRCUIT;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// if we have the heating circuit already present, returns its object
|
||||
@@ -446,7 +527,6 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
||||
// create a new heating circuit object
|
||||
// TODO do we need to create a new object if using emplace_back?
|
||||
heating_circuits_.emplace_back(new HeatingCircuit(hc_num, monitor_typeids[hc_num - 1], set_typeids[hc_num - 1]));
|
||||
|
||||
return heating_circuits_.back();
|
||||
}
|
||||
|
||||
@@ -826,8 +906,9 @@ void Thermostat::process_RC30Set(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
|
||||
void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
// exit if...
|
||||
// - the 15th byte (second from last) is 0x00, which I think is flow temp, means HC is not is use
|
||||
// exit if the 15th byte (second from last) is 0x00, which I think is calculated flow setpoint temperature
|
||||
// with weather controlled RC35s this value can be zero and our setpoint temps will be incorrect
|
||||
// see https://github.com/proddy/EMS-ESP/issues/373#issuecomment-627907301
|
||||
if (telegram->message_data[14] == 0x00) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,10 +104,11 @@ class Thermostat : public EMSdevice {
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
void init_mqtt();
|
||||
|
||||
std::string datetime_; // date and time stamp
|
||||
|
||||
bool mqtt_nested_json_;
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
|
||||
// Installation parameters
|
||||
uint8_t ibaMainDisplay =
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0a3"
|
||||
#define EMSESP_APP_VERSION "2.0.0a4"
|
||||
|
||||
Reference in New Issue
Block a user