ESPHome 2025.5.0
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 break;
172 }
173 }
174}
175
177#ifdef USE_OUTPUT
178 if (this->status_indicator_ == nullptr)
179 return;
180 if (this->status_indicator_state_ == state)
181 return;
183 if (state) {
184 this->status_indicator_->turn_on();
185 } else {
187 }
188#endif
189}
190
192 uint32_t now = millis();
193
194 bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
195
196 if (identify) {
197 uint32_t time = now % 1000;
198 this->set_status_indicator_state_(time < 600 && time % 200 < 100);
199 }
200 return identify;
201}
202
204 ESP_LOGV(TAG, "Setting state: %d", state);
205 this->state_ = state;
206 if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
207 this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
208 if (state != improv::STATE_STOPPED)
209 this->status_->notify();
210 }
211 std::vector<uint8_t> service_data(8, 0);
212 service_data[0] = 0x77; // PR
213 service_data[1] = 0x46; // IM
214 service_data[2] = static_cast<uint8_t>(state);
215
216 uint8_t capabilities = 0x00;
217#ifdef USE_OUTPUT
218 if (this->status_indicator_ != nullptr)
219 capabilities |= improv::CAPABILITY_IDENTIFY;
220#endif
221
222 service_data[3] = capabilities;
223 service_data[4] = 0x00; // Reserved
224 service_data[5] = 0x00; // Reserved
225 service_data[6] = 0x00; // Reserved
226 service_data[7] = 0x00; // Reserved
227
229#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
230 this->state_callback_.call(this->state_, this->error_state_);
231#endif
232}
233
234void ESP32ImprovComponent::set_error_(improv::Error error) {
235 if (error != improv::ERROR_NONE) {
236 ESP_LOGE(TAG, "Error: %d", error);
237 }
238 if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
239 this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
240 if (this->state_ != improv::STATE_STOPPED)
241 this->error_->notify();
242 }
243}
244
245void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
246 this->rpc_response_->set_value(ByteBuffer::wrap(response));
247 if (this->state_ != improv::STATE_STOPPED)
248 this->rpc_response_->notify();
249}
250
252 if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
253 return;
254
255 ESP_LOGD(TAG, "Setting Improv to start");
256 this->should_start_ = true;
257}
258
260 this->should_start_ = false;
261 this->set_timeout("end-service", 1000, [this] {
262 if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
263 return;
264 this->service_->stop();
265 this->set_state_(improv::STATE_STOPPED);
266 });
267}
268
270
272 ESP_LOGCONFIG(TAG, "ESP32 Improv:");
273#ifdef USE_BINARY_SENSOR
274 LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
275#endif
276#ifdef USE_OUTPUT
277 ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
278#endif
279}
280
282 uint8_t length = this->incoming_data_[1];
283
284 ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
285 if (this->incoming_data_.size() - 3 == length) {
286 this->set_error_(improv::ERROR_NONE);
287 improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
288 switch (command.command) {
289 case improv::BAD_CHECKSUM:
290 ESP_LOGW(TAG, "Error decoding Improv payload");
291 this->set_error_(improv::ERROR_INVALID_RPC);
292 this->incoming_data_.clear();
293 break;
294 case improv::WIFI_SETTINGS: {
295 if (this->state_ != improv::STATE_AUTHORIZED) {
296 ESP_LOGW(TAG, "Settings received, but not authorized");
297 this->set_error_(improv::ERROR_NOT_AUTHORIZED);
298 this->incoming_data_.clear();
299 return;
300 }
301 wifi::WiFiAP sta{};
302 sta.set_ssid(command.ssid);
303 sta.set_password(command.password);
304 this->connecting_sta_ = sta;
305
308 this->set_state_(improv::STATE_PROVISIONING);
309 ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
310 command.password.c_str());
311
312 auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
313 this->set_timeout("wifi-connect-timeout", 30000, f);
314 this->incoming_data_.clear();
315 break;
316 }
317 case improv::IDENTIFY:
318 this->incoming_data_.clear();
319 this->identify_start_ = millis();
320 break;
321 default:
322 ESP_LOGW(TAG, "Unknown Improv payload");
323 this->set_error_(improv::ERROR_UNKNOWN_RPC);
324 this->incoming_data_.clear();
325 }
326 } else if (this->incoming_data_.size() - 2 > length) {
327 ESP_LOGV(TAG, "Too much data received or data malformed; resetting buffer...");
328 this->incoming_data_.clear();
329 } else {
330 ESP_LOGV(TAG, "Waiting for split data packets...");
331 }
332}
333
335 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
336 this->set_state_(improv::STATE_AUTHORIZED);
337#ifdef USE_BINARY_SENSOR
338 if (this->authorizer_ != nullptr)
339 this->authorized_start_ = millis();
340#endif
341 ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
343}
344
345ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
346
347} // namespace esp32_improv
348} // namespace esphome
349
350#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.
Definition component.cpp:76
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:72
void add_on_state_callback(std::function< void(bool)> &&callback)
Add a callback to be notified of state changes.
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:68
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:431
ESP32ImprovComponent * global_improv_component
const float AFTER_BLUETOOTH
Definition component.cpp:22
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string to_string(int value)
Definition helpers.cpp:82
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
Application App
Global storage of Application pointer - only one Application can exist.
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 length
Definition tt21100.cpp:0