ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
automation.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef USE_ESP32
4
5#include <utility>
6#include <vector>
7
11#include "esphome/core/log.h"
12
13// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
14static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
15
16namespace esphome::ble_client {
17
18// placeholder class for static TAG .
20 public:
21 // could be made inline with C++17
22 static const char *const TAG;
23};
24
25// Base for nodes that never read the parent's services.
26// The parent releases its services only once every node reports Established, so a node that never
27// reports it keeps that memory allocated for the life of the connection.
29 public:
30 // Final so that Established is always reported on SEARCH_CMPL, before the derived node sees the event.
31 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) final {
32 if (event == ESP_GATTC_SEARCH_CMPL_EVT)
33 this->node_state = espbt::ClientState::ESTABLISHED;
34 this->on_gattc_event(event, gattc_if, param);
35 }
36
37 protected:
38 // Derived nodes handle GATT events here rather than by overriding the handler above.
39 virtual void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {}
40};
41
42// implement on_connect automation.
43class BLEClientConnectTrigger final : public Trigger<>, public BLEClientNode {
44 public:
46 void loop() override {}
47 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
48 esp_ble_gattc_cb_param_t *param) override {
49 if (event == ESP_GATTC_SEARCH_CMPL_EVT) {
50 this->node_state = espbt::ClientState::ESTABLISHED;
51 this->trigger();
52 }
53 }
54};
55
56// on_disconnect automation
57class BLEClientDisconnectTrigger final : public Trigger<>, public BLEClientNode {
58 public:
60 void loop() override {}
61 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
62 esp_ble_gattc_cb_param_t *param) override {
63 // test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred.
64 // So this will not trigger unless a complete open has previously succeeded.
65 switch (event) {
66 case ESP_GATTC_SEARCH_CMPL_EVT: {
67 this->node_state = espbt::ClientState::ESTABLISHED;
68 break;
69 }
70 case ESP_GATTC_CLOSE_EVT: {
71 this->trigger();
72 break;
73 }
74 default: {
75 break;
76 }
77 }
78 }
79};
80
82 public:
84 void loop() override {}
85 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
86 if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr))
87 this->trigger();
88 }
89};
90
92 public:
94 void loop() override {}
95 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
96 if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
97 this->trigger(param->ble_security.key_notif.passkey);
98 }
99 }
100};
101
103 public:
105 void loop() override {}
106 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
107 if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
108 this->trigger(param->ble_security.key_notif.passkey);
109 }
110 }
111};
112
113// implement the ble_client.ble_write action.
114template<typename... Ts> class BLEClientWriteAction final : public Action<Ts...>, public BLEClientNode {
115 public:
117 ble_client->register_ble_node(this);
118 ble_client_ = ble_client;
119 }
120
121 void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
122 void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
123 void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
124
125 void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
126 void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
127 void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
128
129 void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
130 this->value_.func = func;
131 this->len_ = -1; // Sentinel value indicates template mode
132 }
133
134 // Store pointer to static data in flash (no RAM copy)
135 void set_value_simple(const uint8_t *data, size_t len) {
136 this->value_.data = data;
137 this->len_ = len; // Length >= 0 indicates static mode
138 }
139
140 void play(const Ts &...x) override {}
141
142 void play_complex(const Ts &...x) override {
143 this->num_running_++;
144 this->var_ = std::make_tuple(x...);
145
146 bool result;
147 if (this->len_ >= 0) {
148 // Static mode: write directly from flash pointer
149 result = this->write(this->value_.data, this->len_);
150 } else {
151 // Template mode: call function and write the vector
152 std::vector<uint8_t> value = this->value_.func(x...);
153 result = this->write(value);
154 }
155
156 // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
157 if (!result)
158 this->play_next_(x...);
159 }
160
169 // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
170 bool write(const uint8_t *data, size_t len) {
171 if (this->node_state != espbt::ClientState::ESTABLISHED) {
172 esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
173 return false;
174 }
175#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
176 char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
177 esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
178#endif
179 esp_err_t err =
180 esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
181 const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
182 if (err != ESP_OK) {
183 esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
184 return false;
185 }
186 return true;
187 }
188
189 bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
190
191 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
192 esp_ble_gattc_cb_param_t *param) override {
193 switch (event) {
194 case ESP_GATTC_WRITE_CHAR_EVT:
195 // upstream code checked the MAC address, verify the characteristic.
196 if (param->write.handle == this->char_handle_)
197 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
198 break;
199 case ESP_GATTC_DISCONNECT_EVT:
200 if (this->num_running_ != 0)
201 this->stop_complex();
202 break;
203 case ESP_GATTC_SEARCH_CMPL_EVT: {
204 auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
205 if (chr == nullptr) {
206 char char_buf[esp32_ble::UUID_STR_LEN];
207 char service_buf[esp32_ble::UUID_STR_LEN];
208 esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
209 this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf));
210 break;
211 }
212 this->char_handle_ = chr->handle;
213 this->char_props_ = chr->properties;
214 if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
215 this->write_type_ = ESP_GATT_WRITE_TYPE_RSP;
216 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
217 } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
218 this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
219 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
220 } else {
221 char char_buf[esp32_ble::UUID_STR_LEN];
222 esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf));
223 break;
224 }
225 this->node_state = espbt::ClientState::ESTABLISHED;
226 char char_buf[esp32_ble::UUID_STR_LEN];
227 esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
228 ble_client_->address_str());
229 break;
230 }
231 default:
232 break;
233 }
234 }
235
236 private:
237 BLEClient *ble_client_;
238 ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
239 union Value {
240 std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
241 const uint8_t *data; // Pointer to static data in flash
242 } value_;
243 espbt::ESPBTUUID service_uuid_;
244 espbt::ESPBTUUID char_uuid_;
245 std::tuple<Ts...> var_{};
246 uint16_t char_handle_{};
247 esp_gatt_char_prop_t char_props_{};
248 esp_gatt_write_type_t write_type_{};
249};
250
251template<typename... Ts> class BLEClientPasskeyReplyAction final : public Action<Ts...> {
252 public:
253 BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
254
255 void play(const Ts &...x) override {
256 uint32_t passkey;
257 if (has_simple_value_) {
258 passkey = this->value_.simple;
259 } else {
260 passkey = this->value_.template_func(x...);
261 }
262 if (passkey > 999999)
263 return;
264 esp_bd_addr_t remote_bda;
265 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
266 esp_ble_passkey_reply(remote_bda, true, passkey);
267 }
268
269 void set_value_template(uint32_t (*func)(Ts...)) {
270 this->value_.template_func = func;
271 this->has_simple_value_ = false;
272 }
273
274 void set_value_simple(const uint32_t &value) {
275 this->value_.simple = value;
276 this->has_simple_value_ = true;
277 }
278
279 private:
280 BLEClient *parent_{nullptr};
281 bool has_simple_value_ = true;
282 union {
285 } value_{.simple = 0};
286};
287
288template<typename... Ts> class BLEClientNumericComparisonReplyAction final : public Action<Ts...> {
289 public:
290 BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
291
292 void play(const Ts &...x) override {
293 esp_bd_addr_t remote_bda;
294 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
295 if (has_simple_value_) {
296 esp_ble_confirm_reply(remote_bda, this->value_.simple);
297 } else {
298 esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
299 }
300 }
301
302 void set_value_template(bool (*func)(Ts...)) {
303 this->value_.template_func = func;
304 this->has_simple_value_ = false;
305 }
306
307 void set_value_simple(const bool &value) {
308 this->value_.simple = value;
309 this->has_simple_value_ = true;
310 }
311
312 private:
313 BLEClient *parent_{nullptr};
314 bool has_simple_value_ = true;
315 union {
316 bool simple;
317 bool (*template_func)(Ts...);
318 } value_{.simple = false};
319};
320
321template<typename... Ts> class BLEClientRemoveBondAction final : public Action<Ts...> {
322 public:
323 BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
324
325 void play(const Ts &...x) override {
326 esp_bd_addr_t remote_bda;
327 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
328 esp_ble_remove_bond_device(remote_bda);
329 }
330
331 private:
332 BLEClient *parent_{nullptr};
333};
334
335template<typename... Ts> class BLEClientConnectAction final : public Action<Ts...>, public BLEClientServicelessNode {
336 public:
338 ble_client->register_ble_node(this);
339 ble_client_ = ble_client;
340 }
341 void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override {
342 if (this->num_running_ == 0)
343 return;
344 switch (event) {
345 case ESP_GATTC_SEARCH_CMPL_EVT:
346 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
347 break;
348 // if the connection is closed, terminate the automation chain.
349 case ESP_GATTC_DISCONNECT_EVT:
350 this->stop_complex();
351 break;
352 default:
353 break;
354 }
355 }
356
357 // not used since we override play_complex_
358 void play(const Ts &...x) override {}
359
360 void play_complex(const Ts &...x) override {
361 // it makes no sense to have multiple instances of this running at the same time.
362 // this would occur only if the same automation was re-triggered while still
363 // running. So just cancel the second chain if this is detected.
364 if (this->num_running_ != 0) {
365 this->stop_complex();
366 return;
367 }
368 this->num_running_++;
369 if (this->node_state == espbt::ClientState::ESTABLISHED) {
370 this->play_next_(x...);
371 } else {
372 this->var_ = std::make_tuple(x...);
373 this->ble_client_->connect();
374 }
375 }
376
377 private:
378 BLEClient *ble_client_;
379 std::tuple<Ts...> var_{};
380};
381
382template<typename... Ts> class BLEClientDisconnectAction final : public Action<Ts...>, public BLEClientServicelessNode {
383 public:
385 ble_client->register_ble_node(this);
386 ble_client_ = ble_client;
387 }
388 void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override {
389 if (this->num_running_ == 0)
390 return;
391 switch (event) {
392 case ESP_GATTC_CLOSE_EVT:
393 case ESP_GATTC_DISCONNECT_EVT:
394 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
395 break;
396 default:
397 break;
398 }
399 }
400
401 // not used since we override play_complex_
402 void play(const Ts &...x) override {}
403
404 void play_complex(const Ts &...x) override {
405 this->num_running_++;
406 if (this->node_state == espbt::ClientState::IDLE) {
407 this->play_next_(x...);
408 } else {
409 this->var_ = std::make_tuple(x...);
410 this->ble_client_->disconnect();
411 }
412 }
413
414 private:
415 BLEClient *ble_client_;
416 std::tuple<Ts...> var_{};
417};
418} // namespace esphome::ble_client
419
420#endif
void play_next_(const Ts &...x)
Definition automation.h:518
virtual void stop_complex()
Definition automation.h:494
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:526
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:461
static const char *const TAG
Definition automation.h:22
void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
Definition automation.h:341
void play_complex(const Ts &...x) override
Definition automation.h:360
BLEClientConnectAction(BLEClient *ble_client)
Definition automation.h:337
void play(const Ts &...x) override
Definition automation.h:358
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 automation.h:47
void play_complex(const Ts &...x) override
Definition automation.h:404
void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override
Definition automation.h:388
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 automation.h:61
void register_ble_node(BLEClientNode *node)
Definition ble_client.h:66
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:106
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:95
void set_value_template(uint32_t(*func)(Ts...))
Definition automation.h:269
void set_value_simple(const uint32_t &value)
Definition automation.h:274
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:85
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) final
Definition automation.h:31
virtual void on_gattc_event(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
Definition automation.h:39
bool write(const uint8_t *data, size_t len)
Note about logging: the esph_log_X macros are used here because the CI checks complain about use of t...
Definition automation.h:170
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 automation.h:191
void set_value_template(std::vector< uint8_t >(*func)(Ts...))
Definition automation.h:129
void play(const Ts &...x) override
Definition automation.h:140
void play_complex(const Ts &...x) override
Definition automation.h:142
BLEClientWriteAction(BLEClient *ble_client)
Definition automation.h:116
bool write(const std::vector< uint8_t > &value)
Definition automation.h:189
void set_value_simple(const uint8_t *data, size_t len)
Definition automation.h:135
const char * to_str(char *buf) const
Write "0xABCD" / "0xABCDEF01" / the dashed 128-bit form, or "None" for an unset UUID,...
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
void run_later(std::function< void()> &&f)
__int64 ssize_t
Definition httplib.h:178
const void size_t len
Definition hal.h:64
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:425
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1438
static void uint32_t
uint16_t x
Definition tt21100.cpp:5