ESPHome 2025.5.0
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
34optional<uint8_t> ImprovSerialComponent::read_byte_() {
35 optional<uint8_t> byte;
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_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
63 if (esp_usb_console_available_for_read()) {
64#else
65 if (esp_usb_console_read_available()) {
66#endif
67 esp_usb_console_read_buf((char *) &data, 1);
68 byte = data;
69 }
70 break;
71#endif // USE_LOGGER_USB_CDC
72#ifdef USE_LOGGER_USB_SERIAL_JTAG
74 if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
75 byte = data;
76 }
77 break;
78 }
79#endif // USE_LOGGER_USB_SERIAL_JTAG
80 default:
81 break;
82 }
83#endif
84 return byte;
85}
86
87void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
88 data.push_back('\n');
89#ifdef USE_ARDUINO
90 this->hw_serial_->write(data.data(), data.size());
91#endif
92#ifdef USE_ESP_IDF
93 switch (logger::global_logger->get_uart()) {
96#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
97 !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
99#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
100 uart_write_bytes(this->uart_num_, data.data(), data.size());
101 break;
102#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
104 const char *msg = (char *) data.data();
105 esp_usb_console_write_buf(msg, data.size());
106 break;
107 }
108#endif // USE_LOGGER_USB_CDC
109#ifdef USE_LOGGER_USB_SERIAL_JTAG
111 usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
112 delay(10);
113 usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
114 break;
115#endif // USE_LOGGER_USB_SERIAL_JTAG
116 default:
117 break;
118 }
119#endif
120}
121
123 if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
124 this->last_read_byte_ = 0;
125 this->rx_buffer_.clear();
126 ESP_LOGV(TAG, "Improv Serial timeout");
127 }
128
129 auto byte = this->read_byte_();
130 while (byte.has_value()) {
131 if (this->parse_improv_serial_byte_(byte.value())) {
132 this->last_read_byte_ = millis();
133 } else {
134 this->last_read_byte_ = 0;
135 this->rx_buffer_.clear();
136 }
137 byte = this->read_byte_();
138 }
139
140 if (this->state_ == improv::STATE_PROVISIONING) {
143 this->connecting_sta_.get_password());
144 this->connecting_sta_ = {};
145 this->cancel_timeout("wifi-connect-timeout");
146 this->set_state_(improv::STATE_PROVISIONED);
147
148 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
149 this->send_response_(url);
150 }
151 }
152}
153
154std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
155 std::vector<std::string> urls;
156 if (!this->next_url_.empty()) {
157 urls.push_back(this->get_formatted_next_url_());
158 }
159#ifdef USE_WEBSERVER
160 for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
161 if (ip.is_ip4()) {
162 std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
163 urls.push_back(webserver_url);
164 break;
165 }
166 }
167#endif
168 std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
169 return data;
170}
171
172std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
173#ifdef ESPHOME_PROJECT_NAME
174 std::vector<std::string> infos = {ESPHOME_PROJECT_NAME, ESPHOME_PROJECT_VERSION, ESPHOME_VARIANT, App.get_name()};
175#else
176 std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
177#endif
178 std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
179 return data;
180};
181
183 size_t at = this->rx_buffer_.size();
184 this->rx_buffer_.push_back(byte);
185 ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
186 const uint8_t *raw = &this->rx_buffer_[0];
187
188 return improv::parse_improv_serial_byte(
189 at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
190 [this](improv::Error error) -> void {
191 ESP_LOGW(TAG, "Error decoding Improv payload");
192 this->set_error_(error);
193 });
194}
195
196bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
197 switch (command.command) {
198 case improv::WIFI_SETTINGS: {
199 wifi::WiFiAP sta{};
200 sta.set_ssid(command.ssid);
201 sta.set_password(command.password);
202 this->connecting_sta_ = sta;
203
206 this->set_state_(improv::STATE_PROVISIONING);
207 ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
208 command.password.c_str());
209
210 auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
211 this->set_timeout("wifi-connect-timeout", 30000, f);
212 return true;
213 }
214 case improv::GET_CURRENT_STATE:
215 this->set_state_(this->state_);
216 if (this->state_ == improv::STATE_PROVISIONED) {
217 std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
218 this->send_response_(url);
219 }
220 return true;
221 case improv::GET_DEVICE_INFO: {
222 std::vector<uint8_t> info = this->build_version_info_();
223 this->send_response_(info);
224 return true;
225 }
226 case improv::GET_WIFI_NETWORKS: {
227 std::vector<std::string> networks;
229 for (auto &scan : results) {
230 if (scan.get_is_hidden())
231 continue;
232 const std::string &ssid = scan.get_ssid();
233 if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
234 continue;
235 // Send each ssid separately to avoid overflowing the buffer
236 std::vector<uint8_t> data = improv::build_rpc_response(
237 improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
238 this->send_response_(data);
239 networks.push_back(ssid);
240 }
241 // Send empty response to signify the end of the list.
242 std::vector<uint8_t> data =
243 improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
244 this->send_response_(data);
245 return true;
246 }
247 default: {
248 ESP_LOGW(TAG, "Unknown Improv payload");
249 this->set_error_(improv::ERROR_UNKNOWN_RPC);
250 return false;
251 }
252 }
253}
254
255void ImprovSerialComponent::set_state_(improv::State state) {
256 this->state_ = state;
257
258 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
259 data.resize(11);
260 data[6] = IMPROV_SERIAL_VERSION;
261 data[7] = TYPE_CURRENT_STATE;
262 data[8] = 1;
263 data[9] = state;
264
265 uint8_t checksum = 0x00;
266 for (uint8_t d : data)
267 checksum += d;
268 data[10] = checksum;
269
270 this->write_data_(data);
271}
272
273void ImprovSerialComponent::set_error_(improv::Error error) {
274 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
275 data.resize(11);
276 data[6] = IMPROV_SERIAL_VERSION;
277 data[7] = TYPE_ERROR_STATE;
278 data[8] = 1;
279 data[9] = error;
280
281 uint8_t checksum = 0x00;
282 for (uint8_t d : data)
283 checksum += d;
284 data[10] = checksum;
285 this->write_data_(data);
286}
287
288void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
289 std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
290 data.resize(9);
291 data[6] = IMPROV_SERIAL_VERSION;
292 data[7] = TYPE_RPC_RESPONSE;
293 data[8] = response.size();
294 data.insert(data.end(), response.begin(), response.end());
295
296 uint8_t checksum = 0x00;
297 for (uint8_t d : data)
298 checksum += d;
299 data.push_back(checksum);
300
301 this->write_data_(data);
302}
303
305 this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
306 this->set_state_(improv::STATE_AUTHORIZED);
307 ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
309}
310
311ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
312 nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
313
314} // namespace improv_serial
315} // namespace esphome
316#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.
Definition component.cpp:76
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:72
std::vector< uint8_t > build_rpc_settings_response_(improv::Command command)
void send_response_(std::vector< uint8_t > &response)
void write_data_(std::vector< uint8_t > &data)
std::vector< uint8_t > build_version_info_()
bool parse_improv_payload_(improv::ImprovCommand &command)
Stream * get_hw_serial() const
Definition logger.h:117
uart_port_t get_uart_num() const
Definition logger.h:120
const std::string & get_ssid() const
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:73
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:79
@ UART_SELECTION_USB_CDC
Definition logger.h:76
@ UART_SELECTION_UART0
Definition logger.h:67
@ UART_SELECTION_UART1
Definition logger.h:71
Logger * global_logger
Definition logger.cpp:251
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:15
const char *const TAG
Definition spi.cpp:8
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string to_string(int value)
Definition helpers.cpp:82
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:323
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
Application App
Global storage of Application pointer - only one Application can exist.