ESPHome  2024.9.0
improv_serial_component.cpp
Go to the documentation of this file.
2 #ifdef USE_WIFI
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(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
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_LOGGER_USB_CDC
72 #ifdef USE_LOGGER_USB_SERIAL_JTAG
74  if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
75  byte = data;
76  }
77  break;
78  }
79 #endif // USE_LOGGER_USB_SERIAL_JTAG
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(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
104  const char *msg = (char *) data.data();
105  esp_usb_console_write_buf(msg, data.size());
106  break;
107  }
108 #endif // USE_LOGGER_USB_CDC
109 #ifdef USE_LOGGER_USB_SERIAL_JTAG
111  usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
112  delay(10);
113  usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
114  break;
115 #endif // USE_LOGGER_USB_SERIAL_JTAG
116  default:
117  break;
118  }
119 #endif
120 }
121 
123  if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
124  this->last_read_byte_ = 0;
125  this->rx_buffer_.clear();
126  ESP_LOGV(TAG, "Improv Serial timeout");
127  }
128 
129  auto byte = this->read_byte_();
130  while (byte.has_value()) {
131  if (this->parse_improv_serial_byte_(byte.value())) {
132  this->last_read_byte_ = millis();
133  } else {
134  this->last_read_byte_ = 0;
135  this->rx_buffer_.clear();
136  }
137  byte = this->read_byte_();
138  }
139 
140  if (this->state_ == improv::STATE_PROVISIONING) {
143  this->connecting_sta_.get_password());
144  this->connecting_sta_ = {};
145  this->cancel_timeout("wifi-connect-timeout");
146  this->set_state_(improv::STATE_PROVISIONED);
147 
148  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
149  this->send_response_(url);
150  }
151  }
152 }
153 
154 std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
155  std::vector<std::string> urls;
156  if (!this->next_url_.empty()) {
157  urls.push_back(this->get_formatted_next_url_());
158  }
159 #ifdef USE_WEBSERVER
160  for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
161  if (ip.is_ip4()) {
162  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
163  urls.push_back(webserver_url);
164  break;
165  }
166  }
167 #endif
168  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
169  return data;
170 }
171 
172 std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
173 #ifdef ESPHOME_PROJECT_NAME
174  std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
175 #else
176  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
177 #endif
178  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
179  return data;
180 };
181 
183  size_t at = this->rx_buffer_.size();
184  this->rx_buffer_.push_back(byte);
185  ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
186  const uint8_t *raw = &this->rx_buffer_[0];
187 
188  return improv::parse_improv_serial_byte(
189  at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
190  [this](improv::Error error) -> void {
191  ESP_LOGW(TAG, "Error decoding Improv payload");
192  this->set_error_(error);
193  });
194 }
195 
196 bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
197  switch (command.command) {
198  case improv::WIFI_SETTINGS: {
199  wifi::WiFiAP sta{};
200  sta.set_ssid(command.ssid);
201  sta.set_password(command.password);
202  this->connecting_sta_ = sta;
203 
206  this->set_state_(improv::STATE_PROVISIONING);
207  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
208  command.password.c_str());
209 
210  auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
211  this->set_timeout("wifi-connect-timeout", 30000, f);
212  return true;
213  }
214  case improv::GET_CURRENT_STATE:
215  this->set_state_(this->state_);
216  if (this->state_ == improv::STATE_PROVISIONED) {
217  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
218  this->send_response_(url);
219  }
220  return true;
221  case improv::GET_DEVICE_INFO: {
222  std::vector<uint8_t> info = this->build_version_info_();
223  this->send_response_(info);
224  return true;
225  }
226  case improv::GET_WIFI_NETWORKS: {
227  std::vector<std::string> networks;
229  for (auto &scan : results) {
230  if (scan.get_is_hidden())
231  continue;
232  const std::string &ssid = scan.get_ssid();
233  if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
234  continue;
235  // Send each ssid separately to avoid overflowing the buffer
236  std::vector<uint8_t> data = improv::build_rpc_response(
237  improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
238  this->send_response_(data);
239  networks.push_back(ssid);
240  }
241  // Send empty response to signify the end of the list.
242  std::vector<uint8_t> data =
243  improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
244  this->send_response_(data);
245  return true;
246  }
247  default: {
248  ESP_LOGW(TAG, "Unknown Improv payload");
249  this->set_error_(improv::ERROR_UNKNOWN_RPC);
250  return false;
251  }
252  }
253 }
254 
256  this->state_ = state;
257 
258  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
259  data.resize(11);
260  data[6] = IMPROV_SERIAL_VERSION;
261  data[7] = TYPE_CURRENT_STATE;
262  data[8] = 1;
263  data[9] = state;
264 
265  uint8_t checksum = 0x00;
266  for (uint8_t d : data)
267  checksum += d;
268  data[10] = checksum;
269 
270  this->write_data_(data);
271 }
272 
274  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
275  data.resize(11);
276  data[6] = IMPROV_SERIAL_VERSION;
277  data[7] = TYPE_ERROR_STATE;
278  data[8] = 1;
279  data[9] = error;
280 
281  uint8_t checksum = 0x00;
282  for (uint8_t d : data)
283  checksum += d;
284  data[10] = checksum;
285  this->write_data_(data);
286 }
287 
288 void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
289  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
290  data.resize(9);
291  data[6] = IMPROV_SERIAL_VERSION;
292  data[7] = TYPE_RPC_RESPONSE;
293  data[8] = response.size();
294  data.insert(data.end(), response.begin(), response.end());
295 
296  uint8_t checksum = 0x00;
297  for (uint8_t d : data)
298  checksum += d;
299  data.push_back(checksum);
300 
301  this->write_data_(data);
302 }
303 
305  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
306  this->set_state_(improv::STATE_AUTHORIZED);
307  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
309 }
310 
311 ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
312  nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
313 
314 } // namespace improv_serial
315 } // namespace esphome
316 #endif
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:73
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)
uint8_t checksum
Definition: bl0906.h:210
const std::vector< WiFiScanResult > & get_scan_result() const
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
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)
Logger * global_logger
Definition: logger.cpp:198
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:312
Application App
Global storage of Application pointer - only one Application can exist.
WiFiComponent * global_wifi_component
const std::string & get_name() const
Get the name of this Application set by pre_setup().
Definition: application.h:202
Stream * get_hw_serial() const
Definition: logger.h:66
std::string to_string(int value)
Definition: helpers.cpp:82
void set_sta(const WiFiAP &ap)
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
const std::string & get_ssid() const
uart_port_t get_uart_num() const
Definition: logger.h:69
std::vector< uint8_t > build_version_info_()
bool state
Definition: fan.h:34
ImprovSerialComponent * global_improv_serial_component
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26