ESPHome  2025.2.0
esp32_improv_component.cpp
Go to the documentation of this file.
2 
6 #include "esphome/core/log.h"
8 
9 #ifdef USE_ESP32
10 
11 namespace esphome {
12 namespace esp32_improv {
13 
14 using namespace bytebuffer;
15 
16 static const char *const TAG = "esp32_improv.component";
17 static 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
33  [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
34 }
35 
37  this->status_ = this->service_->create_characteristic(
39  BLEDescriptor *status_descriptor = new BLE2902();
40  this->status_->add_descriptor(status_descriptor);
41 
42  this->error_ = this->service_->create_characteristic(
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 
57  this->rpc_response_ = this->service_->create_characteristic(
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 
76  if (!global_ble_server->is_running()) {
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);
90  this->setup_characteristics();
91  }
92 
93  if (!this->incoming_data_.empty())
94  this->process_incoming_data_();
95  uint32_t now = millis();
96 
97  switch (this->state_) {
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);
146  wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
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;
182  this->status_indicator_state_ = state;
183  if (state) {
184  this->status_indicator_->turn_on();
185  } else {
186  this->status_indicator_->turn_off();
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 
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 
245 void 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 
345 ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
346 
347 } // namespace esp32_improv
348 } // namespace esphome
349 
350 #endif
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:373
ESP32BLE * global_ble
Definition: ble.cpp:438
void save_wifi_sta(const std::string &ssid, const std::string &password)
EventEmitterListenerID on(EvtType event, std::function< void(Args...)> listener)
Definition: event_emitter.h:19
const float AFTER_BLUETOOTH
Definition: component.cpp:22
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void start_connecting(const WiFiAP &ap, bool two)
void send_response_(std::vector< uint8_t > &response)
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition: ble.cpp:72
void set_ssid(const std::string &ssid)
WiFiComponent * global_wifi_component
ESP32ImprovComponent * global_improv_component
std::string to_string(int value)
Definition: helpers.cpp:83
uint16_t length
Definition: tt21100.cpp:12
void set_sta(const WiFiAP &ap)
BLEService * create_service(ESPBTUUID uuid, bool advertise=false, uint16_t num_handles=15)
Definition: ble_server.cpp:106
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
static ESPBTUUID from_raw(const uint8_t *data)
Definition: ble_uuid.cpp:28
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
Definition: bytebuffer.h:156
bool state
Definition: fan.h:34