ESPHome  2022.12.8
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  this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true);
20  this->setup_characteristics();
21 }
22 
24  this->status_ = this->service_->create_characteristic(
26  BLEDescriptor *status_descriptor = new BLE2902();
27  this->status_->add_descriptor(status_descriptor);
28 
29  this->error_ = this->service_->create_characteristic(
31  BLEDescriptor *error_descriptor = new BLE2902();
32  this->error_->add_descriptor(error_descriptor);
33 
34  this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE);
35  this->rpc_->on_write([this](const std::vector<uint8_t> &data) {
36  if (!data.empty()) {
37  this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end());
38  }
39  });
40  BLEDescriptor *rpc_descriptor = new BLE2902();
41  this->rpc_->add_descriptor(rpc_descriptor);
42 
43  this->rpc_response_ = this->service_->create_characteristic(
45  BLEDescriptor *rpc_response_descriptor = new BLE2902();
46  this->rpc_response_->add_descriptor(rpc_response_descriptor);
47 
48  this->capabilities_ =
49  this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ);
50  BLEDescriptor *capabilities_descriptor = new BLE2902();
51  this->capabilities_->add_descriptor(capabilities_descriptor);
52  uint8_t capabilities = 0x00;
53  if (this->status_indicator_ != nullptr)
54  capabilities |= improv::CAPABILITY_IDENTIFY;
55  this->capabilities_->set_value(capabilities);
56  this->setup_complete_ = true;
57 }
58 
60  if (!this->incoming_data_.empty())
61  this->process_incoming_data_();
62  uint32_t now = millis();
63 
64  switch (this->state_) {
65  case improv::STATE_STOPPED:
66  if (this->status_indicator_ != nullptr)
67  this->status_indicator_->turn_off();
68 
69  if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
70  if (this->service_->is_running()) {
72 
73  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
74  this->set_error_(improv::ERROR_NONE);
75  this->should_start_ = false;
76  ESP_LOGD(TAG, "Service started!");
77  } else {
78  this->service_->start();
79  }
80  }
81  break;
82  case improv::STATE_AWAITING_AUTHORIZATION: {
83  if (this->authorizer_ == nullptr || this->authorizer_->state) {
84  this->set_state_(improv::STATE_AUTHORIZED);
85  this->authorized_start_ = now;
86  } else {
87  if (this->status_indicator_ != nullptr) {
88  if (!this->check_identify_())
89  this->status_indicator_->turn_on();
90  }
91  }
92  break;
93  }
94  case improv::STATE_AUTHORIZED: {
95  if (this->authorizer_ != nullptr) {
96  if (now - this->authorized_start_ > this->authorized_duration_) {
97  ESP_LOGD(TAG, "Authorization timeout");
98  this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
99  return;
100  }
101  }
102  if (this->status_indicator_ != nullptr) {
103  if (!this->check_identify_()) {
104  if ((now % 1000) < 500) {
105  this->status_indicator_->turn_on();
106  } else {
107  this->status_indicator_->turn_off();
108  }
109  }
110  }
111  break;
112  }
113  case improv::STATE_PROVISIONING: {
114  if (this->status_indicator_ != nullptr) {
115  if ((now % 200) < 100) {
116  this->status_indicator_->turn_on();
117  } else {
118  this->status_indicator_->turn_off();
119  }
120  }
123  this->connecting_sta_.get_password());
124  this->connecting_sta_ = {};
125  this->cancel_timeout("wifi-connect-timeout");
126  this->set_state_(improv::STATE_PROVISIONED);
127 
128  std::vector<std::string> urls = {ESPHOME_MY_LINK};
129 #ifdef USE_WEBSERVER
131  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
132  urls.push_back(webserver_url);
133 #endif
134  std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
135  this->send_response_(data);
136  this->set_timeout("end-service", 1000, [this] {
137  this->service_->stop();
138  this->set_state_(improv::STATE_STOPPED);
139  });
140  }
141  break;
142  }
143  case improv::STATE_PROVISIONED: {
144  this->incoming_data_.clear();
145  if (this->status_indicator_ != nullptr)
146  this->status_indicator_->turn_off();
147  break;
148  }
149  }
150 }
151 
153  uint32_t now = millis();
154 
155  bool identify = this->identify_start_ != 0 && now - this->identify_start_ <= this->identify_duration_;
156 
157  if (identify) {
158  uint32_t time = now % 1000;
159  if (time < 600 && time % 200 < 100) {
160  this->status_indicator_->turn_on();
161  } else {
162  this->status_indicator_->turn_off();
163  }
164  }
165  return identify;
166 }
167 
169  ESP_LOGV(TAG, "Setting state: %d", state);
170  this->state_ = state;
171  if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
172  uint8_t data[1]{state};
173  this->status_->set_value(data, 1);
174  if (state != improv::STATE_STOPPED)
175  this->status_->notify();
176  }
177 }
178 
180  if (error != improv::ERROR_NONE) {
181  ESP_LOGE(TAG, "Error: %d", error);
182  }
183  if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
184  uint8_t data[1]{error};
185  this->error_->set_value(data, 1);
186  if (this->state_ != improv::STATE_STOPPED)
187  this->error_->notify();
188  }
189 }
190 
191 void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
192  this->rpc_response_->set_value(response);
193  if (this->state_ != improv::STATE_STOPPED)
194  this->rpc_response_->notify();
195 }
196 
198  if (this->state_ != improv::STATE_STOPPED)
199  return;
200 
201  ESP_LOGD(TAG, "Setting Improv to start");
202  this->should_start_ = true;
203 }
204 
206  this->set_timeout("end-service", 1000, [this] {
207  this->service_->stop();
208  this->set_state_(improv::STATE_STOPPED);
209  });
210 }
211 
213 
215  ESP_LOGCONFIG(TAG, "ESP32 Improv:");
216  LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
217  ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
218 }
219 
221  uint8_t length = this->incoming_data_[1];
222 
223  ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
224  if (this->incoming_data_.size() - 3 == length) {
225  this->set_error_(improv::ERROR_NONE);
226  improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
227  switch (command.command) {
228  case improv::BAD_CHECKSUM:
229  ESP_LOGW(TAG, "Error decoding Improv payload");
230  this->set_error_(improv::ERROR_INVALID_RPC);
231  this->incoming_data_.clear();
232  break;
233  case improv::WIFI_SETTINGS: {
234  if (this->state_ != improv::STATE_AUTHORIZED) {
235  ESP_LOGW(TAG, "Settings received, but not authorized");
236  this->set_error_(improv::ERROR_NOT_AUTHORIZED);
237  this->incoming_data_.clear();
238  return;
239  }
240  wifi::WiFiAP sta{};
241  sta.set_ssid(command.ssid);
242  sta.set_password(command.password);
243  this->connecting_sta_ = sta;
244 
247  this->set_state_(improv::STATE_PROVISIONING);
248  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
249  command.password.c_str());
250 
251  auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
252  this->set_timeout("wifi-connect-timeout", 30000, f);
253  this->incoming_data_.clear();
254  break;
255  }
256  case improv::IDENTIFY:
257  this->incoming_data_.clear();
258  this->identify_start_ = millis();
259  break;
260  default:
261  ESP_LOGW(TAG, "Unknown Improv payload");
262  this->set_error_(improv::ERROR_UNKNOWN_RPC);
263  this->incoming_data_.clear();
264  }
265  } else if (this->incoming_data_.size() - 2 > length) {
266  ESP_LOGV(TAG, "Too much data came in, or malformed resetting buffer...");
267  this->incoming_data_.clear();
268  } else {
269  ESP_LOGV(TAG, "Waiting for split data packets...");
270  }
271 }
272 
274  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
275  this->set_state_(improv::STATE_AUTHORIZED);
276  if (this->authorizer_ != nullptr)
277  this->authorized_start_ = millis();
278  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
280 }
281 
282 void ESP32ImprovComponent::on_client_disconnect() { this->set_error_(improv::ERROR_NONE); };
283 
284 ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
285 
286 } // namespace esp32_improv
287 } // namespace esphome
288 
289 #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:237
ESP32BLE * global_ble
Definition: ble.cpp:208
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition: component.cpp:72
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
std::string str() const
Definition: ip_address.h:28
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:68
void set_value(const uint8_t *data, size_t length)
std::shared_ptr< BLEService > create_service(const uint8_t *uuid, bool advertise=false)
Definition: ble_server.cpp:101
const float AFTER_BLUETOOTH
Definition: component.cpp:21
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:26
void send_response_(std::vector< uint8_t > &response)
void set_ssid(const std::string &ssid)
bool state
The current reported state of the binary sensor.
Definition: binary_sensor.h:52
WiFiComponent * global_wifi_component
void on_write(const std::function< void(const std::vector< uint8_t > &)> &&func)
BLEAdvertising * get_advertising()
Definition: ble.h:46
ESP32ImprovComponent * global_improv_component
std::string to_string(int value)
Definition: helpers.cpp:42
void add_descriptor(BLEDescriptor *descriptor)
void set_sta(const WiFiAP &ap)
Definition: a4988.cpp:4
const std::string & get_ssid() const
bool state
Definition: fan.h:34