diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index 1d609ff33..e251bbcf9 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -183,29 +183,43 @@ const MqttSettingsForm: FC = () => { control={} label="Publish command output to a 'response' topic" /> - } - label="Publish single value topics on change" - /> - - } - label="Enable MQTT Discovery (for Home Assistant, Domoticz)" - /> - - {data.ha_enabled && ( - - + + } + label="Publish single value topics on change" /> - )} + {data.publish_single && ( + + } + label="publish to command topics (ioBroker)" + /> + + )} + + + + } + label="Enable MQTT Discovery (for Home Assistant, Domoticz)" + /> + + {data.ha_enabled && ( + + + + )} + Publish Intervals (in seconds, 0=automatic) diff --git a/interface/src/types/mqtt.ts b/interface/src/types/mqtt.ts index a372f217f..1e2b50a00 100644 --- a/interface/src/types/mqtt.ts +++ b/interface/src/types/mqtt.ts @@ -40,5 +40,6 @@ export interface MqttSettings { nested_format: number; send_response: boolean; publish_single: boolean; + publish_single2cmd: boolean; discovery_prefix: string; } diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index df9866958..561d7926d 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -176,6 +176,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) { root["nested_format"] = settings.nested_format; root["discovery_prefix"] = settings.discovery_prefix; root["publish_single"] = settings.publish_single; + root["publish_single2cmd"] = settings.publish_single2cmd; root["send_response"] = settings.send_response; } @@ -203,11 +204,12 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME; - newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; - newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; - newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX; - newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE; - newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE; + newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED; + newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT; + newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX; + newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE; + newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD; + newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE; if (newSettings.enabled != settings.enabled) { changed = true; @@ -230,6 +232,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting changed = true; } + if (newSettings.publish_single2cmd != settings.publish_single2cmd) { + changed = true; + } + if (newSettings.send_response != settings.send_response) { changed = true; } diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index f51efb6a8..560e29c96 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -90,6 +90,7 @@ class MqttSettings { uint8_t nested_format; String discovery_prefix; bool publish_single; + bool publish_single2cmd; bool send_response; static void read(MqttSettings & settings, JsonObject & root); diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index d1eb9215f..fe7846a5b 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -395,7 +395,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i void DallasSensor::publish_sensor(const Sensor & sensor) { if (Mqtt::publish_single()) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str()); + if (Mqtt::publish_single2cmd()) { + snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str()); + } else { + snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", sensor.name().c_str()); + } char payload[10]; Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0)); } diff --git a/src/default_settings.h b/src/default_settings.h index d399f9ace..b70fef920 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -128,10 +128,6 @@ #define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off #endif -#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT -#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp -#endif - #ifndef EMSESP_DEFAULT_MQTT_QOS #define EMSESP_DEFAULT_MQTT_QOS 0 #endif @@ -160,6 +156,10 @@ #define EMSESP_DEFAULT_PUBLISH_SINGLE false #endif +#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE2CMD +#define EMSESP_DEFAULT_PUBLISH_SINGLE2CMD false +#endif + #ifndef EMSESP_DEFAULT_SEND_RESPONSE #define EMSESP_DEFAULT_SEND_RESPONSE false #endif diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 52ed4be07..0306e4b10 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -533,16 +533,26 @@ void EMSdevice::publish_value(void * value_p) { for (auto & dv : devicevalues_) { if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; - if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_HC8) - || (dv.tag >= DeviceValueTAG::TAG_WWC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) { + if (Mqtt::publish_single2cmd()) { + if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) { + snprintf(topic, + sizeof(topic), + "%s/%s/%s", + device_type_2_device_name(device_type_).c_str(), + tag_to_mqtt(dv.tag).c_str(), + read_flash_string(dv.short_name).c_str()); + } else { + snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str()); + } + } else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) { snprintf(topic, sizeof(topic), "%s/%s/%s", - device_type_2_device_name(device_type_).c_str(), + Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), tag_to_mqtt(dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); } else { - snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str()); + snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str()); } int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 5158a3643..ddd6d1b69 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -41,6 +41,7 @@ uint8_t Mqtt::nested_format_; std::string Mqtt::discovery_prefix_; bool Mqtt::send_response_; bool Mqtt::publish_single_; +bool Mqtt::publish_single2cmd_; std::deque Mqtt::mqtt_messages_; std::vector Mqtt::mqtt_subfunctions_; @@ -264,6 +265,14 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { LOG_DEBUG(F("Received topic `%s`"), topic); } #endif + // remove HA topics if we don't use discovery + if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) { + if (!ha_enabled_ && len) { // don't ping pong the empty message + queue_publish_message(topic, "", true); + LOG_DEBUG(F("Remove topic %s"), topic); + } + return; + } // check first againts any of our subscribed topics for (const auto & mf : mqtt_subfunctions_) { @@ -290,7 +299,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) { // convert payload into a json doc // if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value' if (len != 0) { - DeserializationError error = deserializeJson(input_doc, message); + DeserializationError error = deserializeJson(input_doc, (const char *)message); if ((!input_doc.containsKey("value") && !input_doc.containsKey("data")) || error) { input_doc.clear(); input_doc["value"] = (const char *)message; // always a string @@ -387,15 +396,16 @@ void Mqtt::reset_mqtt() { void Mqtt::load_settings() { EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { - mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string - mqtt_qos_ = mqttSettings.mqtt_qos; - mqtt_retain_ = mqttSettings.mqtt_retain; - mqtt_enabled_ = mqttSettings.enabled; - ha_enabled_ = mqttSettings.ha_enabled; - nested_format_ = mqttSettings.nested_format; - publish_single_ = mqttSettings.publish_single; - send_response_ = mqttSettings.send_response; - discovery_prefix_ = mqttSettings.discovery_prefix.c_str(); + mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string + mqtt_qos_ = mqttSettings.mqtt_qos; + mqtt_retain_ = mqttSettings.mqtt_retain; + mqtt_enabled_ = mqttSettings.enabled; + ha_enabled_ = mqttSettings.ha_enabled; + nested_format_ = mqttSettings.nested_format; + publish_single_ = mqttSettings.publish_single; + publish_single2cmd_ = mqttSettings.publish_single2cmd; + send_response_ = mqttSettings.send_response; + discovery_prefix_ = mqttSettings.discovery_prefix.c_str(); // convert to milliseconds publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000; @@ -444,14 +454,6 @@ void Mqtt::start() { if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { LOG_INFO(F("MQTT disconnected: Not authorized")); } - // remove message with pending ack - if (!mqtt_messages_.empty()) { - auto mqtt_message = mqtt_messages_.front(); - if (mqtt_message.packet_id_ != 0) { - mqtt_messages_.pop_front(); - } - } - // mqtt_messages_.clear(); }); // create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference @@ -570,9 +572,23 @@ void Mqtt::on_connect() { #endif publish(F_(info), doc.as()); // topic called "info" - // create the EMS-ESP device in HA, which is MQTT retained - if (ha_enabled()) { - ha_status(); + if (ha_enabled_) { + queue_unsubscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#"); + queue_unsubscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#"); + EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any + ha_status(); // create the EMS-ESP device in HA, which is MQTT retained + } else { + queue_subscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#"); + queue_subscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#"); + LOG_INFO(F("start removing topics %s/+/%s/#"), discovery_prefix_.c_str(), mqtt_base_.c_str()); } // send initial MQTT messages for some of our services @@ -582,8 +598,6 @@ void Mqtt::on_connect() { // re-subscribe to all custom registered MQTT topics resubscribe(); - EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any - publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on mqtt_publish_fails_ = 0; // reset fail count to 0 @@ -669,6 +683,7 @@ std::shared_ptr Mqtt::queue_message(const uint8_t operation, if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) { mqtt_messages_.pop_front(); LOG_WARNING(F("Queue overflow, removing one message")); + mqtt_publish_fails_++; } mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); @@ -688,6 +703,11 @@ std::shared_ptr Mqtt::queue_subscribe_message(const std::stri return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload } +// add MQTT unsubscribe message to queue +std::shared_ptr Mqtt::queue_unsubscribe_message(const std::string & topic) { + return queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload +} + // MQTT Publish, using a user's retain flag void Mqtt::publish(const std::string & topic, const std::string & payload) { queue_publish_message(topic, payload, mqtt_retain_); @@ -712,11 +732,6 @@ void Mqtt::publish(const std::string & topic, const JsonObject & payload) { publish_retain(topic, payload, mqtt_retain_); } -// no payload -void Mqtt::publish(const std::string & topic) { - 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(read_flash_string(topic), payload, retain); @@ -750,7 +765,7 @@ void Mqtt::publish_ha(const std::string & topic) { LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str()); #endif - publish(fulltopic); + queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker } // publish a Home Assistant config topic and payload, with retain flag off. @@ -792,12 +807,29 @@ void Mqtt::process_queue() { snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str()); } + // if this has already been published and we're waiting for an ACK, don't publish again + // it will have a real packet ID + if (mqtt_message.packet_id_ > 0) { +#if defined(EMSESP_DEBUG) + LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK")); +#endif + // if we don't get the ack within 10 minutes, republish with new packet_id + if (uuid::get_uptime_sec() - last_publish_queue_ < 600) { + return; + } + } + last_publish_queue_ = uuid::get_uptime_sec(); + // if we're subscribing... if (message->operation == Operation::SUBSCRIBE) { LOG_DEBUG(F("Subscribing to topic '%s'"), topic); uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_); if (!packet_id) { + if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { + return; + } LOG_ERROR(F("Error subscribing to topic '%s'"), topic); + mqtt_publish_fails_++; // increment failure counter } mqtt_messages_.pop_front(); // remove the message from the queue @@ -805,12 +837,20 @@ void Mqtt::process_queue() { return; } - // if this has already been published and we're waiting for an ACK, don't publish again - // it will have a real packet ID - if (mqtt_message.packet_id_ > 0) { -#if defined(EMSESP_DEBUG) - LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK")); -#endif + // if we're unsubscribing... + if (message->operation == Operation::UNSUBSCRIBE) { + LOG_DEBUG(F("Subscribing to topic '%s'"), topic); + uint16_t packet_id = mqttClient_->unsubscribe(topic); + if (!packet_id) { + if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) { + return; + } + LOG_ERROR(F("Error unsubscribing to topic '%s'"), topic); + mqtt_publish_fails_++; // increment failure counter + } + + mqtt_messages_.pop_front(); // remove the message from the queue + return; } @@ -992,8 +1032,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, return; } - bool have_tag = !EMSdevice::tag_to_string(tag).empty(); - bool is_nested = (nested_format_ == 1); // nested_format is 1 if nested, otherwise 2 for single topics + bool have_tag = !EMSdevice::tag_to_string(tag).empty(); // build the payload DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG); @@ -1069,7 +1108,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // value template // if its nested mqtt format then use the appended entity name, otherwise take the original char val_tpl[75]; - if (is_nested) { + if (is_nested()) { snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity); } else { snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str()); @@ -1193,10 +1232,11 @@ const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) { } // if there is a tag add it - if ((EMSdevice::tag_to_mqtt(tag).empty()) || ((nested_format_ == 1) && (device_type != EMSdevice::DeviceType::BOILER))) { - return EMSdevice::device_type_2_device_name(device_type) + "_data"; - } else { + if (!EMSdevice::tag_to_mqtt(tag).empty() + && ((device_type == EMSdevice::DeviceType::BOILER && tag == DeviceValueTAG::TAG_DEVICE_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) { return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag); + } else { + return EMSdevice::device_type_2_device_name(device_type) + "_data"; } } diff --git a/src/mqtt.h b/src/mqtt.h index 95b461de4..8fed88ec0 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -29,9 +29,6 @@ using uuid::console::Shell; -// time between HA publishes -#define MQTT_HA_PUBLISH_DELAY 50 - // size of queue #define MAX_MQTT_MESSAGES 300 @@ -70,16 +67,10 @@ class Mqtt { void set_publish_time_sensor(uint16_t publish_time); bool get_publish_onchange(uint8_t device_type); - enum Operation { PUBLISH, SUBSCRIBE }; + enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE }; + enum NestedFormat : uint8_t { NESTED = 1, SINGLE }; - enum HA_Climate_Format : uint8_t { - CURRENT = 1, // 1 - SETPOINT, // 2 - ZERO // 3 - - }; - - static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength + static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = FACTORY_MQTT_MAX_TOPIC_LENGTH; // fixed, not a user setting anymore static void on_connect(); @@ -92,7 +83,6 @@ class Mqtt { 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 std::string & payload); - static void publish(const std::string & topic); static void publish_retain(const std::string & topic, const JsonObject & 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); @@ -125,10 +115,6 @@ class Mqtt { static void ha_status(); - void disconnect() { - mqttClient_->disconnect(); - } - #if defined(EMSESP_DEBUG) void incoming(const char * topic, const char * payload = ""); // for testing only #endif @@ -179,13 +165,10 @@ class Mqtt { static void reset_mqtt(); - // nested_format is 1 if nested, otherwise 2 for single topics - static uint8_t nested_format() { - return nested_format_; - } static bool is_nested() { - return nested_format_ == 1; + return nested_format_ == NestedFormat::NESTED; } + static void nested_format(uint8_t nested_format) { nested_format_ = nested_format; } @@ -193,6 +176,11 @@ class Mqtt { static bool publish_single() { return publish_single_; } + + static bool publish_single2cmd() { + return publish_single2cmd_; + } + static void publish_single(bool publish_single) { publish_single_ = publish_single; } @@ -200,6 +188,7 @@ class Mqtt { static bool ha_enabled() { return ha_enabled_; } + static void ha_enabled(bool ha_enabled) { ha_enabled_ = ha_enabled; } @@ -207,6 +196,7 @@ class Mqtt { static bool send_response() { return send_response_; } + static void send_response(bool send_response) { send_response_ = send_response; } @@ -232,7 +222,7 @@ class Mqtt { uint16_t packet_id_; ~QueuedMqttMessage() = default; - QueuedMqttMessage(uint16_t id, std::shared_ptr && content) + QueuedMqttMessage(uint32_t id, std::shared_ptr && content) : id_(id) , content_(std::move(content)) { retry_count_ = 0; @@ -254,6 +244,7 @@ class Mqtt { static std::shared_ptr queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain); static std::shared_ptr queue_publish_message(const std::string & topic, const std::string & payload, bool retain); static std::shared_ptr queue_subscribe_message(const std::string & topic); + static std::shared_ptr queue_unsubscribe_message(const std::string & topic); void on_publish(uint16_t packetId); void on_message(const char * topic, const char * payload, size_t len); @@ -281,6 +272,7 @@ class Mqtt { uint32_t last_publish_mixer_ = 0; uint32_t last_publish_other_ = 0; uint32_t last_publish_sensor_ = 0; + uint32_t last_publish_queue_ = 0; static bool connecting_; static bool initialized_; @@ -303,6 +295,7 @@ class Mqtt { static uint8_t nested_format_; static std::string discovery_prefix_; static bool publish_single_; + static bool publish_single2cmd_; static bool send_response_; }; diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index c2efaffef..a012c2d35 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -144,7 +144,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { obj["t"] = sensor.type(); if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { - obj["v"] = Helpers::round2(sensor.value(), 1); // is optional and is a float + obj["v"] = Helpers::round2(sensor.value(), 0); // is optional and is a float } } }