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