ESPHome  2022.12.8
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 
35 #ifdef USE_ARDUINO
36  return this->hw_serial_->available();
37 #endif
38 #ifdef USE_ESP_IDF
39  size_t available;
40  uart_get_buffered_data_len(this->uart_num_, &available);
41  return available;
42 #endif
43 }
44 
46  uint8_t data;
47 #ifdef USE_ARDUINO
48  this->hw_serial_->readBytes(&data, 1);
49 #endif
50 #ifdef USE_ESP_IDF
51  uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
52 #endif
53  return data;
54 }
55 
56 void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
57  data.push_back('\n');
58 #ifdef USE_ARDUINO
59  this->hw_serial_->write(data.data(), data.size());
60 #endif
61 #ifdef USE_ESP_IDF
62  uart_write_bytes(this->uart_num_, data.data(), data.size());
63 #endif
64 }
65 
67  const uint32_t now = millis();
68  if (now - this->last_read_byte_ > 50) {
69  this->rx_buffer_.clear();
70  this->last_read_byte_ = now;
71  }
72 
73  while (this->available_()) {
74  uint8_t byte = this->read_byte_();
75  if (this->parse_improv_serial_byte_(byte)) {
76  this->last_read_byte_ = now;
77  } else {
78  this->rx_buffer_.clear();
79  }
80  }
81 
82  if (this->state_ == improv::STATE_PROVISIONING) {
86  this->connecting_sta_ = {};
87  this->cancel_timeout("wifi-connect-timeout");
88  this->set_state_(improv::STATE_PROVISIONED);
89 
90  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
91  this->send_response_(url);
92  }
93  }
94 }
95 
96 std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
97  std::vector<std::string> urls;
98 #ifdef USE_WEBSERVER
100  std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
101  urls.push_back(webserver_url);
102 #endif
103  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
104  return data;
105 }
106 
108  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
109  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
110  return data;
111 };
112 
114  size_t at = this->rx_buffer_.size();
115  this->rx_buffer_.push_back(byte);
116  ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
117  const uint8_t *raw = &this->rx_buffer_[0];
118 
119  return improv::parse_improv_serial_byte(
120  at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
121  [this](improv::Error error) -> void {
122  ESP_LOGW(TAG, "Error decoding Improv payload");
123  this->set_error_(error);
124  });
125 }
126 
127 bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
128  switch (command.command) {
129  case improv::WIFI_SETTINGS: {
130  wifi::WiFiAP sta{};
131  sta.set_ssid(command.ssid);
132  sta.set_password(command.password);
133  this->connecting_sta_ = sta;
134 
137  this->set_state_(improv::STATE_PROVISIONING);
138  ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
139  command.password.c_str());
140 
141  auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
142  this->set_timeout("wifi-connect-timeout", 30000, f);
143  return true;
144  }
145  case improv::GET_CURRENT_STATE:
146  this->set_state_(this->state_);
147  if (this->state_ == improv::STATE_PROVISIONED) {
148  std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
149  this->send_response_(url);
150  }
151  return true;
152  case improv::GET_DEVICE_INFO: {
153  std::vector<uint8_t> info = this->build_version_info_();
154  this->send_response_(info);
155  return true;
156  }
157  case improv::GET_WIFI_NETWORKS: {
158  std::vector<std::string> networks;
160  for (auto &scan : results) {
161  if (scan.get_is_hidden())
162  continue;
163  const std::string &ssid = scan.get_ssid();
164  if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
165  continue;
166  // Send each ssid separately to avoid overflowing the buffer
167  std::vector<uint8_t> data = improv::build_rpc_response(
168  improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
169  this->send_response_(data);
170  networks.push_back(ssid);
171  }
172  // Send empty response to signify the end of the list.
173  std::vector<uint8_t> data =
174  improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
175  this->send_response_(data);
176  return true;
177  }
178  default: {
179  ESP_LOGW(TAG, "Unknown Improv payload");
180  this->set_error_(improv::ERROR_UNKNOWN_RPC);
181  return false;
182  }
183  }
184 }
185 
187  this->state_ = state;
188 
189  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
190  data.resize(11);
191  data[6] = IMPROV_SERIAL_VERSION;
192  data[7] = TYPE_CURRENT_STATE;
193  data[8] = 1;
194  data[9] = state;
195 
196  uint8_t checksum = 0x00;
197  for (uint8_t d : data)
198  checksum += d;
199  data[10] = checksum;
200 
201  this->write_data_(data);
202 }
203 
205  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
206  data.resize(11);
207  data[6] = IMPROV_SERIAL_VERSION;
208  data[7] = TYPE_ERROR_STATE;
209  data[8] = 1;
210  data[9] = error;
211 
212  uint8_t checksum = 0x00;
213  for (uint8_t d : data)
214  checksum += d;
215  data[10] = checksum;
216  this->write_data_(data);
217 }
218 
219 void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
220  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
221  data.resize(9);
222  data[6] = IMPROV_SERIAL_VERSION;
223  data[7] = TYPE_RPC_RESPONSE;
224  data[8] = response.size();
225  data.insert(data.end(), response.begin(), response.end());
226 
227  uint8_t checksum = 0x00;
228  for (uint8_t d : data)
229  checksum += d;
230  data.push_back(checksum);
231 
232  this->write_data_(data);
233 }
234 
236  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
237  this->set_state_(improv::STATE_AUTHORIZED);
238  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
240 }
241 
242 ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
243  nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
244 
245 } // namespace improv_serial
246 } // namespace esphome
uint8_t raw[35]
Definition: bl0939.h:19
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: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
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
Logger * global_logger
Definition: logger.cpp:303
void send_response_(std::vector< uint8_t > &response)
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:188
void set_ssid(const std::string &ssid)
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 set_name().
Definition: application.h:135
ImprovSerialComponent * global_improv_serial_component
Stream * get_hw_serial() const
Definition: logger.h:64
std::string to_string(int value)
Definition: helpers.cpp:42
void set_sta(const WiFiAP &ap)
Definition: a4988.cpp:4
const std::string & get_ssid() const
uart_port_t get_uart_num() const
Definition: logger.h:67
std::vector< uint8_t > build_rpc_settings_response_(improv::Command command)
bool state
Definition: fan.h:34