ESPHome  2024.2.0
improv_serial_component.cpp
Go to the documentation of this file.
2 
4 #include "esphome/core/defines.h"
5 #include "esphome/core/hal.h"
6 #include "esphome/core/log.h"
7 #include "esphome/core/version.h"
8 
10 
11 namespace esphome {
12 namespace improv_serial {
13 
14 static const char *const TAG = "improv_serial";
15 
18 #ifdef USE_ARDUINO
20 #endif
21 #ifdef USE_ESP_IDF
23 #endif
24 
25  if (wifi::global_wifi_component->has_sta()) {
26  this->state_ = improv::STATE_PROVISIONED;
27  } else {
29  }
30 }
31 
32 void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
33 
34 optional<uint8_t> ImprovSerialComponent::read_byte_() {
35  optional<uint8_t> byte;
36  uint8_t data = 0;
37 #ifdef USE_ARDUINO
38  if (this->hw_serial_->available()) {
39  this->hw_serial_->readBytes(&data, 1);
40  byte = data;
41  }
42 #endif
43 #ifdef USE_ESP_IDF
44  switch (logger::global_logger->get_uart()) {
47 #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
48  !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
50 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
51  if (this->uart_num_ >= 0) {
52  size_t available;
53  uart_get_buffered_data_len(this->uart_num_, &available);
54  if (available) {
55  uart_read_bytes(this->uart_num_, &data, 1, 0);
56  byte = data;
57  }
58  }
59  break;
60 #if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
62 #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
63  if (esp_usb_console_available_for_read()) {
64 #else
65  if (esp_usb_console_read_available()) {
66 #endif
67  esp_usb_console_read_buf((char *) &data, 1);
68  byte = data;
69  }
70  break;
71 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
72 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
74  if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
75  byte = data;
76  }
77  break;
78  }
79 #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3
80  default:
81  break;
82  }
83 #endif
84  return byte;
85 }
86 
87 void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
88  data.push_back('\n');
89 #ifdef USE_ARDUINO
90  this->hw_serial_->write(data.data(), data.size());
91 #endif
92 #ifdef USE_ESP_IDF
93  switch (logger::global_logger->get_uart()) {
96 #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
97  !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
99 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
100  uart_write_bytes(this->uart_num_, data.data(), data.size());
101  break;
102 #if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
104  const char *msg = (char *) data.data();
105  esp_usb_console_write_buf(msg, data.size());
106  break;
107  }
108 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
109 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
111  usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
112  break;
113 #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
114  default:
115  break;
116  }
117 #endif
118 }
119 
121  if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
122  this->last_read_byte_ = 0;
123  this->rx_buffer_.clear();
124  ESP_LOGV(TAG, "Improv Serial timeout");
125  }
126 
127  auto byte = this->read_byte_();
128  while (byte.has_value()) {
129  if (this->parse_improv_serial_byte_(byte.value())) {
130  this->last_read_byte_ = millis();
131  } else {
132  this->last_read_byte_ = 0;
133  this->rx_buffer_.clear();
134  }
135  byte = this->read_byte_();
136  }
137 
138  if (this->state_ == improv::STATE_PROVISIONING) {
141  this->connecting_sta_.get_password());
142  this->connecting_sta_ = {};
143  this->cancel_timeout("wifi-connect-timeout");
144  this->set_state_(improv::STATE_PROVISIONED);
145 
146  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
147  this->send_response_(url);
148  }
149  }
150 }
151 
152 std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
153  std::vector<std::string> urls;
154  if (!this->next_url_.empty()) {
155  urls.push_back(this->get_formatted_next_url_());
156  }
157 #ifdef USE_WEBSERVER
159  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
160  urls.push_back(webserver_url);
161 #endif
162  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
163  return data;
164 }
165 
166 std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
167  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
168  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
169  return data;
170 };
171 
173  size_t at = this->rx_buffer_.size();
174  this->rx_buffer_.push_back(byte);
175  ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
176  const uint8_t *raw = &this->rx_buffer_[0];
177 
178  return improv::parse_improv_serial_byte(
179  at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
180  [this](improv::Error error) -> void {
181  ESP_LOGW(TAG, "Error decoding Improv payload");
182  this->set_error_(error);
183  });
184 }
185 
186 bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
187  switch (command.command) {
188  case improv::WIFI_SETTINGS: {
189  wifi::WiFiAP sta{};
190  sta.set_ssid(command.ssid);
191  sta.set_password(command.password);
192  this->connecting_sta_ = sta;
193 
196  this->set_state_(improv::STATE_PROVISIONING);
197  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
198  command.password.c_str());
199 
200  auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
201  this->set_timeout("wifi-connect-timeout", 30000, f);
202  return true;
203  }
204  case improv::GET_CURRENT_STATE:
205  this->set_state_(this->state_);
206  if (this->state_ == improv::STATE_PROVISIONED) {
207  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
208  this->send_response_(url);
209  }
210  return true;
211  case improv::GET_DEVICE_INFO: {
212  std::vector<uint8_t> info = this->build_version_info_();
213  this->send_response_(info);
214  return true;
215  }
216  case improv::GET_WIFI_NETWORKS: {
217  std::vector<std::string> networks;
219  for (auto &scan : results) {
220  if (scan.get_is_hidden())
221  continue;
222  const std::string &ssid = scan.get_ssid();
223  if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
224  continue;
225  // Send each ssid separately to avoid overflowing the buffer
226  std::vector<uint8_t> data = improv::build_rpc_response(
227  improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
228  this->send_response_(data);
229  networks.push_back(ssid);
230  }
231  // Send empty response to signify the end of the list.
232  std::vector<uint8_t> data =
233  improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
234  this->send_response_(data);
235  return true;
236  }
237  default: {
238  ESP_LOGW(TAG, "Unknown Improv payload");
239  this->set_error_(improv::ERROR_UNKNOWN_RPC);
240  return false;
241  }
242  }
243 }
244 
246  this->state_ = state;
247 
248  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
249  data.resize(11);
250  data[6] = IMPROV_SERIAL_VERSION;
251  data[7] = TYPE_CURRENT_STATE;
252  data[8] = 1;
253  data[9] = state;
254 
255  uint8_t checksum = 0x00;
256  for (uint8_t d : data)
257  checksum += d;
258  data[10] = checksum;
259 
260  this->write_data_(data);
261 }
262 
264  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
265  data.resize(11);
266  data[6] = IMPROV_SERIAL_VERSION;
267  data[7] = TYPE_ERROR_STATE;
268  data[8] = 1;
269  data[9] = error;
270 
271  uint8_t checksum = 0x00;
272  for (uint8_t d : data)
273  checksum += d;
274  data[10] = checksum;
275  this->write_data_(data);
276 }
277 
278 void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
279  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
280  data.resize(9);
281  data[6] = IMPROV_SERIAL_VERSION;
282  data[7] = TYPE_RPC_RESPONSE;
283  data[8] = response.size();
284  data.insert(data.end(), response.begin(), response.end());
285 
286  uint8_t checksum = 0x00;
287  for (uint8_t d : data)
288  checksum += d;
289  data.push_back(checksum);
290 
291  this->write_data_(data);
292 }
293 
295  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
296  this->set_state_(improv::STATE_AUTHORIZED);
297  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
299 }
300 
301 ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
302  nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
303 
304 } // namespace improv_serial
305 } // namespace esphome
uint8_t raw[35]
Definition: bl0939.h:19
void write_data_(std::vector< uint8_t > &data)
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition: component.cpp:72
const std::string & get_password() const
bool parse_improv_payload_(improv::ImprovCommand &command)
void save_wifi_sta(const std::string &ssid, const std::string &password)
const std::vector< WiFiScanResult > & get_scan_result() const
std::string str() const
Definition: ip_address.h:112
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
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
Logger * global_logger
Definition: logger.cpp:460
std::vector< uint8_t > build_rpc_settings_response_(improv::Command command)
const char *const TAG
Definition: spi.cpp:8
void send_response_(std::vector< uint8_t > &response)
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:310
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t checksum
Definition: bl0939.h:35
WiFiComponent * global_wifi_component
const std::string & get_name() const
Get the name of this Application set by pre_setup().
Definition: application.h:160
Stream * get_hw_serial() const
Definition: logger.h:76
std::string to_string(int value)
Definition: helpers.cpp:80
void set_sta(const WiFiAP &ap)
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
const std::string & get_ssid() const
uart_port_t get_uart_num() const
Definition: logger.h:79
std::vector< uint8_t > build_version_info_()
bool state
Definition: fan.h:34
ImprovSerialComponent * global_improv_serial_component