ESPHome 2025.8.0b2
Loading...
Searching...
No Matches
esp32_improv_component.cpp
Go to the documentation of this file.
2
6#include "esphome/core/log.h"
8
9#ifdef USE_ESP32
10
11namespace esphome {
12namespace esp32_improv {
13
14using namespace bytebuffer;
15
16static const char *const TAG = "esp32_improv.component";
17static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
18
20
22#ifdef USE_BINARY_SENSOR
23 if (this->authorizer_ != nullptr) {
24 this->authorizer_->add_on_state_callback([this](bool state) {
25 if (state) {
26 this->authorized_start_ = millis();
27 this->identify_start_ = 0;
28 }
29 });
30 }
31#endif
32 global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
33 [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
34}
35
39 BLEDescriptor *status_descriptor = new BLE2902();
40 this->status_->add_descriptor(status_descriptor);
41
44 BLEDescriptor *error_descriptor = new BLE2902();
45 this->error_->add_descriptor(error_descriptor);
46
47 this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
48 this->rpc_->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on(
49 BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &data, uint16_t id) {
50 if (!data.empty()) {
51 this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
52 }
53 });
54 BLEDescriptor *rpc_descriptor = new BLE2902();
55 this->rpc_->add_descriptor(rpc_descriptor);
56
59 BLEDescriptor *rpc_response_descriptor = new BLE2902();
60 this->rpc_response_->add_descriptor(rpc_response_descriptor);
61
62 this->capabilities_ =
63 this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ);
64 BLEDescriptor *capabilities_descriptor = new BLE2902();
65 this->capabilities_->add_descriptor(capabilities_descriptor);
66 uint8_t capabilities = 0x00;
67#ifdef USE_OUTPUT
68 if (this->status_indicator_ != nullptr)
69 capabilities |= improv::CAPABILITY_IDENTIFY;
70#endif
71 this->capabilities_->set_value(ByteBuffer::wrap(capabilities));
72 this->setup_complete_ = true;
73}
74
77 if (this->state_ != improv::STATE_STOPPED) {
78 this->state_ = improv::STATE_STOPPED;
79#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
80 this->state_callback_.call(this->state_, this->error_state_);
81#endif
82 }
83 this->incoming_data_.clear();
84 return;
85 }
86 if (this->service_ == nullptr) {
87 // Setup the service
88 ESP_LOGD(TAG, "Creating Improv service");
89 this->service_ = global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
91 }
92
93 if (!this->incoming_data_.empty())
95 uint32_t now = App.get_loop_component_start_time();
96
97 switch (this->state_) {
98 case improv::STATE_STOPPED:
99 this->set_status_indicator_state_(false);
100
101 if (this->should_start_ && this->setup_complete_) {
102 if (this->service_->is_created()) {
103 this->service_->start();
104 } else if (this->service_->is_running()) {
106
107 this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
108 this->set_error_(improv::ERROR_NONE);
109 ESP_LOGD(TAG, "Service started!");
110 }
111 }
112 break;
113 case improv::STATE_AWAITING_AUTHORIZATION: {
114#ifdef USE_BINARY_SENSOR
115 if (this->authorizer_ == nullptr ||
116 (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
117 this->set_state_(improv::STATE_AUTHORIZED);
118 } else
119#else
120 { this->set_state_(improv::STATE_AUTHORIZED); }
121#endif
122 {
123 if (!this->check_identify_())
124 this->set_status_indicator_state_(true);
125 }
126 break;
127 }
128 case improv::STATE_AUTHORIZED: {
129#ifdef USE_BINARY_SENSOR
130 if (this->authorizer_ != nullptr) {
131 if (now - this->authorized_start_ > this->authorized_duration_) {
132 ESP_LOGD(TAG, "Authorization timeout");
133 this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
134 return;
135 }
136 }
137#endif
138 if (!this->check_identify_()) {
139 this->set_status_indicator_state_((now % 1000) < 500);
140 }
141 break;
142 }
143 case improv::STATE_PROVISIONING: {
144 this->set_status_indicator_state_((now % 200) < 100);
145 if (wifi::global_wifi_component->is_connected()) {
147 this->connecting_sta_.get_password());
148 this->connecting_sta_ = {};
149 this->cancel_timeout("wifi-connect-timeout");
150 this->set_state_(improv::STATE_PROVISIONED);
151
152 std::vector<std::string> urls = {ESPHOME_MY_LINK};
153#ifdef USE_WEBSERVER
154 for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
155 if (ip.is_ip4()) {
156 std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
157 urls.push_back(webserver_url);
158 break;
159 }
160 }
161#endif
162 std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
163 this->send_response_(data);
164 this->stop();
165 }
166 break;
167 }
168 case improv::STATE_PROVISIONED: {
169 this->incoming_data_.clear();
170 this->set_status_indicator_state_(false);
171 // Provisioning complete, no further loop execution needed
172 this->disable_loop();
173 break;
174 }
175 }
176}
177
179#ifdef USE_OUTPUT
180 if (this->status_indicator_ == nullptr)
181 return;
182 if (this->status_indicator_state_ == state)
183 return;
185 if (state) {
186 this->status_indicator_->turn_on();
187 } else {
189 }
190#endif
191}
192
194 uint32_t now = millis();
195
196 bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
197
198 if (identify) {
199 uint32_t time = now % 1000;
200 this->set_status_indicator_state_(time < 600 && time % 200 < 100);
201 }
202 return identify;
203}
204
206 ESP_LOGV(TAG, "Setting state: %d", state);
207 this->state_ = state;
208 if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
209 this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
210 if (state != improv::STATE_STOPPED)
211 this->status_->notify();
212 }
213 std::vector<uint8_t> service_data(8, 0);
214 service_data[0] = 0x77; // PR
215 service_data[1] = 0x46; // IM
216 service_data[2] = static_cast<uint8_t>(state);
217
218 uint8_t capabilities = 0x00;
219#ifdef USE_OUTPUT
220 if (this->status_indicator_ != nullptr)
221 capabilities |= improv::CAPABILITY_IDENTIFY;
222#endif
223
224 service_data[3] = capabilities;
225 service_data[4] = 0x00; // Reserved
226 service_data[5] = 0x00; // Reserved
227 service_data[6] = 0x00; // Reserved
228 service_data[7] = 0x00; // Reserved
229
231#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
232 this->state_callback_.call(this->state_, this->error_state_);
233#endif
234}
235
236void ESP32ImprovComponent::set_error_(improv::Error error) {
237 if (error != improv::ERROR_NONE) {
238 ESP_LOGE(TAG, "Error: %d", error);
239 }
240 if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
241 this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
242 if (this->state_ != improv::STATE_STOPPED)
243 this->error_->notify();
244 }
245}
246
247void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
248 this->rpc_response_->set_value(ByteBuffer::wrap(response));
249 if (this->state_ != improv::STATE_STOPPED)
250 this->rpc_response_->notify();
251}
252
254 if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
255 return;
256
257 ESP_LOGD(TAG, "Setting Improv to start");
258 this->should_start_ = true;
259 this->enable_loop();
260}
261
263 this->should_start_ = false;
264 this->set_timeout("end-service", 1000, [this] {
265 if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
266 return;
267 this->service_->stop();
268 this->set_state_(improv::STATE_STOPPED);
269 });
270}
271
273
275 ESP_LOGCONFIG(TAG, "ESP32 Improv:");
276#ifdef USE_BINARY_SENSOR
277 LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
278#endif
279#ifdef USE_OUTPUT
280 ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
281#endif
282}
283
285 uint8_t length = this->incoming_data_[1];
286
287 ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
288 if (this->incoming_data_.size() - 3 == length) {
289 this->set_error_(improv::ERROR_NONE);
290 improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
291 switch (command.command) {
292 case improv::BAD_CHECKSUM:
293 ESP_LOGW(TAG, "Error decoding Improv payload");
294 this->set_error_(improv::ERROR_INVALID_RPC);
295 this->incoming_data_.clear();
296 break;
297 case improv::WIFI_SETTINGS: {
298 if (this->state_ != improv::STATE_AUTHORIZED) {
299 ESP_LOGW(TAG, "Settings received, but not authorized");
300 this->set_error_(improv::ERROR_NOT_AUTHORIZED);
301 this->incoming_data_.clear();
302 return;
303 }
304 wifi::WiFiAP sta{};
305 sta.set_ssid(command.ssid);
306 sta.set_password(command.password);
307 this->connecting_sta_ = sta;
308
311 this->set_state_(improv::STATE_PROVISIONING);
312 ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
313 command.password.c_str());
314
315 auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
316 this->set_timeout("wifi-connect-timeout", 30000, f);
317 this->incoming_data_.clear();
318 break;
319 }
320 case improv::IDENTIFY:
321 this->incoming_data_.clear();
322 this->identify_start_ = millis();
323 break;
324 default:
325 ESP_LOGW(TAG, "Unknown Improv payload");
326 this->set_error_(improv::ERROR_UNKNOWN_RPC);
327 this->incoming_data_.clear();
328 }
329 } else if (this->incoming_data_.size() - 2 > length) {
330 ESP_LOGV(TAG, "Too much data received or data malformed; resetting buffer");
331 this->incoming_data_.clear();
332 } else {
333 ESP_LOGV(TAG, "Waiting for split data packets");
334 }
335}
336
338 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
339 this->set_state_(improv::STATE_AUTHORIZED);
340#ifdef USE_BINARY_SENSOR
341 if (this->authorizer_ != nullptr)
342 this->authorized_start_ = millis();
343#endif
344 ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
346}
347
348ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
349
350} // namespace esp32_improv
351} // namespace esphome
352
353#endif
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void add_on_state_callback(std::function< void(T)> &&callback)
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
Definition bytebuffer.h:156
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition ble.cpp:64
static ESPBTUUID from_raw(const uint8_t *data)
Definition ble_uuid.cpp:28
void add_descriptor(BLEDescriptor *descriptor)
BLEService * create_service(ESPBTUUID uuid, bool advertise=false, uint16_t num_handles=15)
BLECharacteristic * create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties)
void send_response_(std::vector< uint8_t > &response)
CallbackManager< void(improv::State, improv::Error)> state_callback_
EventEmitterListenerID on(EvtType event, std::function< void(Args...)> listener)
virtual void turn_off()
Disable this binary output.
virtual void turn_on()
Enable this binary output.
const std::string & get_ssid() const
void set_ssid(const std::string &ssid)
void set_sta(const WiFiAP &ap)
void save_wifi_sta(const std::string &ssid, const std::string &password)
void start_connecting(const WiFiAP &ap, bool two)
bool state
Definition fan.h:0
ESP32BLE * global_ble
Definition ble.cpp:544
ESP32ImprovComponent * global_improv_component
const float AFTER_BLUETOOTH
Definition component.cpp:53
WiFiComponent * global_wifi_component
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, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:280
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0