From fe7af21c91e5845fb52d243a6d30188c15a4ef7d Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 9 Aug 2021 06:05:36 +1000 Subject: [PATCH] Anova fahrenheit support (#2126) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 19 ++++++++++++++----- esphome/components/anova/anova.h | 2 ++ esphome/components/anova/anova_base.cpp | 20 +++++++++++++++++++- esphome/components/anova/anova_base.h | 1 + esphome/components/anova/climate.py | 15 +++++++++++++-- tests/test1.yaml | 1 + 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 63e0710936..f330969c33 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -90,19 +90,24 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ if (this->codec_->has_running()) { this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF; } + if (this->codec_->has_unit()) { + this->fahrenheit_ = (this->codec_->unit_ == 'f'); + ESP_LOGD(TAG, "Anova units is %s", this->fahrenheit_ ? "fahrenheit" : "celcius"); + this->current_request_++; + } this->publish_state(); - if (this->current_request_ > 0) { + if (this->current_request_ > 1) { AnovaPacket *pkt = nullptr; switch (this->current_request_++) { - case 1: + case 2: pkt = this->codec_->get_read_target_temp_request(); break; - case 2: + case 3: pkt = this->codec_->get_read_current_temp_request(); break; default: - this->current_request_ = 0; + this->current_request_ = 1; break; } if (pkt != nullptr) { @@ -121,12 +126,16 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } } +void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); } + void Anova::update() { if (this->node_state != espbt::ClientState::Established) return; - if (this->current_request_ == 0) { + if (this->current_request_ < 2) { auto pkt = this->codec_->get_read_device_status_request(); + if (this->current_request_ == 0) + auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 63d03cb329..42bdbcaed0 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -36,12 +36,14 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode traits.set_visual_temperature_step(0.1); return traits; } + void set_unit_of_measurement(const char *); protected: AnovaCodec *codec_; void control(const climate::ClimateCall &call) override; uint16_t char_handle_; uint8_t current_request_; + bool fahrenheit_; }; } // namespace anova diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index 8cbc481643..ad581b1e6c 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -3,6 +3,10 @@ namespace esphome { namespace anova { +float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); } + +float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; } + AnovaPacket *AnovaCodec::clean_packet_() { this->packet_.length = strlen((char *) this->packet_.data); this->packet_.data[this->packet_.length] = '\0'; @@ -42,6 +46,8 @@ AnovaPacket *AnovaCodec::get_read_data_request() { AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) { this->current_query_ = SET_TARGET_TEMPERATURE; + if (this->fahrenheit_) + temperature = ctof(temperature); sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature); return this->clean_packet_(); } @@ -67,7 +73,6 @@ AnovaPacket *AnovaCodec::get_stop_request() { void AnovaCodec::decode(const uint8_t *data, uint16_t length) { memset(this->buf_, 0, 32); strncpy(this->buf_, (char *) data, length); - ESP_LOGV("anova", "Received: %s\n", this->buf_); this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; switch (this->current_query_) { case READ_DEVICE_STATUS: { @@ -97,19 +102,32 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } case READ_TARGET_TEMPERATURE: { this->target_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { this->target_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { this->current_temp_ = strtof(this->buf_, nullptr); + if (this->fahrenheit_) + this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; break; } + case SET_UNIT: + case READ_UNIT: { + this->unit_ = this->buf_[0]; + this->fahrenheit_ = this->buf_[0] == 'f'; + this->has_unit_ = true; + break; + } default: break; } diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h index e94fe619a6..7c1383512d 100644 --- a/esphome/components/anova/anova_base.h +++ b/esphome/components/anova/anova_base.h @@ -71,6 +71,7 @@ class AnovaCodec { bool has_unit_; bool has_running_; char buf_[32]; + bool fahrenheit_; CurrentQuery current_query_; }; diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py index 763ae1f4be..bdd77d6a33 100644 --- a/esphome/components/anova/climate.py +++ b/esphome/components/anova/climate.py @@ -1,7 +1,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate, ble_client -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT + +UNITS = { + "f": "f", + "c": "c", +} CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] @@ -12,7 +17,12 @@ Anova = anova_ns.class_( ) CONFIG_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend({cv.GenerateID(): cv.declare_id(Anova)}) + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Anova), + cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS), + } + ) .extend(ble_client.BLE_CLIENT_SCHEMA) .extend(cv.polling_component_schema("60s")) ) @@ -23,3 +33,4 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) await ble_client.register_ble_node(var, config) + cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) diff --git a/tests/test1.yaml b/tests/test1.yaml index c1f3e378c4..6d6fc113ea 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1566,6 +1566,7 @@ climate: - platform: anova name: Anova cooker ble_client_id: ble_blah + unit_of_measurement: c midea_dongle: uart_id: uart0