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