ESPHome 2025.12.3
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
32 if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
33 this->last_read_byte_ = 0;
34 this->rx_buffer_.clear();
35 ESP_LOGV(TAG, "Timeout");
36 }
37
38 auto byte = this->read_byte_();
39 while (byte.has_value()) {
40 if (this->parse_improv_serial_byte_(byte.value())) {
41 this->last_read_byte_ = millis();
42 } else {
43 this->last_read_byte_ = 0;
44 this->rx_buffer_.clear();
45 }
46 byte = this->read_byte_();
47 }
48
49 if (this->state_ == improv::STATE_PROVISIONING) {
50 if (wifi::global_wifi_component->is_connected()) {
52 this->connecting_sta_.get_password());
53 this->connecting_sta_ = {};
54 this->cancel_timeout("wifi-connect-timeout");
55 this->set_state_(improv::STATE_PROVISIONED);
56
57 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
58 this->send_response_(url);
59 }
60 }
61}
62
63void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
64
67 uint8_t data = 0;
68#ifdef USE_ESP32
69 switch (logger::global_logger->get_uart()) {
72#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
73 !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
75#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32C61 &&
76 // !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
77 if (this->uart_num_ >= 0) {
78 size_t available;
79 uart_get_buffered_data_len(this->uart_num_, &available);
80 if (available) {
81 uart_read_bytes(this->uart_num_, &data, 1, 0);
82 byte = data;
83 }
84 }
85 break;
86#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
88 if (esp_usb_console_available_for_read()) {
89 esp_usb_console_read_buf((char *) &data, 1);
90 byte = data;
91 }
92 break;
93#endif // USE_LOGGER_USB_CDC
94#ifdef USE_LOGGER_USB_SERIAL_JTAG
96 if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
97 byte = data;
98 }
99 break;
100 }
101#endif // USE_LOGGER_USB_SERIAL_JTAG
102 default:
103 break;
104 }
105#elif defined(USE_ARDUINO)
106 if (this->hw_serial_->available()) {
107 this->hw_serial_->readBytes(&data, 1);
108 byte = data;
109 }
110#endif
111 return byte;
112}
113
114void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) {
115 // First, set length field
116 this->tx_header_[TX_LENGTH_IDX] = this->tx_header_[TX_TYPE_IDX] == TYPE_RPC_RESPONSE ? size : 1;
117
118 const bool there_is_data = data != nullptr && size > 0;
119 // If there_is_data, checksum must not include our optional data byte
120 const uint8_t header_checksum_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE - 2;
121 // Only transmit the full buffer length if there is no data (only state/error byte is provided in this case)
122 const uint8_t header_tx_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE;
123 // Calculate checksum for message
124 uint8_t checksum = 0;
125 for (uint8_t i = 0; i < header_checksum_len; i++) {
126 checksum += this->tx_header_[i];
127 }
128 if (there_is_data) {
129 // Include data in checksum
130 for (size_t i = 0; i < size; i++) {
131 checksum += data[i];
132 }
133 }
134 this->tx_header_[TX_CHECKSUM_IDX] = checksum;
135
136#ifdef USE_ESP32
137 switch (logger::global_logger->get_uart()) {
140#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
141 !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
143#endif
144 uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len);
145 if (there_is_data) {
146 uart_write_bytes(this->uart_num_, data, size);
147 uart_write_bytes(this->uart_num_, &this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
148 }
149 break;
150#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
152 esp_usb_console_write_buf((const char *) this->tx_header_, header_tx_len);
153 if (there_is_data) {
154 esp_usb_console_write_buf((const char *) data, size);
155 esp_usb_console_write_buf((const char *) &this->tx_header_[TX_CHECKSUM_IDX],
156 2); // Footer: checksum and newline
157 }
158 break;
159#endif
160#ifdef USE_LOGGER_USB_SERIAL_JTAG
162 usb_serial_jtag_write_bytes((const char *) this->tx_header_, header_tx_len, 20 / portTICK_PERIOD_MS);
163 if (there_is_data) {
164 usb_serial_jtag_write_bytes((const char *) data, size, 20 / portTICK_PERIOD_MS);
165 usb_serial_jtag_write_bytes((const char *) &this->tx_header_[TX_CHECKSUM_IDX], 2,
166 20 / portTICK_PERIOD_MS); // Footer: checksum and newline
167 }
168 break;
169#endif
170 default:
171 break;
172 }
173#elif defined(USE_ARDUINO)
174 this->hw_serial_->write(this->tx_header_, header_tx_len);
175 if (there_is_data) {
176 this->hw_serial_->write(data, size);
177 this->hw_serial_->write(&this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
178 }
179#endif
180}
181
182std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
183 std::vector<std::string> urls;
184#ifdef USE_IMPROV_SERIAL_NEXT_URL
185 if (!this->next_url_.empty()) {
186 urls.push_back(this->get_formatted_next_url_());
187 }
188#endif
189#ifdef USE_WEBSERVER
190 for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
191 if (ip.is_ip4()) {
192 std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
193 urls.push_back(webserver_url);
194 break;
195 }
196 }
197#endif
198 std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
199 return data;
200}
201
203#ifdef ESPHOME_PROJECT_NAME
204 std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
205#else
206 std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
207#endif
208 std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
209 return data;
210};
211
213 size_t at = this->rx_buffer_.size();
214 this->rx_buffer_.push_back(byte);
215 ESP_LOGV(TAG, "Byte: 0x%02X", byte);
216 const uint8_t *raw = &this->rx_buffer_[0];
217
218 return improv::parse_improv_serial_byte(
219 at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
220 [this](improv::Error error) -> void {
221 ESP_LOGW(TAG, "Error decoding payload");
222 this->set_error_(error);
223 });
224}
225
226bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
227 switch (command.command) {
228 case improv::WIFI_SETTINGS: {
229 wifi::WiFiAP sta{};
230 sta.set_ssid(command.ssid);
231 sta.set_password(command.password);
232 this->connecting_sta_ = sta;
233
236 this->set_state_(improv::STATE_PROVISIONING);
237 ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
238 command.password.c_str());
239
240 auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
241 this->set_timeout("wifi-connect-timeout", 30000, f);
242 return true;
243 }
244 case improv::GET_CURRENT_STATE:
245 this->set_state_(this->state_);
246 if (this->state_ == improv::STATE_PROVISIONED) {
247 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
248 this->send_response_(url);
249 }
250 return true;
251 case improv::GET_DEVICE_INFO: {
252 std::vector<uint8_t> info = this->build_version_info_();
253 this->send_response_(info);
254 return true;
255 }
256 case improv::GET_WIFI_NETWORKS: {
257 std::vector<std::string> networks;
258 const auto &results = wifi::global_wifi_component->get_scan_result();
259 for (auto &scan : results) {
260 if (scan.get_is_hidden())
261 continue;
262 const std::string &ssid = scan.get_ssid();
263 if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
264 continue;
265 // Send each ssid separately to avoid overflowing the buffer
266 std::vector<uint8_t> data = improv::build_rpc_response(
267 improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
268 this->send_response_(data);
269 networks.push_back(ssid);
270 }
271 // Send empty response to signify the end of the list.
272 std::vector<uint8_t> data =
273 improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
274 this->send_response_(data);
275 return true;
276 }
277 default: {
278 ESP_LOGW(TAG, "Unknown payload");
279 this->set_error_(improv::ERROR_UNKNOWN_RPC);
280 return false;
281 }
282 }
283}
284
286 this->state_ = state;
287 this->tx_header_[TX_TYPE_IDX] = TYPE_CURRENT_STATE;
288 this->tx_header_[TX_DATA_IDX] = state;
289 this->write_data_();
290}
291
292void ImprovSerialComponent::set_error_(improv::Error error) {
293 this->tx_header_[TX_TYPE_IDX] = TYPE_ERROR_STATE;
294 this->tx_header_[TX_DATA_IDX] = error;
295 this->write_data_();
296}
297
298void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
299 this->tx_header_[TX_TYPE_IDX] = TYPE_RPC_RESPONSE;
300 this->write_data_(response.data(), response.size());
301}
302
304 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
305 this->set_state_(improv::STATE_AUTHORIZED);
306 ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
308}
309
310ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
311 nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
312
313} // namespace improv_serial
314} // namespace esphome
315#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 write_data_(const uint8_t *data=nullptr, size_t size=0)
void send_response_(std::vector< uint8_t > &response)
bool parse_improv_payload_(improv::ImprovCommand &command)
Stream * get_hw_serial() const
Definition logger.h:189
uart_port_t get_uart_num() const
Definition logger.h:192
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)
const wifi_scan_vector_t< WiFiScanResult > & get_scan_result() const
bool state
Definition fan.h:0
ImprovSerialComponent * global_improv_serial_component
@ UART_SELECTION_UART2
Definition logger.h:145
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:151
@ UART_SELECTION_USB_CDC
Definition logger.h:148
@ UART_SELECTION_UART0
Definition logger.h:139
@ UART_SELECTION_UART1
Definition logger.h:143
Logger * global_logger
Definition logger.cpp:297
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:222
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
Application App
Global storage of Application pointer - only one Application can exist.