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