From ae8700447ebd9d87b870af8de7cd2ebf2c475c93 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Oct 2019 15:46:39 +0200 Subject: [PATCH] Add sensor force_update option (#783) * Add sensor force_update option * Add test --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ esphome/components/sensor/__init__.py | 9 +++++---- esphome/components/sensor/sensor.h | 13 +++++++++++++ esphome/const.py | 1 + tests/test1.yaml | 1 + 8 files changed, 34 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e454bf1d31..175bd3858f 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -406,6 +406,7 @@ message ListEntitiesSensorResponse { string icon = 5; string unit_of_measurement = 6; int32 accuracy_decimals = 7; + bool force_update = 8; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c4fa89ef97..3f635d1cdb 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1359,6 +1359,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->accuracy_decimals = value.as_int32(); return true; } + case 8: { + this->force_update = value.as_bool(); + return true; + } default: return false; } @@ -1407,6 +1411,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_string(6, this->unit_of_measurement); buffer.encode_int32(7, this->accuracy_decimals); + buffer.encode_bool(8, this->force_update); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1440,6 +1445,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { sprintf(buffer, "%d", this->accuracy_decimals); out.append(buffer); out.append("\n"); + + out.append(" force_update: "); + out.append(YESNO(this->force_update)); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9997a68477..2e685959bf 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -364,6 +364,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { std::string icon{}; // NOLINT std::string unit_of_measurement{}; // NOLINT int32_t accuracy_decimals{0}; // NOLINT + bool force_update{false}; // NOLINT void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index a241cf6ed6..f87e7651b9 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -55,6 +55,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (!this->sensor_->get_icon().empty()) root["icon"] = this->sensor_->get_icon(); + if (this->sensor_->get_force_update()) + root["force_update"] = true; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index a9b00b7c08..11a6e5e173 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -6,10 +6,9 @@ from esphome import automation from esphome.components import mqtt from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, \ CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, CONF_INTERNAL, \ - CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, \ - CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_TO, CONF_TRIGGER_ID, \ - CONF_UNIT_OF_MEASUREMENT, \ - CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID + CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, \ + CONF_TO, CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID, \ + CONF_FORCE_UPDATE from esphome.core import CORE, coroutine, coroutine_with_priority from esphome.util import Registry @@ -87,6 +86,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ cv.Optional(CONF_UNIT_OF_MEASUREMENT): unit_of_measurement, cv.Optional(CONF_ICON): icon, cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, + cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All(cv.requires_component('mqtt'), cv.Any(None, cv.positive_time_period_milliseconds)), cv.Optional(CONF_FILTERS): validate_filters, @@ -258,6 +258,7 @@ def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if CONF_FILTERS in config: filters = yield build_filters(config[CONF_FILTERS]) cg.add(var.set_filters(filters)) diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 1c7c854394..14ace91b35 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -18,6 +18,9 @@ namespace sensor { if (!obj->unique_id().empty()) { \ ESP_LOGV(TAG, prefix " Unique ID: '%s'", obj->unique_id().c_str()); \ } \ + if (obj->get_force_update()) { \ + ESP_LOGV(TAG, prefix " Force Update: YES"); \ + } \ } /** Base-class for all sensors. @@ -142,6 +145,15 @@ class Sensor : public Nameable { void internal_send_state_to_frontend(float state); + bool get_force_update() const { return force_update_; } + /** Set this sensor's force_update mode. + * + * If the sensor is in force_update mode, the frontend is required to save all + * state changes to the database when they are published, even if the state is the + * same as before. + */ + void set_force_update(bool force_update) { force_update_ = force_update; } + protected: /** Override this to set the Home Assistant unit of measurement for this sensor. * @@ -174,6 +186,7 @@ class Sensor : public Nameable { optional accuracy_decimals_; Filter *filter_list_{nullptr}; ///< Store all active filters. bool has_state_{false}; + bool force_update_{false}; }; class PollingSensorComponent : public PollingComponent, public Sensor { diff --git a/esphome/const.py b/esphome/const.py index 524031da8c..ac020d00e6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -156,6 +156,7 @@ CONF_FILTERS = 'filters' CONF_FILTER_OUT = 'filter_out' CONF_FLASH_LENGTH = 'flash_length' CONF_FOR = 'for' +CONF_FORCE_UPDATE = 'force_update' CONF_FORMALDEHYDE = 'formaldehyde' CONF_FORMAT = 'format' CONF_FREQUENCY = 'frequency' diff --git a/tests/test1.yaml b/tests/test1.yaml index 5caa03cf03..2fbef9d4f7 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -185,6 +185,7 @@ sensor: accuracy_decimals: 5 expire_after: 120s setup_priority: -100 + force_update: true filters: - offset: 2.0 - multiply: 1.2