ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
improv_serial_component.cpp
Go to the documentation of this file.
2#ifdef USE_WIFI
5#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
8
10
11namespace esphome {
12namespace improv_serial {
13
14static 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
32void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
33
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_usb_console_available_for_read()) {
63 esp_usb_console_read_buf((char *) &data, 1);
64 byte = data;
65 }
66 break;
67#endif // USE_LOGGER_USB_CDC
68#ifdef USE_LOGGER_USB_SERIAL_JTAG
70 if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
71 byte = data;
72 }
73 break;
74 }
75#endif // USE_LOGGER_USB_SERIAL_JTAG
76 default:
77 break;
78 }
79#endif
80 return byte;
81}
82
83void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
84 data.push_back('\n');
85#ifdef USE_ARDUINO
86 this->hw_serial_->write(data.data(), data.size());
87#endif
88#ifdef USE_ESP_IDF
89 switch (logger::global_logger->get_uart()) {
92#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
93 !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
95#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
96 uart_write_bytes(this->uart_num_, data.data(), data.size());
97 break;
98#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
100 const char *msg = (char *) data.data();
101 esp_usb_console_write_buf(msg, data.size());
102 break;
103 }
104#endif // USE_LOGGER_USB_CDC
105#ifdef USE_LOGGER_USB_SERIAL_JTAG
107 usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
108 delay(10);
109 usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
110 break;
111#endif // USE_LOGGER_USB_SERIAL_JTAG
112 default:
113 break;
114 }
115#endif
116}
117
119 if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
120 this->last_read_byte_ = 0;
121 this->rx_buffer_.clear();
122 ESP_LOGV(TAG, "Improv Serial timeout");
123 }
124
125 auto byte = this->read_byte_();
126 while (byte.has_value()) {
127 if (this->parse_improv_serial_byte_(byte.value())) {
128 this->last_read_byte_ = millis();
129 } else {
130 this->last_read_byte_ = 0;
131 this->rx_buffer_.clear();
132 }
133 byte = this->read_byte_();
134 }
135
136 if (this->state_ == improv::STATE_PROVISIONING) {
137 if (wifi::global_wifi_component->is_connected()) {
139 this->connecting_sta_.get_password());
140 this->connecting_sta_ = {};
141 this->cancel_timeout("wifi-connect-timeout");
142 this->set_state_(improv::STATE_PROVISIONED);
143
144 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
145 this->send_response_(url);
146 }
147 }
148}
149
150std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
151 std::vector<std::string> urls;
152 if (!this->next_url_.empty()) {
153 urls.push_back(this->get_formatted_next_url_());
154 }
155#ifdef USE_WEBSERVER
156 for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
157 if (ip.is_ip4()) {
158 std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
159 urls.push_back(webserver_url);
160 break;
161 }
162 }
163#endif
164 std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
165 return data;
166}
167
169#ifdef ESPHOME_PROJECT_NAME
170 std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
171#else
172 std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
173#endif
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
192bool 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
269void ImprovSerialComponent::set_error_(improv::Error error) {
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
284void 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
307ImprovSerialComponent *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
312#endif
uint8_t checksum
Definition bl0906.h:3
uint8_t raw[35]
Definition bl0939.h:0
const std::string & get_name() const
Get the name of this Application set by pre_setup().
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
std::vector< uint8_t > build_rpc_settings_response_(improv::Command command)
void send_response_(std::vector< uint8_t > &response)
bool parse_improv_payload_(improv::ImprovCommand &command)
Stream * get_hw_serial() const
Definition logger.h:120
uart_port_t get_uart_num() const
Definition logger.h:123
const std::string & get_ssid() const
void set_ssid(const std::string &ssid)
void set_sta(const WiFiAP &ap)
void save_wifi_sta(const std::string &ssid, const std::string &password)
void start_connecting(const WiFiAP &ap, bool two)
const std::vector< WiFiScanResult > & get_scan_result() const
bool state
Definition fan.h:0
ImprovSerialComponent * global_improv_serial_component
@ UART_SELECTION_UART2
Definition logger.h:76
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:82
@ UART_SELECTION_USB_CDC
Definition logger.h:79
@ UART_SELECTION_UART0
Definition logger.h:70
@ UART_SELECTION_UART1
Definition logger.h:74
Logger * global_logger
Definition logger.cpp:283
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:208
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.