ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
anova.cpp
Go to the documentation of this file.
1#include "anova.h"
2#include "esphome/core/log.h"
3
4#ifdef USE_ESP32
5
6namespace esphome::anova {
7
8ESPHOME_LOG_TAG(TAG, "anova");
9
10using namespace esphome::climate;
11
12void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
13
15 this->codec_ = make_unique<AnovaCodec>();
17}
18
20 // Parent BLEClientNode has a loop() method, but this component uses
21 // polling via update() and BLE callbacks so loop isn't needed
22 this->disable_loop();
23}
24
26 auto status =
27 esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
28 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
29 if (status) {
30 ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
31 }
32}
33
34void Anova::control(const ClimateCall &call) {
35 auto mode_val = call.get_mode();
36 if (mode_val.has_value()) {
37 ClimateMode mode = *mode_val;
38 AnovaPacket *pkt;
39 switch (mode) {
41 pkt = this->codec_->get_stop_request();
42 break;
44 pkt = this->codec_->get_start_request();
45 break;
46 default:
47 ESP_LOGW(TAG, "Unsupported mode: %d", mode);
48 return;
49 }
50 this->write_request_(pkt);
51 }
52 auto target_temp = call.get_target_temperature();
53 if (target_temp.has_value()) {
54 this->write_request_(this->codec_->get_set_target_temp_request(*target_temp));
55 }
56}
57
58void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
59 switch (event) {
60 case ESP_GATTC_DISCONNECT_EVT: {
61 this->current_temperature = NAN;
62 this->target_temperature = NAN;
64 this->publish_state();
65 break;
66 }
67 case ESP_GATTC_SEARCH_CMPL_EVT: {
68 auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
69 if (chr == nullptr) {
70 ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", LOG_STR_ARG(this->get_log_name()));
71 ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.",
72 LOG_STR_ARG(this->get_log_name()));
73 break;
74 }
75 this->char_handle_ = chr->handle;
76
77 auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
78 chr->handle);
79 if (status) {
80 ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", LOG_STR_ARG(this->get_log_name()),
81 status);
82 }
83 break;
84 }
85 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
86 this->node_state = espbt::ClientState::ESTABLISHED;
88 this->update(); // begin the first poll cycle immediately
89 break;
90 }
91 case ESP_GATTC_NOTIFY_EVT: {
92 if (param->notify.handle != this->char_handle_)
93 break;
94 this->codec_->decode(param->notify.value, param->notify.value_len);
95 if (this->codec_->has_target_temp()) {
96 this->target_temperature = this->codec_->target_temp_;
97 }
98 if (this->codec_->has_current_temp()) {
99 this->current_temperature = this->codec_->current_temp_;
100 }
101 if (this->codec_->has_running()) {
103 }
104 if (this->codec_->has_unit()) {
105 ESP_LOGD(TAG, "Anova units is %s", (this->codec_->unit_ == 'f') ? "fahrenheit" : "celsius");
106 }
107 this->publish_state();
108
109 // Advance the poll cycle to its next request based on the reply we got.
110 switch (this->poll_step_) {
113 this->write_request_(this->codec_->get_read_device_status_request());
114 break;
115 case PollStep::STATUS:
117 this->write_request_(this->codec_->get_read_target_temp_request());
118 break;
119 case PollStep::TARGET:
121 this->write_request_(this->codec_->get_read_current_temp_request());
122 break;
124 this->poll_step_ = PollStep::IDLE; // full cycle complete
125 break;
126 default:
127 // A reply to an ad-hoc control() write, outside a managed cycle.
128 break;
129 }
130 break;
131 }
132 default:
133 break;
134 }
135}
136
137void Anova::set_unit_of_measurement(const char *unit) { this->want_fahrenheit_ = !strncmp(unit, "f", 1); }
138
140 if (this->node_state != espbt::ClientState::ESTABLISHED)
141 return;
142 if (this->poll_step_ != PollStep::IDLE) {
143 // The previous cycle never finished within a full polling interval -- a
144 // reply was missed or a write failed. Restart the cycle rather than stall;
145 // the polling interval itself acts as the timeout. A late reply from the
146 // abandoned cycle is harmless: state decoding happens on every notify
147 // regardless of step, and each notify sends at most one follow-up request.
148 ESP_LOGW(TAG, "[%s] Poll cycle incomplete (step %u); restarting cycle", this->parent_->address_str(),
149 static_cast<uint8_t>(this->poll_step_));
150 }
151 // Re-assert the configured unit at the start of every poll cycle, then fall
152 // through the status/temperature reads via the notification handler. Always
153 // command the configured unit (want_fahrenheit_) -- never the last value the
154 // device reported, or a drift to 'c' would lock itself in.
156 this->write_request_(this->codec_->get_set_unit_request(this->want_fahrenheit_ ? 'f' : 'c'));
157}
158
159} // namespace esphome::anova
160
161#endif
uint8_t status
Definition bl0942.h:8
void disable_loop()
Disable this component's loop.
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
void dump_config() override
Definition anova.cpp:12
void update() override
Definition anova.cpp:139
void setup() override
Definition anova.cpp:14
void loop() override
Definition anova.cpp:19
PollStep poll_step_
Definition anova.h:53
std::unique_ptr< AnovaCodec > codec_
Definition anova.h:49
uint16_t char_handle_
Definition anova.h:51
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
Definition anova.cpp:58
void control(const climate::ClimateCall &call) override
Definition anova.cpp:34
void write_request_(AnovaPacket *pkt)
Definition anova.cpp:25
void set_unit_of_measurement(const char *unit)
Definition anova.cpp:137
This class is used to encode all control actions on a climate device.
Definition climate.h:31
ClimateMode mode
The active mode of the climate device.
Definition climate.h:317
float target_temperature
The target temperature of the climate device.
Definition climate.h:298
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:291
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
ESPHOME_LOG_TAG(TAG, "anova")
ClimateMode
Enum for all modes a climate device can be in.
@ CLIMATE_MODE_HEAT
The climate device is set to heat to reach the target temperature.
@ CLIMATE_MODE_OFF
The climate device is off.