ESPHome 2025.5.0
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::function<std::vector<uint8_t>(Ts...)> func) {
110 this->value_template_ = std::move(func);
111 has_simple_value_ = false;
112 }
113
114 void set_value_simple(const std::vector<uint8_t> &value) {
115 this->value_simple_ = value;
116 has_simple_value_ = true;
117 }
118
119 void play(Ts... x) override {}
120
121 void play_complex(Ts... x) override {
122 this->num_running_++;
123 this->var_ = std::make_tuple(x...);
124 auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...);
125 // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
126 if (!write(value))
127 this->play_next_(x...);
128 }
129
138 // initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
139 bool write(const std::vector<uint8_t> &value) {
140 if (this->node_state != espbt::ClientState::ESTABLISHED) {
141 esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
142 return false;
143 }
144 esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
145 esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
146 this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
147 this->write_type_, ESP_GATT_AUTH_REQ_NONE);
148 if (err != ESP_OK) {
149 esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
150 return false;
151 }
152 return true;
153 }
154
155 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
156 esp_ble_gattc_cb_param_t *param) override {
157 switch (event) {
158 case ESP_GATTC_WRITE_CHAR_EVT:
159 // upstream code checked the MAC address, verify the characteristic.
160 if (param->write.handle == this->char_handle_)
161 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
162 break;
163 case ESP_GATTC_DISCONNECT_EVT:
164 if (this->num_running_ != 0)
165 this->stop_complex();
166 break;
167 case ESP_GATTC_SEARCH_CMPL_EVT: {
168 auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
169 if (chr == nullptr) {
170 esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
171 this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
172 break;
173 }
174 this->char_handle_ = chr->handle;
175 this->char_props_ = chr->properties;
176 if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
177 this->write_type_ = ESP_GATT_WRITE_TYPE_RSP;
178 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
179 } else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
180 this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
181 esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
182 } else {
183 esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
184 break;
185 }
186 this->node_state = espbt::ClientState::ESTABLISHED;
187 esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
188 ble_client_->address_str().c_str());
189 break;
190 }
191 default:
192 break;
193 }
194 }
195
196 private:
197 BLEClient *ble_client_;
198 bool has_simple_value_ = true;
199 std::vector<uint8_t> value_simple_;
200 std::function<std::vector<uint8_t>(Ts...)> value_template_{};
201 espbt::ESPBTUUID service_uuid_;
202 espbt::ESPBTUUID char_uuid_;
203 std::tuple<Ts...> var_{};
204 uint16_t char_handle_{};
205 esp_gatt_char_prop_t char_props_{};
206 esp_gatt_write_type_t write_type_{};
207};
208
209template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
210 public:
211 BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
212
213 void play(Ts... x) override {
214 uint32_t passkey;
215 if (has_simple_value_) {
216 passkey = this->value_simple_;
217 } else {
218 passkey = this->value_template_(x...);
219 }
220 if (passkey > 999999)
221 return;
222 esp_bd_addr_t remote_bda;
223 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
224 esp_ble_passkey_reply(remote_bda, true, passkey);
225 }
226
227 void set_value_template(std::function<uint32_t(Ts...)> func) {
228 this->value_template_ = std::move(func);
229 has_simple_value_ = false;
230 }
231
232 void set_value_simple(const uint32_t &value) {
233 this->value_simple_ = value;
234 has_simple_value_ = true;
235 }
236
237 private:
238 BLEClient *parent_{nullptr};
239 bool has_simple_value_ = true;
240 uint32_t value_simple_{0};
241 std::function<uint32_t(Ts...)> value_template_{};
242};
243
244template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
245 public:
246 BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
247
248 void play(Ts... x) override {
249 esp_bd_addr_t remote_bda;
250 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
251 if (has_simple_value_) {
252 esp_ble_confirm_reply(remote_bda, this->value_simple_);
253 } else {
254 esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
255 }
256 }
257
258 void set_value_template(std::function<bool(Ts...)> func) {
259 this->value_template_ = std::move(func);
260 has_simple_value_ = false;
261 }
262
263 void set_value_simple(const bool &value) {
264 this->value_simple_ = value;
265 has_simple_value_ = true;
266 }
267
268 private:
269 BLEClient *parent_{nullptr};
270 bool has_simple_value_ = true;
271 bool value_simple_{false};
272 std::function<bool(Ts...)> value_template_{};
273};
274
275template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
276 public:
277 BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
278
279 void play(Ts... x) override {
280 esp_bd_addr_t remote_bda;
281 memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
282 esp_ble_remove_bond_device(remote_bda);
283 }
284
285 private:
286 BLEClient *parent_{nullptr};
287};
288
289template<typename... Ts> class BLEClientConnectAction : public Action<Ts...>, public BLEClientNode {
290 public:
292 ble_client->register_ble_node(this);
293 ble_client_ = ble_client;
294 }
295 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
296 esp_ble_gattc_cb_param_t *param) override {
297 if (this->num_running_ == 0)
298 return;
299 switch (event) {
300 case ESP_GATTC_SEARCH_CMPL_EVT:
301 this->node_state = espbt::ClientState::ESTABLISHED;
302 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
303 break;
304 // if the connection is closed, terminate the automation chain.
305 case ESP_GATTC_DISCONNECT_EVT:
306 this->stop_complex();
307 break;
308 default:
309 break;
310 }
311 }
312
313 // not used since we override play_complex_
314 void play(Ts... x) override {}
315
316 void play_complex(Ts... x) override {
317 // it makes no sense to have multiple instances of this running at the same time.
318 // this would occur only if the same automation was re-triggered while still
319 // running. So just cancel the second chain if this is detected.
320 if (this->num_running_ != 0) {
321 this->stop_complex();
322 return;
323 }
324 this->num_running_++;
325 if (this->node_state == espbt::ClientState::ESTABLISHED) {
326 this->play_next_(x...);
327 } else {
328 this->var_ = std::make_tuple(x...);
329 this->ble_client_->connect();
330 }
331 }
332
333 private:
334 BLEClient *ble_client_;
335 std::tuple<Ts...> var_{};
336};
337
338template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>, public BLEClientNode {
339 public:
341 ble_client->register_ble_node(this);
342 ble_client_ = ble_client;
343 }
344 void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
345 esp_ble_gattc_cb_param_t *param) override {
346 if (this->num_running_ == 0)
347 return;
348 switch (event) {
349 case ESP_GATTC_CLOSE_EVT:
350 case ESP_GATTC_DISCONNECT_EVT:
351 this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
352 break;
353 default:
354 break;
355 }
356 }
357
358 // not used since we override play_complex_
359 void play(Ts... x) override {}
360
361 void play_complex(Ts... x) override {
362 this->num_running_++;
363 if (this->node_state == espbt::ClientState::IDLE) {
364 this->play_next_(x...);
365 } else {
366 this->var_ = std::make_tuple(x...);
367 this->ble_client_->disconnect();
368 }
369 }
370
371 private:
372 BLEClient *ble_client_;
373 std::tuple<Ts...> var_{};
374};
375} // namespace ble_client
376} // namespace esphome
377
378#endif
void play_next_tuple_(const std::tuple< Ts... > &tuple, seq< S... >)
Definition automation.h:160
virtual void stop_complex()
Definition automation.h:129
void play_next_(Ts... x)
Definition automation.h:152
void trigger(Ts... x)
Definition automation.h:96
static const char *const TAG
Definition automation.h:19
BLEClientConnectAction(BLEClient *ble_client)
Definition automation.h:291
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:295
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 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:344
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 set_value_template(std::function< bool(Ts...)> func)
Definition automation.h:258
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_simple(const uint32_t &value)
Definition automation.h:232
void set_value_template(std::function< uint32_t(Ts...)> func)
Definition automation.h:227
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:155
void set_value_template(std::function< std::vector< uint8_t >(Ts...)> func)
Definition automation.h:109
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:139
void set_value_simple(const std::vector< uint8_t > &value)
Definition automation.h:114
std::string to_string() const
Definition ble_uuid.cpp:171
BLECharacteristic * get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr)
void run_later(std::function< void()> &&f)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition helpers.cpp:372
uint16_t x
Definition tt21100.cpp:5