From c78fb90e2f3f89fd2a583296dce115eb46e82585 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 20 Sep 2021 04:30:41 +1200 Subject: [PATCH 1/7] Fix MQTT discovery for sensor state_class (#2331) --- esphome/components/sensor/sensor.cpp | 2 +- esphome/components/sensor/sensor.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1dbc1c901a..db81edcc61 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,7 +6,7 @@ namespace sensor { static const char *const TAG = "sensor"; -const char *state_class_to_string(StateClass state_class) { +std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 34b8b26a54..209e83d205 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -1,8 +1,8 @@ #pragma once +#include "esphome/components/sensor/filter.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/sensor/filter.h" namespace esphome { namespace sensor { @@ -13,7 +13,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->get_state_class()).c_str()); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -36,7 +36,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const char *state_class_to_string(StateClass state_class); +std::string state_class_to_string(StateClass state_class); /** Base-class for all sensors. * From 4c61cf153c8139acf20053fe4c8904fa45953dae Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:20 +0200 Subject: [PATCH 2/7] Light transition fixes (#2320) --- esphome/components/light/addressable_light.cpp | 11 ++++++----- esphome/components/light/light_state.cpp | 3 +++ esphome/components/light/transformers.h | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index a8fa2cd7ac..599d43d8b1 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -62,10 +62,13 @@ void AddressableLightTransformer::start() { } optional AddressableLightTransformer::apply() { - // Don't try to transition over running effects, instead immediately use the target values. write_state() and the - // effects pick up the change from current_values. + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); + + // When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the + // LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the + // effects which respect it. if (this->light_.is_effect_active()) - return this->target_values_; + return LightColorValues::lerp(this->get_start_values(), this->get_target_values(), smoothed_progress); // Use a specialized transition for addressable lights: instead of using a unified transition for // all LEDs, we use the current state of each LED as the start. @@ -75,8 +78,6 @@ optional AddressableLightTransformer::apply() { // Instead, we "fake" the look of the LERP by using an exponential average over time and using // dynamically-calculated alpha values to match the look. - float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - float denom = (1.0f - smoothed_progress); float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 945d3910d5..d9574abf7a 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -121,6 +121,9 @@ void LightState::loop() { } if (this->transformer_->is_finished()) { + // if the transition has written directly to the output, current_values is outdated, so update it + this->current_values = this->transformer_->get_target_values(); + this->transformer_->stop(); this->transformer_ = nullptr; this->target_state_reached_callback_.call(); diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index d501d53f72..90646f4e61 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -12,12 +12,18 @@ namespace light { class LightTransitionTransformer : public LightTransformer { public: void start() override { - // When turning light on from off state, use colors from target state. + // When turning light on from off state, use target state and only increase brightness from zero. if (!this->start_values_.is_on() && this->target_values_.is_on()) { this->start_values_ = LightColorValues(this->target_values_); this->start_values_.set_brightness(0.0f); } + // When turning light off from on state, use source state and only decrease brightness to zero. + if (this->start_values_.is_on() && !this->target_values_.is_on()) { + this->target_values_ = LightColorValues(this->start_values_); + this->target_values_.set_brightness(0.0f); + } + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { this->changing_color_mode_ = true; From e3ffecefc098aba2499ce3f86c132486f855dc9b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:31 +0200 Subject: [PATCH 3/7] Cease using deprecated Cover methods in automations (#2326) --- esphome/components/cover/automation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0092f987f2..0b364e1e09 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->open(); } + void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); } protected: Cover *cover_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->close(); } + void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); } protected: Cover *cover_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->stop(); } + void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); } protected: Cover *cover_; From d180aee57f8f93dc50cd38ed50dd85bba68246c0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:46:26 +0200 Subject: [PATCH 4/7] Apply color brightness to addressable light effects (#2321) --- esphome/components/light/addressable_light.cpp | 6 +++--- esphome/components/light/addressable_light.h | 3 +++ esphome/components/light/addressable_light_effect.h | 7 ++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 599d43d8b1..f3e6c0ef1d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -27,7 +27,7 @@ std::unique_ptr AddressableLight::create_default_transition() return make_unique(*this); } -Color esp_color_from_light_color_values(LightColorValues val) { +Color color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue()); @@ -44,7 +44,7 @@ void AddressableLight::update_state(LightState *state) { return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView - this->all() = esp_color_from_light_color_values(val); + this->all() = color_from_light_color_values(val); this->schedule_show(); } @@ -54,7 +54,7 @@ void AddressableLightTransformer::start() { return; auto end_values = this->target_values_; - this->target_color_ = esp_color_from_light_color_values(end_values); + this->target_color_ = color_from_light_color_values(end_values); // our transition will handle brightness, disable brightness in correction. this->light_.correction_.set_local_brightness(255); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index bba2158457..97f4a4687d 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -19,6 +19,9 @@ namespace light { using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color; +/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). +Color color_from_light_color_values(LightColorValues val); + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 1cb29dfa4e..358fe69c23 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -38,11 +38,8 @@ class AddressableLightEffect : public LightEffect { void stop() override { this->get_addressable_()->set_effect_active(false); } virtual void apply(AddressableLight &it, const Color ¤t_color) = 0; void apply() override { - LightColorValues color = this->state_->remote_values; - // not using any color correction etc. that will be handled by the addressable layer - Color current_color = - Color(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); + // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection + Color current_color = color_from_light_color_values(this->state_->remote_values); this->apply(*this->get_addressable_(), current_color); } From 7c17e72db42ae6016bf315a7b09368395b38736d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 00:33:10 +0200 Subject: [PATCH 5/7] Add readv and writev for more efficient API packets (#2342) --- esphome/components/api/api_connection.cpp | 29 ++-- esphome/components/api/api_frame_helper.cpp | 127 ++++++++++-------- esphome/components/api/api_frame_helper.h | 5 +- .../components/socket/bsd_sockets_impl.cpp | 48 +++++++ esphome/components/socket/headers.h | 6 + .../components/socket/lwip_raw_tcp_impl.cpp | 87 +++++++++--- esphome/components/socket/socket.h | 2 + 7 files changed, 220 insertions(+), 84 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 786fc28d68..8e6d1bc1c2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -702,15 +702,7 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 - bool success = this->send_buffer(buffer, 29); - if (!success) { - buffer = this->create_buffer(); - // bool send_failed = 4; - buffer.encode_bool(4, true); - return this->send_buffer(buffer, 29); - } else { - return true; - } + return this->send_buffer(buffer, 29); } HelloResponse APIConnection::hello(const HelloRequest &msg) { @@ -783,8 +775,23 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; - if (!this->helper_->can_write_without_blocking()) - return false; + if (!this->helper_->can_write_without_blocking()) { + delay(0); + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + return false; + } + if (!this->helper_->can_write_without_blocking()) { + // SubscribeLogsResponse + if (message_type != 29) { + ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); + } + delay(0); + return false; + } + } APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); if (err == APIError::WOULD_BLOCK) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index e68831e594..15014b7937 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -125,13 +125,6 @@ APIError APINoiseFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -494,12 +487,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload size_t total_len = 3 + mbuf.size; tmpbuf[1] = (uint8_t)(mbuf.size >> 8); tmpbuf[2] = (uint8_t) mbuf.size; + + struct iovec iov; + iov.iov_base = &tmpbuf[0]; + iov.iov_len = total_len; + // write raw to not have two packets sent if NAGLE disabled - aerr = write_raw_(&tmpbuf[0], total_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + return write_raw_(&iov, 1); } APIError APINoiseFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -526,16 +520,19 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -546,41 +543,56 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent return APIError::OK; } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - uint8_t header[3]; header[0] = 0x01; // indicator header[1] = (uint8_t)(len >> 8); header[2] = (uint8_t) len; - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; + struct iovec iov[2]; + iov[0].iov_base = header; + iov[0].iov_len = 3; + iov[1].iov_base = const_cast(data); + iov[1].iov_len = len; + + return write_raw_(iov, 2); } /** Initiate the data structures for the handshake. @@ -709,13 +721,6 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } state_ = State::DATA; return APIError::OK; @@ -863,15 +868,13 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay ProtoVarInt(payload_len).encode(header); ProtoVarInt(type).encode(header); - aerr = write_raw_(&header[0], header.size()); - if (aerr != APIError::OK) { - return aerr; - } - aerr = write_raw_(payload, payload_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + struct iovec iov[2]; + iov[0].iov_base = &header[0]; + iov[0].iov_len = header.size(); + iov[1].iov_base = const_cast(payload); + iov[1].iov_len = payload_len; + + return write_raw_(iov, 2); } APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -896,16 +899,19 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -916,23 +922,38 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a9a653cf4f..44df629b2f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -58,6 +58,7 @@ const char *api_error_to_str(APIError err); class APIFrameHelper { public: + virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; @@ -96,7 +97,7 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); APIError write_frame_(const uint8_t *data, size_t len); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const std::string &reason); @@ -154,7 +155,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); std::unique_ptr socket_; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index a0cdb6ec42..aca15f118e 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -5,6 +5,10 @@ #include +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + namespace esphome { namespace socket { @@ -75,7 +79,51 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have readv, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::readv(fd_, iov, iovcnt); +#endif + } ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have writev, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = + this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); + if (err == -1) { + if (ret != 0) + // if we already wrote some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::writev(fd_, iov, iovcnt); +#endif + } int setblocking(bool blocking) override { int fl = ::fcntl(fd_, F_GETFL, 0); if (blocking) { diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index da710b760e..fbe8f929a0 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -81,6 +81,11 @@ struct sockaddr_storage { }; typedef uint32_t socklen_t; +struct iovec { + void *iov_base; + size_t iov_len; +}; + #ifdef ARDUINO_ARCH_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY @@ -104,6 +109,7 @@ typedef uint32_t socklen_t; #include #include #include +#include #include #include #include diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 39741ea7ec..3c225aeb82 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -371,7 +371,23 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t write(const void *buf, size_t len) override { + ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; + } + ssize_t internal_write(const void *buf, size_t len) { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -400,25 +416,60 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return -1; } - if (tcp_nagle_disabled(pcb_)) { - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err == ERR_ABRT) { - LWIP_LOG(" -> err ERR_ABRT"); - // sometimes lwip returns ERR_ABRT for no apparent reason - // the connection works fine afterwards, and back with ESPAsyncTCP we - // indirectly also ignored this error - // FIXME: figure out where this is returned and what it means in this context - return to_send; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - } return to_send; } + int internal_output() { + LWIP_LOG("tcp_output(%p)", pcb_); + err_t err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return 0; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return 0; + } + ssize_t write(const void *buf, size_t len) override { + ssize_t written = internal_write(buf, len); + if (written == -1) + return -1; + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } + ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t written = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (written != 0) + // if we already read some don't return an error + break; + return err; + } + written += err; + if (err != iov[i].iov_len) + break; + } + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } int setblocking(bool blocking) override { if (pcb_ == nullptr) { errno = ECONNRESET; diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 7a5ce79161..9920610bf5 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,7 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; + virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; virtual int setblocking(bool blocking) = 0; virtual int loop() { return 0; }; }; From 954b8a0cfff66d54e6a24d16aab5849bfbd6e627 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:16:57 +1200 Subject: [PATCH 6/7] Bump version to 2021.9.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5a5351f1b0..fb7250578c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0" +__version__ = "2021.9.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 8bda8e5393bcb339120214dc17e8e50fa21f6db7 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:58:49 +0200 Subject: [PATCH 7/7] Clean-up sensor integration (#2275) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 15 +-- esphome/components/sensor/filter.cpp | 28 ----- esphome/components/sensor/filter.h | 19 --- esphome/components/sensor/sensor.cpp | 106 ++++++++--------- esphome/components/sensor/sensor.h | 138 ++++++++-------------- 7 files changed, 101 insertions(+), 209 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8e6d1bc1c2..d64cff96c2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -422,7 +422,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); - msg.state_class = static_cast(sensor->state_class); + msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 344aaf26f8..9a35674124 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,7 +11,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index d440e30fc4..e921056167 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -31,16 +31,9 @@ void MQTTSensorComponent::dump_config() { std::string MQTTSensorComponent::component_type() const { return "sensor"; } uint32_t MQTTSensorComponent::get_expire_after() const { - if (this->expire_after_.has_value()) { + if (this->expire_after_.has_value()) return *this->expire_after_; - } else { -#ifdef USE_DEEP_SLEEP - if (deep_sleep::global_has_deep_sleep) { - return 0; - } -#endif - return this->sensor_->calculate_expected_filter_update_interval() * 5; - } + return 0; } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } @@ -61,8 +54,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->state_class); + if (this->sensor_->get_state_class() != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index bbe47b43ec..f048189959 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -8,7 +8,6 @@ namespace sensor { static const char *const TAG = "sensor.filter"; // Filter -uint32_t Filter::expected_interval(uint32_t input) { return input; } void Filter::input(float value) { ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value); optional out = this->new_value(value); @@ -29,15 +28,6 @@ void Filter::initialize(Sensor *parent, Filter *next) { this->parent_ = parent; this->next_ = next; } -uint32_t Filter::calculate_remaining_interval(uint32_t input) { - uint32_t this_interval = this->expected_interval(input); - ESP_LOGVV(TAG, "Filter(%p)::calculate_remaining_interval(%u) -> %u", this, input, this_interval); - if (this->next_ == nullptr) { - return this_interval; - } else { - return this->next_->calculate_remaining_interval(this_interval); - } -} // MedianFilter MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -75,8 +65,6 @@ optional MedianFilter::new_value(float value) { return {}; } -uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MinFilter MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -106,8 +94,6 @@ optional MinFilter::new_value(float value) { return {}; } -uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MaxFilter MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -137,8 +123,6 @@ optional MaxFilter::new_value(float value) { return {}; } -uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // SlidingWindowMovingAverageFilter SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -177,8 +161,6 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { return {}; } -uint32_t SlidingWindowMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // ExponentialMovingAverageFilter ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} @@ -203,7 +185,6 @@ optional ExponentialMovingAverageFilter::new_value(float value) { } void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; } -uint32_t ExponentialMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} @@ -296,14 +277,6 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { this->phi_.initialize(parent, nullptr); } -uint32_t OrFilter::expected_interval(uint32_t input) { - uint32_t min_interval = UINT32_MAX; - for (Filter *filter : this->filters_) { - min_interval = std::min(min_interval, filter->calculate_remaining_interval(input)); - } - - return min_interval; -} // DebounceFilter optional DebounceFilter::new_value(float value) { this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); }); @@ -324,7 +297,6 @@ optional HeartbeatFilter::new_value(float value) { return {}; } -uint32_t HeartbeatFilter::expected_interval(uint32_t input) { return this->time_period_; } void HeartbeatFilter::setup() { this->set_interval("heartbeat", this->time_period_, [this]() { ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_), diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 270a91ccff..29a6813ea9 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -33,11 +33,6 @@ class Filter { void input(float value); - /// Return the amount of time that this filter is expected to take based on the input time interval. - virtual uint32_t expected_interval(uint32_t input); - - uint32_t calculate_remaining_interval(uint32_t input); - void output(float value); protected: @@ -68,8 +63,6 @@ class MedianFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -98,8 +91,6 @@ class MinFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -128,8 +119,6 @@ class MaxFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -159,8 +148,6 @@ class SlidingWindowMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: float sum_{0.0}; std::deque queue_; @@ -183,8 +170,6 @@ class ExponentialMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_alpha(float alpha); - uint32_t expected_interval(uint32_t input) override; - protected: bool first_value_{true}; float accumulator_{0.0f}; @@ -279,8 +264,6 @@ class HeartbeatFilter : public Filter, public Component { optional new_value(float value) override; - uint32_t expected_interval(uint32_t input) override; - float get_setup_priority() const override; protected: @@ -306,8 +289,6 @@ class OrFilter : public Filter { void initialize(Sensor *parent, Filter *next) override; - uint32_t expected_interval(uint32_t input) override; - optional new_value(float value) override; protected: diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index db81edcc61..0dc0275715 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,6 +18,51 @@ std::string state_class_to_string(StateClass state_class) { } } +Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor() : Sensor("") {} + +std::string Sensor::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return this->unit_of_measurement(); +} +void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} +std::string Sensor::unit_of_measurement() { return ""; } + +std::string Sensor::get_icon() { + if (this->icon_.has_value()) + return *this->icon_; + return this->icon(); +} +void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } +std::string Sensor::icon() { return ""; } + +int8_t Sensor::get_accuracy_decimals() { + if (this->accuracy_decimals_.has_value()) + return *this->accuracy_decimals_; + return this->accuracy_decimals(); +} +void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } +int8_t Sensor::accuracy_decimals() { return 0; } + +std::string Sensor::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return this->device_class(); +} +void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Sensor::device_class() { return ""; } + +void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } +StateClass Sensor::get_state_class() { + if (this->state_class_.has_value()) + return *this->state_class_; + return this->state_class(); +} +StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; } + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -30,54 +75,12 @@ void Sensor::publish_state(float state) { this->filter_list_->input(state); } } -std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::icon() { return ""; } -uint32_t Sensor::update_interval() { return 0; } -int8_t Sensor::accuracy_decimals() { return 0; } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} -Sensor::Sensor() : Sensor("") {} -void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { - this->unit_of_measurement_ = unit_of_measurement; -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { this->raw_callback_.add(std::move(callback)); } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return this->device_class(); -} -std::string Sensor::device_class() { return ""; } -void Sensor::set_state_class(StateClass state_class) { this->state_class = state_class; } -void Sensor::set_state_class(const std::string &state_class) { - if (str_equals_case_insensitive(state_class, "measurement")) { - this->state_class = STATE_CLASS_MEASUREMENT; - } else if (str_equals_case_insensitive(state_class, "total_increasing")) { - this->state_class = STATE_CLASS_TOTAL_INCREASING; - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); - } -} -std::string Sensor::get_unit_of_measurement() { - if (this->unit_of_measurement_.has_value()) - return *this->unit_of_measurement_; - return this->unit_of_measurement(); -} -int8_t Sensor::get_accuracy_decimals() { - if (this->accuracy_decimals_.has_value()) - return *this->accuracy_decimals_; - return this->accuracy_decimals(); -} + void Sensor::add_filter(Filter *filter) { // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of // filters @@ -119,24 +122,7 @@ void Sensor::internal_send_state_to_frontend(float state) { this->callback_.call(state); } bool Sensor::has_state() const { return this->has_state_; } -uint32_t Sensor::calculate_expected_filter_update_interval() { - uint32_t interval = this->update_interval(); - if (interval == 4294967295UL) - // update_interval: never - return 0; - - if (this->filter_list_ == nullptr) { - return interval; - } - - return this->filter_list_->calculate_remaining_interval(interval); -} uint32_t Sensor::hash_base() { return 2455723294UL; } -PollingSensorComponent::PollingSensorComponent(const std::string &name, uint32_t update_interval) - : PollingComponent(update_interval), Sensor(name) {} - -uint32_t PollingSensorComponent::update_interval() { return this->get_update_interval(); } - } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 209e83d205..85f56eb8b9 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -47,26 +47,42 @@ class Sensor : public Nameable { explicit Sensor(); explicit Sensor(const std::string &name); - /** Manually set the unit of measurement of this sensor. By default the sensor's default defined by - * unit_of_measurement() is used. - * - * @param unit_of_measurement The unit of measurement, "" to disable. - */ + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /** Manually set the icon of this sensor. By default the sensor's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ + /// Get the icon. Uses the manual override if specified or the default value instead. + std::string get_icon(); + /// Manually set the icon, for example "mdi:flash". void set_icon(const std::string &icon); - /** Manually set the accuracy in decimals for this sensor. By default, the sensor's default defined by - * accuracy_decimals() is used. - * - * @param accuracy_decimals The accuracy decimal that should be used. - */ + /// Get the accuracy in decimals, using the manual override if set. + int8_t get_accuracy_decimals(); + /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const std::string &device_class); + + /// Get the state class, using the manual override if set. + StateClass get_state_class(); + /// Manually set the state class. + void set_state_class(StateClass state_class); + + /** + * Get whether force update mode is enabled. + * + * 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. + */ + bool get_force_update() const { return force_update_; } + /// Set force update mode. + void set_force_update(bool force_update) { force_update_ = force_update; } + /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -93,15 +109,6 @@ class Sensor : public Nameable { /// Getter-syntax for .raw_state float get_raw_state() const; - /// Get the accuracy in decimals. Uses the manual override if specified or the default value instead. - int8_t get_accuracy_decimals(); - - /// Get the unit of measurement. Uses the manual override if specified or the default value instead. - std::string get_unit_of_measurement(); - - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /** Publish a new state to the front-end. * * First, the new state will be assigned to the raw_value. Then it's passed through all filters @@ -127,35 +134,15 @@ class Sensor : public Nameable { */ float state; - /// Manually set the Home Assistant device class (see sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this sensor, using the manual override if specified. - std::string get_device_class(); - - /** This member variable stores the current raw state of the sensor. Unlike .state, - * this will be updated immediately when publish_state is called. + /** This member variable stores the current raw state of the sensor, without any filters applied. + * + * Unlike .state,this will be updated immediately when publish_state is called. */ float raw_state; /// Return whether this sensor has gotten a full state (that passed through all filters) yet. bool has_state() const; - // The state class of this sensor state - StateClass state_class{STATE_CLASS_NONE}; - - /// Manually set the Home Assistant state class (see sensor::state_class) - void set_state_class(StateClass state_class); - void set_state_class(const std::string &state_class); - - /** Override this to set the Home Assistant device class for this sensor. - * - * Return "" to disable this feature. - * - * @return The device class of this sensor, for example "temperature". - */ - virtual std::string device_class(); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * @@ -163,65 +150,38 @@ class Sensor : public Nameable { */ virtual std::string unique_id(); - /// Return with which interval the sensor is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - /// Calculate the expected update interval for values that pass through all filters. - uint32_t calculate_expected_filter_update_interval(); - 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. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "°C". - */ + /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /** Override this to set the Home Assistant icon for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "mdi:battery". - */ + /// Override this to set the default icon. virtual std::string icon(); // NOLINT - /// Return the accuracy in decimals for this sensor. + /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT - optional device_class_{}; ///< Stores the override of the device class + /// Override this to set the default device class. + virtual std::string device_class(); // NOLINT + + /// Override this to set the default state class. + virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; CallbackManager raw_callback_; ///< Storage for raw state callbacks. CallbackManager callback_; ///< Storage for filtered state callbacks. - /// Override the unit of measurement - optional unit_of_measurement_; - /// Override the icon advertised to Home Assistant, otherwise sensor's icon will be used. - optional icon_; - /// Override the accuracy in decimals, otherwise the sensor's values will be used. - optional accuracy_decimals_; - Filter *filter_list_{nullptr}; ///< Store all active filters. + bool has_state_{false}; - bool force_update_{false}; -}; + Filter *filter_list_{nullptr}; ///< Store all active filters. -class PollingSensorComponent : public PollingComponent, public Sensor { - public: - explicit PollingSensorComponent(const std::string &name, uint32_t update_interval); - - uint32_t update_interval() override; + optional unit_of_measurement_; ///< Unit of measurement override + optional icon_; ///< Icon override + optional accuracy_decimals_; ///< Accuracy in decimals override + optional device_class_; ///< Device class override + optional state_class_{STATE_CLASS_NONE}; ///< State class override + bool force_update_{false}; ///< Force update mode }; } // namespace sensor