ESPHome  2024.10.2
esp32_improv_component.cpp
Go to the documentation of this file.
2 
6 #include "esphome/core/log.h"
7 
8 #ifdef USE_ESP32
9 
10 namespace esphome {
11 namespace esp32_improv {
12 
13 static const char *const TAG = "esp32_improv.component";
14 static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
15 
17 
19 #ifdef USE_BINARY_SENSOR
20  if (this->authorizer_ != nullptr) {
21  this->authorizer_->add_on_state_callback([this](bool state) {
22  if (state) {
23  this->authorized_start_ = millis();
24  this->identify_start_ = 0;
25  }
26  });
27  }
28 #endif
29 }
30 
32  this->status_ = this->service_->create_characteristic(
34  BLEDescriptor *status_descriptor = new BLE2902();
35  this->status_->add_descriptor(status_descriptor);
36 
37  this->error_ = this->service_->create_characteristic(
39  BLEDescriptor *error_descriptor = new BLE2902();
40  this->error_->add_descriptor(error_descriptor);
41 
42  this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
43  this->rpc_->on_write([this](const std::vector<uint8_t> &data) {
44  if (!data.empty()) {
45  this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
46  }
47  });
48  BLEDescriptor *rpc_descriptor = new BLE2902();
49  this->rpc_->add_descriptor(rpc_descriptor);
50 
53  BLEDescriptor *rpc_response_descriptor = new BLE2902();
54  this->rpc_response_->add_descriptor(rpc_response_descriptor);
55 
56  this->capabilities_ =
57  this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ);
58  BLEDescriptor *capabilities_descriptor = new BLE2902();
59  this->capabilities_->add_descriptor(capabilities_descriptor);
60  uint8_t capabilities = 0x00;
61 #ifdef USE_OUTPUT
62  if (this->status_indicator_ != nullptr)
63  capabilities |= improv::CAPABILITY_IDENTIFY;
64 #endif
65  this->capabilities_->set_value(capabilities);
66  this->setup_complete_ = true;
67 }
68 
70  if (!global_ble_server->is_running()) {
71  if (this->state_ != improv::STATE_STOPPED) {
73 #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
74  this->state_callback_.call(this->state_, this->error_state_);
75 #endif
76  }
77  this->incoming_data_.clear();
78  return;
79  }
80  if (this->service_ == nullptr) {
81  // Setup the service
82  ESP_LOGD(TAG, "Creating Improv service");
83  global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
84  this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID));
85  this->setup_characteristics();
86  }
87 
88  if (!this->incoming_data_.empty())
89  this->process_incoming_data_();
90  uint32_t now = millis();
91 
92  switch (this->state_) {
94  this->set_status_indicator_state_(false);
95 
96  if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
97  if (this->service_->is_running()) {
99 
100  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
101  this->set_error_(improv::ERROR_NONE);
102  ESP_LOGD(TAG, "Service started!");
103  } else {
104  this->service_->start();
105  }
106  }
107  break;
108  case improv::STATE_AWAITING_AUTHORIZATION: {
109 #ifdef USE_BINARY_SENSOR
110  if (this->authorizer_ == nullptr ||
111  (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
112  this->set_state_(improv::STATE_AUTHORIZED);
113  } else
114 #else
115  this->set_state_(improv::STATE_AUTHORIZED);
116 #endif
117  {
118  if (!this->check_identify_())
119  this->set_status_indicator_state_(true);
120  }
121  break;
122  }
123  case improv::STATE_AUTHORIZED: {
124 #ifdef USE_BINARY_SENSOR
125  if (this->authorizer_ != nullptr) {
126  if (now - this->authorized_start_ > this->authorized_duration_) {
127  ESP_LOGD(TAG, "Authorization timeout");
128  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
129  return;
130  }
131  }
132 #endif
133  if (!this->check_identify_()) {
134  this->set_status_indicator_state_((now % 1000) < 500);
135  }
136  break;
137  }
138  case improv::STATE_PROVISIONING: {
139  this->set_status_indicator_state_((now % 200) < 100);
142  this->connecting_sta_.get_password());
143  this->connecting_sta_ = {};
144  this->cancel_timeout("wifi-connect-timeout");
145  this->set_state_(improv::STATE_PROVISIONED);
146 
147  std::vector<std::string> urls = {ESPHOME_MY_LINK};
148 #ifdef USE_WEBSERVER
149  for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
150  if (ip.is_ip4()) {
151  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
152  urls.push_back(webserver_url);
153  break;
154  }
155  }
156 #endif
157  std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
158  this->send_response_(data);
159  this->stop();
160  }
161  break;
162  }
163  case improv::STATE_PROVISIONED: {
164  this->incoming_data_.clear();
165  this->set_status_indicator_state_(false);
166  break;
167  }
168  }
169 }
170 
172 #ifdef USE_OUTPUT
173  if (this->status_indicator_ == nullptr)
174  return;
175  if (this->status_indicator_state_ == state)
176  return;
178  if (state) {
179  this->status_indicator_->turn_on();
180  } else {
181  this->status_indicator_->turn_off();
182  }
183 #endif
184 }
185 
187  uint32_t now = millis();
188 
189  bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
190 
191  if (identify) {
192  uint32_t time = now % 1000;
193  this->set_status_indicator_state_(time < 600 && time % 200 < 100);
194  }
195  return identify;
196 }
197 
199  ESP_LOGV(TAG, "Setting state: %d", state);
200  this->state_ = state;
201  if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
202  uint8_t data[1]{state};
203  this->status_->set_value(data, 1);
204  if (state != improv::STATE_STOPPED)
205  this->status_->notify();
206  }
207  std::vector<uint8_t> service_data(8, 0);
208  service_data[0] = 0x77; // PR
209  service_data[1] = 0x46; // IM
210  service_data[2] = static_cast<uint8_t>(state);
211 
212  uint8_t capabilities = 0x00;
213 #ifdef USE_OUTPUT
214  if (this->status_indicator_ != nullptr)
215  capabilities |= improv::CAPABILITY_IDENTIFY;
216 #endif
217 
218  service_data[3] = capabilities;
219  service_data[4] = 0x00; // Reserved
220  service_data[5] = 0x00; // Reserved
221  service_data[6] = 0x00; // Reserved
222  service_data[7] = 0x00; // Reserved
223 
225 #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
226  this->state_callback_.call(this->state_, this->error_state_);
227 #endif
228 }
229 
231  if (error != improv::ERROR_NONE) {
232  ESP_LOGE(TAG, "Error: %d", error);
233  }
234  if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
235  uint8_t data[1]{error};
236  this->error_->set_value(data, 1);
237  if (this->state_ != improv::STATE_STOPPED)
238  this->error_->notify();
239  }
240 }
241 
242 void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
243  this->rpc_response_->set_value(response);
244  if (this->state_ != improv::STATE_STOPPED)
245  this->rpc_response_->notify();
246 }
247 
249  if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
250  return;
251 
252  ESP_LOGD(TAG, "Setting Improv to start");
253  this->should_start_ = true;
254 }
255 
257  this->should_start_ = false;
258  this->set_timeout("end-service", 1000, [this] {
259  if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
260  return;
261  this->service_->stop();
263  });
264 }
265 
267 
269  ESP_LOGCONFIG(TAG, "ESP32 Improv:");
270 #ifdef USE_BINARY_SENSOR
271  LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
272 #endif
273 #ifdef USE_OUTPUT
274  ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
275 #endif
276 }
277 
279  uint8_t length = this->incoming_data_[1];
280 
281  ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
282  if (this->incoming_data_.size() - 3 == length) {
283  this->set_error_(improv::ERROR_NONE);
284  improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
285  switch (command.command) {
286  case improv::BAD_CHECKSUM:
287  ESP_LOGW(TAG, "Error decoding Improv payload");
288  this->set_error_(improv::ERROR_INVALID_RPC);
289  this->incoming_data_.clear();
290  break;
291  case improv::WIFI_SETTINGS: {
292  if (this->state_ != improv::STATE_AUTHORIZED) {
293  ESP_LOGW(TAG, "Settings received, but not authorized");
294  this->set_error_(improv::ERROR_NOT_AUTHORIZED);
295  this->incoming_data_.clear();
296  return;
297  }
298  wifi::WiFiAP sta{};
299  sta.set_ssid(command.ssid);
300  sta.set_password(command.password);
301  this->connecting_sta_ = sta;
302 
305  this->set_state_(improv::STATE_PROVISIONING);
306  ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
307  command.password.c_str());
308 
309  auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
310  this->set_timeout("wifi-connect-timeout", 30000, f);
311  this->incoming_data_.clear();
312  break;
313  }
314  case improv::IDENTIFY:
315  this->incoming_data_.clear();
316  this->identify_start_ = millis();
317  break;
318  default:
319  ESP_LOGW(TAG, "Unknown Improv payload");
320  this->set_error_(improv::ERROR_UNKNOWN_RPC);
321  this->incoming_data_.clear();
322  }
323  } else if (this->incoming_data_.size() - 2 > length) {
324  ESP_LOGV(TAG, "Too much data received or data malformed; resetting buffer...");
325  this->incoming_data_.clear();
326  } else {
327  ESP_LOGV(TAG, "Waiting for split data packets...");
328  }
329 }
330 
332  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
333  this->set_state_(improv::STATE_AUTHORIZED);
334 #ifdef USE_BINARY_SENSOR
335  if (this->authorizer_ != nullptr)
336  this->authorized_start_ = millis();
337 #endif
338  ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
340 }
341 
342 void ESP32ImprovComponent::on_client_disconnect() { this->set_error_(improv::ERROR_NONE); };
343 
344 ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
345 
346 } // namespace esp32_improv
347 } // namespace esphome
348 
349 #endif
virtual void turn_on()
Enable this binary output.
Definition: binary_output.h:43
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:359
ESP32BLE * global_ble
Definition: ble.cpp:411
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition: component.cpp:73
const std::string & get_password() const
void save_wifi_sta(const std::string &ssid, const std::string &password)
virtual void turn_off()
Disable this binary output.
Definition: binary_output.h:51
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:69
void create_service(ESPBTUUID uuid, bool advertise=false, uint16_t num_handles=15, uint8_t inst_id=0)
Definition: ble_server.cpp:118
void set_value(const uint8_t *data, size_t length)
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)
CallbackManager< void(improv::State, improv::Error)> state_callback_
void send_response_(std::vector< uint8_t > &response)
BLEService * get_service(ESPBTUUID uuid)
Definition: ble_server.cpp:143
void advertising_set_service_data(const std::vector< uint8_t > &data)
Definition: ble.cpp:69
void set_ssid(const std::string &ssid)
WiFiComponent * global_wifi_component
void on_write(const std::function< void(const std::vector< uint8_t > &)> &&func)
ESP32ImprovComponent * global_improv_component
std::string to_string(int value)
Definition: helpers.cpp:80
void add_descriptor(BLEDescriptor *descriptor)
void add_on_state_callback(std::function< void(bool)> &&callback)
Add a callback to be notified of state changes.
uint16_t length
Definition: tt21100.cpp:12
void set_sta(const WiFiAP &ap)
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
static ESPBTUUID from_raw(const uint8_t *data)
Definition: ble_uuid.cpp:28
const std::string & get_ssid() const
BLECharacteristic * create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties)
Definition: ble_service.cpp:34
bool state
Definition: fan.h:34