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