ESPHome  2024.7.2
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(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  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
174  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
175  return data;
176 };
177 
179  size_t at = this->rx_buffer_.size();
180  this->rx_buffer_.push_back(byte);
181  ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
182  const uint8_t *raw = &this->rx_buffer_[0];
183 
184  return improv::parse_improv_serial_byte(
185  at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
186  [this](improv::Error error) -> void {
187  ESP_LOGW(TAG, "Error decoding Improv payload");
188  this->set_error_(error);
189  });
190 }
191 
192 bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
193  switch (command.command) {
194  case improv::WIFI_SETTINGS: {
195  wifi::WiFiAP sta{};
196  sta.set_ssid(command.ssid);
197  sta.set_password(command.password);
198  this->connecting_sta_ = sta;
199 
202  this->set_state_(improv::STATE_PROVISIONING);
203  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
204  command.password.c_str());
205 
206  auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
207  this->set_timeout("wifi-connect-timeout", 30000, f);
208  return true;
209  }
210  case improv::GET_CURRENT_STATE:
211  this->set_state_(this->state_);
212  if (this->state_ == improv::STATE_PROVISIONED) {
213  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
214  this->send_response_(url);
215  }
216  return true;
217  case improv::GET_DEVICE_INFO: {
218  std::vector<uint8_t> info = this->build_version_info_();
219  this->send_response_(info);
220  return true;
221  }
222  case improv::GET_WIFI_NETWORKS: {
223  std::vector<std::string> networks;
225  for (auto &scan : results) {
226  if (scan.get_is_hidden())
227  continue;
228  const std::string &ssid = scan.get_ssid();
229  if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
230  continue;
231  // Send each ssid separately to avoid overflowing the buffer
232  std::vector<uint8_t> data = improv::build_rpc_response(
233  improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
234  this->send_response_(data);
235  networks.push_back(ssid);
236  }
237  // Send empty response to signify the end of the list.
238  std::vector<uint8_t> data =
239  improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
240  this->send_response_(data);
241  return true;
242  }
243  default: {
244  ESP_LOGW(TAG, "Unknown Improv payload");
245  this->set_error_(improv::ERROR_UNKNOWN_RPC);
246  return false;
247  }
248  }
249 }
250 
252  this->state_ = state;
253 
254  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
255  data.resize(11);
256  data[6] = IMPROV_SERIAL_VERSION;
257  data[7] = TYPE_CURRENT_STATE;
258  data[8] = 1;
259  data[9] = state;
260 
261  uint8_t checksum = 0x00;
262  for (uint8_t d : data)
263  checksum += d;
264  data[10] = checksum;
265 
266  this->write_data_(data);
267 }
268 
270  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
271  data.resize(11);
272  data[6] = IMPROV_SERIAL_VERSION;
273  data[7] = TYPE_ERROR_STATE;
274  data[8] = 1;
275  data[9] = error;
276 
277  uint8_t checksum = 0x00;
278  for (uint8_t d : data)
279  checksum += d;
280  data[10] = checksum;
281  this->write_data_(data);
282 }
283 
284 void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
285  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
286  data.resize(9);
287  data[6] = IMPROV_SERIAL_VERSION;
288  data[7] = TYPE_RPC_RESPONSE;
289  data[8] = response.size();
290  data.insert(data.end(), response.begin(), response.end());
291 
292  uint8_t checksum = 0x00;
293  for (uint8_t d : data)
294  checksum += d;
295  data.push_back(checksum);
296 
297  this->write_data_(data);
298 }
299 
301  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
302  this->set_state_(improv::STATE_AUTHORIZED);
303  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
305 }
306 
307 ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
308  nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
309 
310 } // namespace improv_serial
311 } // 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: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)
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.
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: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