ESPHome  2024.7.0
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()) {
72  this->incoming_data_.clear();
73  return;
74  }
75  if (this->service_ == nullptr) {
76  // Setup the service
77  ESP_LOGD(TAG, "Creating Improv service");
78  global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
79  this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID));
80  this->setup_characteristics();
81  }
82 
83  if (!this->incoming_data_.empty())
84  this->process_incoming_data_();
85  uint32_t now = millis();
86 
87  switch (this->state_) {
89  this->set_status_indicator_state_(false);
90 
91  if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
92  if (this->service_->is_running()) {
94 
95  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
96  this->set_error_(improv::ERROR_NONE);
97  ESP_LOGD(TAG, "Service started!");
98  } else {
99  this->service_->start();
100  }
101  }
102  break;
103  case improv::STATE_AWAITING_AUTHORIZATION: {
104 #ifdef USE_BINARY_SENSOR
105  if (this->authorizer_ == nullptr ||
106  (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
107  this->set_state_(improv::STATE_AUTHORIZED);
108  } else
109 #else
110  this->set_state_(improv::STATE_AUTHORIZED);
111 #endif
112  {
113  if (!this->check_identify_())
114  this->set_status_indicator_state_(true);
115  }
116  break;
117  }
118  case improv::STATE_AUTHORIZED: {
119 #ifdef USE_BINARY_SENSOR
120  if (this->authorizer_ != nullptr) {
121  if (now - this->authorized_start_ > this->authorized_duration_) {
122  ESP_LOGD(TAG, "Authorization timeout");
123  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
124  return;
125  }
126  }
127 #endif
128  if (!this->check_identify_()) {
129  this->set_status_indicator_state_((now % 1000) < 500);
130  }
131  break;
132  }
133  case improv::STATE_PROVISIONING: {
134  this->set_status_indicator_state_((now % 200) < 100);
137  this->connecting_sta_.get_password());
138  this->connecting_sta_ = {};
139  this->cancel_timeout("wifi-connect-timeout");
140  this->set_state_(improv::STATE_PROVISIONED);
141 
142  std::vector<std::string> urls = {ESPHOME_MY_LINK};
143 #ifdef USE_WEBSERVER
144  for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
145  if (ip.is_ip4()) {
146  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
147  urls.push_back(webserver_url);
148  break;
149  }
150  }
151 #endif
152  std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
153  this->send_response_(data);
154  this->stop();
155  }
156  break;
157  }
158  case improv::STATE_PROVISIONED: {
159  this->incoming_data_.clear();
160  this->set_status_indicator_state_(false);
161  break;
162  }
163  }
164 }
165 
167 #ifdef USE_OUTPUT
168  if (this->status_indicator_ == nullptr)
169  return;
170  if (this->status_indicator_state_ == state)
171  return;
173  if (state) {
174  this->status_indicator_->turn_on();
175  } else {
176  this->status_indicator_->turn_off();
177  }
178 #endif
179 }
180 
182  uint32_t now = millis();
183 
184  bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
185 
186  if (identify) {
187  uint32_t time = now % 1000;
188  this->set_status_indicator_state_(time < 600 && time % 200 < 100);
189  }
190  return identify;
191 }
192 
194  ESP_LOGV(TAG, "Setting state: %d", state);
195  this->state_ = state;
196  if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
197  uint8_t data[1]{state};
198  this->status_->set_value(data, 1);
199  if (state != improv::STATE_STOPPED)
200  this->status_->notify();
201  }
202  std::vector<uint8_t> service_data(8, 0);
203  service_data[0] = 0x77; // PR
204  service_data[1] = 0x46; // IM
205  service_data[2] = static_cast<uint8_t>(state);
206 
207  uint8_t capabilities = 0x00;
208 #ifdef USE_OUTPUT
209  if (this->status_indicator_ != nullptr)
210  capabilities |= improv::CAPABILITY_IDENTIFY;
211 #endif
212 
213  service_data[3] = capabilities;
214  service_data[4] = 0x00; // Reserved
215  service_data[5] = 0x00; // Reserved
216  service_data[6] = 0x00; // Reserved
217  service_data[7] = 0x00; // Reserved
218 
220 }
221 
223  if (error != improv::ERROR_NONE) {
224  ESP_LOGE(TAG, "Error: %d", error);
225  }
226  if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
227  uint8_t data[1]{error};
228  this->error_->set_value(data, 1);
229  if (this->state_ != improv::STATE_STOPPED)
230  this->error_->notify();
231  }
232 }
233 
234 void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
235  this->rpc_response_->set_value(response);
236  if (this->state_ != improv::STATE_STOPPED)
237  this->rpc_response_->notify();
238 }
239 
241  if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
242  return;
243 
244  ESP_LOGD(TAG, "Setting Improv to start");
245  this->should_start_ = true;
246 }
247 
249  this->should_start_ = false;
250  this->set_timeout("end-service", 1000, [this] {
251  if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
252  return;
253  this->service_->stop();
255  });
256 }
257 
259 
261  ESP_LOGCONFIG(TAG, "ESP32 Improv:");
262 #ifdef USE_BINARY_SENSOR
263  LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
264 #endif
265 #ifdef USE_OUTPUT
266  ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
267 #endif
268 }
269 
271  uint8_t length = this->incoming_data_[1];
272 
273  ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
274  if (this->incoming_data_.size() - 3 == length) {
275  this->set_error_(improv::ERROR_NONE);
276  improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
277  switch (command.command) {
278  case improv::BAD_CHECKSUM:
279  ESP_LOGW(TAG, "Error decoding Improv payload");
280  this->set_error_(improv::ERROR_INVALID_RPC);
281  this->incoming_data_.clear();
282  break;
283  case improv::WIFI_SETTINGS: {
284  if (this->state_ != improv::STATE_AUTHORIZED) {
285  ESP_LOGW(TAG, "Settings received, but not authorized");
286  this->set_error_(improv::ERROR_NOT_AUTHORIZED);
287  this->incoming_data_.clear();
288  return;
289  }
290  wifi::WiFiAP sta{};
291  sta.set_ssid(command.ssid);
292  sta.set_password(command.password);
293  this->connecting_sta_ = sta;
294 
297  this->set_state_(improv::STATE_PROVISIONING);
298  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
299  command.password.c_str());
300 
301  auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
302  this->set_timeout("wifi-connect-timeout", 30000, f);
303  this->incoming_data_.clear();
304  break;
305  }
306  case improv::IDENTIFY:
307  this->incoming_data_.clear();
308  this->identify_start_ = millis();
309  break;
310  default:
311  ESP_LOGW(TAG, "Unknown Improv payload");
312  this->set_error_(improv::ERROR_UNKNOWN_RPC);
313  this->incoming_data_.clear();
314  }
315  } else if (this->incoming_data_.size() - 2 > length) {
316  ESP_LOGV(TAG, "Too much data came in, or malformed resetting buffer...");
317  this->incoming_data_.clear();
318  } else {
319  ESP_LOGV(TAG, "Waiting for split data packets...");
320  }
321 }
322 
324  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
325  this->set_state_(improv::STATE_AUTHORIZED);
326 #ifdef USE_BINARY_SENSOR
327  if (this->authorizer_ != nullptr)
328  this->authorized_start_ = millis();
329 #endif
330  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
332 }
333 
334 void ESP32ImprovComponent::on_client_disconnect() { this->set_error_(improv::ERROR_NONE); };
335 
336 ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
337 
338 } // namespace esp32_improv
339 } // namespace esphome
340 
341 #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:361
ESP32BLE * global_ble
Definition: ble.cpp:403
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)
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:82
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