ESPHome 2025.10.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_ESP32
20#elif defined(USE_ARDUINO)
22#endif
23
24 if (wifi::global_wifi_component->has_sta()) {
25 this->state_ = improv::STATE_PROVISIONED;
26 } else {
28 }
29}
30
31void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
32
35 uint8_t data = 0;
36#ifdef USE_ESP32
37 switch (logger::global_logger->get_uart()) {
40#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
41 !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
43#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
44 if (this->uart_num_ >= 0) {
45 size_t available;
46 uart_get_buffered_data_len(this->uart_num_, &available);
47 if (available) {
48 uart_read_bytes(this->uart_num_, &data, 1, 0);
49 byte = data;
50 }
51 }
52 break;
53#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
55 if (esp_usb_console_available_for_read()) {
56 esp_usb_console_read_buf((char *) &data, 1);
57 byte = data;
58 }
59 break;
60#endif // USE_LOGGER_USB_CDC
61#ifdef USE_LOGGER_USB_SERIAL_JTAG
63 if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
64 byte = data;
65 }
66 break;
67 }
68#endif // USE_LOGGER_USB_SERIAL_JTAG
69 default:
70 break;
71 }
72#elif defined(USE_ARDUINO)
73 if (this->hw_serial_->available()) {
74 this->hw_serial_->readBytes(&data, 1);
75 byte = data;
76 }
77#endif
78 return byte;
79}
80
81void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
82 data.push_back('\n');
83#ifdef USE_ESP32
84 switch (logger::global_logger->get_uart()) {
87#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
88 !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
90#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
91 uart_write_bytes(this->uart_num_, data.data(), data.size());
92 break;
93#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
95 const char *msg = (char *) data.data();
96 esp_usb_console_write_buf(msg, data.size());
97 break;
98 }
99#endif // USE_LOGGER_USB_CDC
100#ifdef USE_LOGGER_USB_SERIAL_JTAG
102 usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
103 delay(10);
104 usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
105 break;
106#endif // USE_LOGGER_USB_SERIAL_JTAG
107 default:
108 break;
109 }
110#elif defined(USE_ARDUINO)
111 this->hw_serial_->write(data.data(), data.size());
112#endif
113}
114
116 if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
117 this->last_read_byte_ = 0;
118 this->rx_buffer_.clear();
119 ESP_LOGV(TAG, "Improv Serial timeout");
120 }
121
122 auto byte = this->read_byte_();
123 while (byte.has_value()) {
124 if (this->parse_improv_serial_byte_(byte.value())) {
125 this->last_read_byte_ = millis();
126 } else {
127 this->last_read_byte_ = 0;
128 this->rx_buffer_.clear();
129 }
130 byte = this->read_byte_();
131 }
132
133 if (this->state_ == improv::STATE_PROVISIONING) {
134 if (wifi::global_wifi_component->is_connected()) {
136 this->connecting_sta_.get_password());
137 this->connecting_sta_ = {};
138 this->cancel_timeout("wifi-connect-timeout");
139 this->set_state_(improv::STATE_PROVISIONED);
140
141 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
142 this->send_response_(url);
143 }
144 }
145}
146
147std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
148 std::vector<std::string> urls;
149 if (!this->next_url_.empty()) {
150 urls.push_back(this->get_formatted_next_url_());
151 }
152#ifdef USE_WEBSERVER
153 for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
154 if (ip.is_ip4()) {
155 std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
156 urls.push_back(webserver_url);
157 break;
158 }
159 }
160#endif
161 std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
162 return data;
163}
164
166#ifdef ESPHOME_PROJECT_NAME
167 std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
168#else
169 std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
170#endif
171 std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
172 return data;
173};
174
176 size_t at = this->rx_buffer_.size();
177 this->rx_buffer_.push_back(byte);
178 ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
179 const uint8_t *raw = &this->rx_buffer_[0];
180
181 return improv::parse_improv_serial_byte(
182 at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
183 [this](improv::Error error) -> void {
184 ESP_LOGW(TAG, "Error decoding Improv payload");
185 this->set_error_(error);
186 });
187}
188
189bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
190 switch (command.command) {
191 case improv::WIFI_SETTINGS: {
192 wifi::WiFiAP sta{};
193 sta.set_ssid(command.ssid);
194 sta.set_password(command.password);
195 this->connecting_sta_ = sta;
196
199 this->set_state_(improv::STATE_PROVISIONING);
200 ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
201 command.password.c_str());
202
203 auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
204 this->set_timeout("wifi-connect-timeout", 30000, f);
205 return true;
206 }
207 case improv::GET_CURRENT_STATE:
208 this->set_state_(this->state_);
209 if (this->state_ == improv::STATE_PROVISIONED) {
210 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
211 this->send_response_(url);
212 }
213 return true;
214 case improv::GET_DEVICE_INFO: {
215 std::vector<uint8_t> info = this->build_version_info_();
216 this->send_response_(info);
217 return true;
218 }
219 case improv::GET_WIFI_NETWORKS: {
220 std::vector<std::string> networks;
222 for (auto &scan : results) {
223 if (scan.get_is_hidden())
224 continue;
225 const std::string &ssid = scan.get_ssid();
226 if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
227 continue;
228 // Send each ssid separately to avoid overflowing the buffer
229 std::vector<uint8_t> data = improv::build_rpc_response(
230 improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
231 this->send_response_(data);
232 networks.push_back(ssid);
233 }
234 // Send empty response to signify the end of the list.
235 std::vector<uint8_t> data =
236 improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
237 this->send_response_(data);
238 return true;
239 }
240 default: {
241 ESP_LOGW(TAG, "Unknown Improv payload");
242 this->set_error_(improv::ERROR_UNKNOWN_RPC);
243 return false;
244 }
245 }
246}
247
249 this->state_ = state;
250
251 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
252 data.resize(11);
253 data[6] = IMPROV_SERIAL_VERSION;
254 data[7] = TYPE_CURRENT_STATE;
255 data[8] = 1;
256 data[9] = state;
257
258 uint8_t checksum = 0x00;
259 for (uint8_t d : data)
260 checksum += d;
261 data[10] = checksum;
262
263 this->write_data_(data);
264}
265
266void ImprovSerialComponent::set_error_(improv::Error error) {
267 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
268 data.resize(11);
269 data[6] = IMPROV_SERIAL_VERSION;
270 data[7] = TYPE_ERROR_STATE;
271 data[8] = 1;
272 data[9] = error;
273
274 uint8_t checksum = 0x00;
275 for (uint8_t d : data)
276 checksum += d;
277 data[10] = checksum;
278 this->write_data_(data);
279}
280
281void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
282 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
283 data.resize(9);
284 data[6] = IMPROV_SERIAL_VERSION;
285 data[7] = TYPE_RPC_RESPONSE;
286 data[8] = response.size();
287 data.insert(data.end(), response.begin(), response.end());
288
289 uint8_t checksum = 0x00;
290 for (uint8_t d : data)
291 checksum += d;
292 data.push_back(checksum);
293
294 this->write_data_(data);
295}
296
298 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
299 this->set_state_(improv::STATE_AUTHORIZED);
300 ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
302}
303
304ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
305 nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
306
307} // namespace improv_serial
308} // namespace esphome
309#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:288
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:221
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.