ESPHome 2026.1.5
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// implement on_connect automation.
27 public:
29 void loop() override {}
30 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
31 esp_ble_gattc_cb_param_t *param) override {
32 if (event == ESP_GATTC_SEARCH_CMPL_EVT) {
33 this->node_state = espbt::ClientState::ESTABLISHED;
34 this->trigger();
35 }
36 }
37};
38
39// on_disconnect automation
41 public:
43 void loop() override {}
44 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
45 esp_ble_gattc_cb_param_t *param) override {
46 // test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred.
47 // So this will not trigger unless a complete open has previously succeeded.
48 switch (event) {
49 case ESP_GATTC_SEARCH_CMPL_EVT: {
50 this->node_state = espbt::ClientState::ESTABLISHED;
51 break;
52 }
53 case ESP_GATTC_CLOSE_EVT: {
54 this->trigger();
55 break;
56 }
57 default: {
58 break;
59 }
60 }
61 }
62};
63
65 public:
67 void loop() override {}
68 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
69 if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr))
70 this->trigger();
71 }
72};
73
75 public:
77 void loop() override {}
78 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
79 if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
80 this->trigger(param->ble_security.key_notif.passkey);
81 }
82 }
83};
84
86 public:
88 void loop() override {}
89 void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
90 if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
91 this->trigger(param->ble_security.key_notif.passkey);
92 }
93 }
94};
95
96// implement the ble_client.ble_write action.
97template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEClientNode {
98 public:
100 ble_client->register_ble_node(this);
101 ble_client_ = ble_client;
102 }
103
104 void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
105 void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
106 void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
107
108 void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
109 void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
110 void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
111
112 void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
113 this->value_.func = func;
114 this->len_ = -1; // Sentinel value indicates template mode
115 }
116
117 // Store pointer to static data in flash (no RAM copy)
118 void set_value_simple(const uint8_t *data, size_t len) {
119 this->value_.data = data;
120 this->len_ = len; // Length >= 0 indicates static mode
121 }
122
123 void play(const Ts &...x) override {}
124
125 void play_complex(const Ts &...x) override {
126 this->num_running_++;
127 this->var_ = std::make_tuple(x...);
128
129 bool result;
130 if (this->len_ >= 0) {
131 // Static mode: write directly from flash pointer
132 result = this->write(this->value_.data, this->len_);
133 } else {
134 // Template mode: call function and write the vector
135 std::vector<uint8_t> value = this->value_.func(x...);
136 result = this->write(value);
137 }
138
139 // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
140 if (!result)
141 this->play_next_(x...);
142 }
143
152 // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
153 bool write(const uint8_t *data, size_t len) {
154 if (this->node_state != espbt::ClientState::ESTABLISHED) {
155 esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
156 return false;
157 }
158#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
159 char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
160 esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
161#endif
162 esp_err_t err =
163 esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
164 const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
165 if (err != ESP_OK) {
166 esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
167 return false;
168 }
169 return true;
170 }
171
172 bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
173
174 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
175 esp_ble_gattc_cb_param_t *param) override {
176 switch (event) {
177 case ESP_GATTC_WRITE_CHAR_EVT:
178 // upstream code checked the MAC address, verify the characteristic.
179 if (param->write.handle == this->char_handle_)
180 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
181 break;
182 case ESP_GATTC_DISCONNECT_EVT:
183 if (this->num_running_ != 0)
184 this->stop_complex();
185 break;
186 case ESP_GATTC_SEARCH_CMPL_EVT: {
187 auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
188 if (chr == nullptr) {
189 char char_buf[esp32_ble::UUID_STR_LEN];
190 char service_buf[esp32_ble::UUID_STR_LEN];
191 esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
192 this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf));
193 break;
194 }
195 this->char_handle_ = chr->handle;
196 this->char_props_ = chr->properties;
197 if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
198 this->write_type_ = ESP_GATT_WRITE_TYPE_RSP;
199 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
200 } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
201 this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
202 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
203 } else {
204 char char_buf[esp32_ble::UUID_STR_LEN];
205 esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf));
206 break;
207 }
208 this->node_state = espbt::ClientState::ESTABLISHED;
209 char char_buf[esp32_ble::UUID_STR_LEN];
210 esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
211 ble_client_->address_str());
212 break;
213 }
214 default:
215 break;
216 }
217 }
218
219 private:
220 BLEClient *ble_client_;
221 ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
222 union Value {
223 std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
224 const uint8_t *data; // Pointer to static data in flash
225 } value_;
226 espbt::ESPBTUUID service_uuid_;
227 espbt::ESPBTUUID char_uuid_;
228 std::tuple<Ts...> var_{};
229 uint16_t char_handle_{};
230 esp_gatt_char_prop_t char_props_{};
231 esp_gatt_write_type_t write_type_{};
232};
233
234template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
235 public:
236 BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
237
238 void play(const Ts &...x) override {
239 uint32_t passkey;
240 if (has_simple_value_) {
241 passkey = this->value_.simple;
242 } else {
243 passkey = this->value_.template_func(x...);
244 }
245 if (passkey > 999999)
246 return;
247 esp_bd_addr_t remote_bda;
248 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
249 esp_ble_passkey_reply(remote_bda, true, passkey);
250 }
251
252 void set_value_template(uint32_t (*func)(Ts...)) {
253 this->value_.template_func = func;
254 this->has_simple_value_ = false;
255 }
256
257 void set_value_simple(const uint32_t &value) {
258 this->value_.simple = value;
259 this->has_simple_value_ = true;
260 }
261
262 private:
263 BLEClient *parent_{nullptr};
264 bool has_simple_value_ = true;
265 union {
266 uint32_t simple;
267 uint32_t (*template_func)(Ts...);
268 } value_{.simple = 0};
269};
270
271template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
272 public:
273 BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
274
275 void play(const Ts &...x) override {
276 esp_bd_addr_t remote_bda;
277 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
278 if (has_simple_value_) {
279 esp_ble_confirm_reply(remote_bda, this->value_.simple);
280 } else {
281 esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
282 }
283 }
284
285 void set_value_template(bool (*func)(Ts...)) {
286 this->value_.template_func = func;
287 this->has_simple_value_ = false;
288 }
289
290 void set_value_simple(const bool &value) {
291 this->value_.simple = value;
292 this->has_simple_value_ = true;
293 }
294
295 private:
296 BLEClient *parent_{nullptr};
297 bool has_simple_value_ = true;
298 union {
299 bool simple;
300 bool (*template_func)(Ts...);
301 } value_{.simple = false};
302};
303
304template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
305 public:
306 BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
307
308 void play(const Ts &...x) override {
309 esp_bd_addr_t remote_bda;
310 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
311 esp_ble_remove_bond_device(remote_bda);
312 }
313
314 private:
315 BLEClient *parent_{nullptr};
316};
317
318template<typename... Ts> class BLEClientConnectAction : public Action<Ts...>, public BLEClientNode {
319 public:
321 ble_client->register_ble_node(this);
322 ble_client_ = ble_client;
323 }
324 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
325 esp_ble_gattc_cb_param_t *param) override {
326 if (this->num_running_ == 0)
327 return;
328 switch (event) {
329 case ESP_GATTC_SEARCH_CMPL_EVT:
330 this->node_state = espbt::ClientState::ESTABLISHED;
331 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
332 break;
333 // if the connection is closed, terminate the automation chain.
334 case ESP_GATTC_DISCONNECT_EVT:
335 this->stop_complex();
336 break;
337 default:
338 break;
339 }
340 }
341
342 // not used since we override play_complex_
343 void play(const Ts &...x) override {}
344
345 void play_complex(const Ts &...x) override {
346 // it makes no sense to have multiple instances of this running at the same time.
347 // this would occur only if the same automation was re-triggered while still
348 // running. So just cancel the second chain if this is detected.
349 if (this->num_running_ != 0) {
350 this->stop_complex();
351 return;
352 }
353 this->num_running_++;
354 if (this->node_state == espbt::ClientState::ESTABLISHED) {
355 this->play_next_(x...);
356 } else {
357 this->var_ = std::make_tuple(x...);
358 this->ble_client_->connect();
359 }
360 }
361
362 private:
363 BLEClient *ble_client_;
364 std::tuple<Ts...> var_{};
365};
366
367template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>, public BLEClientNode {
368 public:
370 ble_client->register_ble_node(this);
371 ble_client_ = ble_client;
372 }
373 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
374 esp_ble_gattc_cb_param_t *param) override {
375 if (this->num_running_ == 0)
376 return;
377 switch (event) {
378 case ESP_GATTC_CLOSE_EVT:
379 case ESP_GATTC_DISCONNECT_EVT:
380 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
381 break;
382 default:
383 break;
384 }
385 }
386
387 // not used since we override play_complex_
388 void play(const Ts &...x) override {}
389
390 void play_complex(const Ts &...x) override {
391 this->num_running_++;
392 if (this->node_state == espbt::ClientState::IDLE) {
393 this->play_next_(x...);
394 } else {
395 this->var_ = std::make_tuple(x...);
396 this->ble_client_->disconnect();
397 }
398 }
399
400 private:
401 BLEClient *ble_client_;
402 std::tuple<Ts...> var_{};
403};
404} // namespace esphome::ble_client
405
406#endif
void play_next_(const Ts &...x)
Definition automation.h:295
virtual void stop_complex()
Definition automation.h:271
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:303
void trigger(const Ts &...x)
Definition automation.h:238
static const char *const TAG
Definition automation.h:22
void play_complex(const Ts &...x) override
Definition automation.h:345
BLEClientConnectAction(BLEClient *ble_client)
Definition automation.h:320
void play(const Ts &...x) override
Definition automation.h:343
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:324
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:30
void play_complex(const Ts &...x) override
Definition automation.h:390
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:373
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:44
void register_ble_node(BLEClientNode *node)
Definition ble_client.h:61
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:89
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:78
void set_value_template(uint32_t(*func)(Ts...))
Definition automation.h:252
void set_value_simple(const uint32_t &value)
Definition automation.h:257
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override
Definition automation.h:68
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:153
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:174
void set_value_template(std::vector< uint8_t >(*func)(Ts...))
Definition automation.h:112
void play(const Ts &...x) override
Definition automation.h:123
void play_complex(const Ts &...x) override
Definition automation.h:125
BLEClientWriteAction(BLEClient *ble_client)
Definition automation.h:99
bool write(const std::vector< uint8_t > &value)
Definition automation.h:172
void set_value_simple(const uint8_t *data, size_t len)
Definition automation.h:118
const char * to_str(std::span< char, UUID_STR_LEN > output) const
Definition ble_uuid.cpp:146
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
void run_later(std::function< void()> &&f)
__int64 ssize_t
Definition httplib.h:178
std::string size_t len
Definition helpers.h:595
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:334
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:830
uint16_t x
Definition tt21100.cpp:5