ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
serial_proxy.cpp
Go to the documentation of this file.
1#include "serial_proxy.h"
2
3#ifdef USE_SERIAL_PROXY
4
6#include "esphome/core/log.h"
7
8#include <algorithm>
9#include <cinttypes>
10#include "esphome/core/util.h"
11
12#ifdef USE_API
15#endif
16
17#ifdef USE_SERIAL_PROXY_USB_IDENTITY
19#endif
20
21#ifdef USE_SERIAL_PROXY_HOST_UART
23#endif
24
26
27ESPHOME_LOG_TAG(TAG, "serial_proxy");
28
31
33 // Set up modem control pins if configured
34 if (this->rts_pin_ != nullptr) {
35 this->rts_pin_->setup();
36 this->rts_pin_->digital_write(this->rts_state_);
37 }
38 if (this->dtr_pin_ != nullptr) {
39 this->dtr_pin_->setup();
40 this->dtr_pin_->digital_write(this->dtr_state_);
41 }
42#ifdef USE_API
43 // instance_index_ is fixed at registration time; pre-set it so loop() only needs to update data
45#endif
46#ifdef USE_SERIAL_PROXY_USB_IDENTITY
47 if (this->usb_channel_ != nullptr) {
48 this->usb_channel_->get_parent()->add_on_connection_callback(
49 [this](bool connected) { this->on_usb_connection_changed_(connected); });
50 }
51#endif
52#ifdef USE_SERIAL_PROXY_TAP
53 // A tap sets itself up before this runs (its setup priority is higher), so it may
54 // already be waiting on the port -- a boot-time handshake with the device, say. Leaving
55 // the loop enabled is what lets that finish; without it the tap would stall until a
56 // client happened to subscribe.
57 if (this->tap_ != nullptr && this->tap_->tap_needs_port()) {
58 return;
59 }
60#endif
61 // No subscriber at startup; disable loop until a client subscribes
62 this->disable_loop();
63}
64
65#ifdef USE_SERIAL_PROXY_TAP
67 // The mode belongs to a session, not to the port. Carrying a departed client's choice
68 // over to the next one would inject protocol bytes into a stream that never asked for
69 // them -- a firmware upload, or any client built before this request existed and so
70 // unable to turn it off. Guessing RAW is the safe direction: a client that wanted
71 // protocol handling and did not ask for it merely sends its own acknowledgements.
73 return;
74 }
75 ESP_LOGD(TAG, "Session ended, returning serial proxy [%" PRIu32 "] to RAW mode", this->instance_index_);
77}
78#endif
79
81#ifdef USE_API
82 // Detect subscriber disconnect
83 if (this->api_connection_ != nullptr && (this->api_connection_->is_marked_for_removal() ||
84 !this->api_connection_->is_connection_setup() || !api_is_connected())) {
85 ESP_LOGW(TAG, "Subscriber disconnected");
86 this->api_connection_ = nullptr;
87 this->reset_mode_();
88 }
89
90 // With no subscriber there is normally nothing to do, but a tap may still need the port
91 // read -- it does its protocol work precisely while nobody else is listening.
92 if (this->api_connection_ == nullptr) [[unlikely]] {
93#ifdef USE_SERIAL_PROXY_TAP
94 if (this->tap_ == nullptr || !this->tap_->tap_needs_port()) {
95 this->disable_loop();
96 return;
97 }
98#else
99 this->disable_loop();
100 return;
101#endif
102 }
103
104 // Read available data from UART and forward to subscribed client
105 size_t available = this->available();
106 if (available == 0)
107 return;
108
109 this->read_and_send_(available);
110#endif
111}
112
113#ifdef USE_API
114void __attribute__((noinline)) SerialProxy::read_and_send_(size_t available) {
115 // Read in chunks up to SERIAL_PROXY_MAX_READ_SIZE
117 size_t to_read = std::min(available, sizeof(buffer));
118
119 if (!this->read_array(buffer, to_read))
120 return;
121
122#ifdef USE_SERIAL_PROXY_TAP
123 // Before forwarding, so a tap that answers the device (an acknowledgement, say) is not
124 // waiting on the network round trip to a subscriber that may not even exist.
125 if (this->tap_observing_()) {
126 this->tap_->on_device_rx(buffer, to_read);
127 }
128#endif
129
130 if (this->api_connection_ == nullptr) {
131 return;
132 }
133 this->outgoing_msg_.set_data(buffer, to_read);
135}
136#endif
137
138#ifdef USE_SERIAL_PROXY_TAP
139
141 if (this->tap_ == nullptr) {
142 return false;
143 }
144 // With no subscriber, a tap doing its own protocol work (the boot-time handshake with
145 // the device, say) is served regardless of mode -- nobody has chosen one yet. Once a
146 // subscriber holds the port, the mode alone decides, so RAW stays inert.
147 if (this->api_connection_ == nullptr && this->tap_->tap_needs_port()) {
148 return true;
149 }
150 // Otherwise the mode decides. RAW must be inert: a client that flips to RAW before
151 // flashing firmware is entitled to a byte pipe with nothing injecting protocol bytes
152 // into it, and "the tap turned out not to recognise the stream" is not good enough.
154}
155
157#ifdef USE_API
158 // Nothing would consume the bytes; leave them in the FIFO
159 if (!this->tap_observing_() && this->api_connection_ == nullptr) {
160 return;
161 }
162 const size_t available = this->available();
163 if (available > 0) {
164 this->read_and_send_(available);
165 }
166#endif
167}
168#endif
169
171 ESP_LOGCONFIG(TAG,
172 "Serial Proxy [%" PRIu32 "]:\n"
173 " Name: %s\n"
174 " Port Type: %s\n"
175 " RTS Pin: %s\n"
176 " DTR Pin: %s",
177 this->instance_index_, this->name_ != nullptr ? this->name_ : "",
178 this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS485 ? LOG_STR_LITERAL("RS485")
179 : this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS232 ? LOG_STR_LITERAL("RS232")
180 : this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_USB_SERIAL ? LOG_STR_LITERAL("USB_SERIAL")
181 : LOG_STR_LITERAL("TTL"),
182 this->rts_pin_ != nullptr ? LOG_STR_LITERAL("configured") : LOG_STR_LITERAL("not configured"),
183 this->dtr_pin_ != nullptr ? LOG_STR_LITERAL("configured") : LOG_STR_LITERAL("not configured"));
184}
185
186SerialProxyResult SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control,
187 uint8_t parity, uint8_t stop_bits, uint8_t data_size) {
188#ifdef USE_API
189 if (!this->is_subscriber_(api_connection)) {
190 ESP_LOGW(TAG, "Ignoring configure request from client without port subscription [%" PRIu32 "]",
191 this->instance_index_);
193 }
194#endif
195 ESP_LOGD(TAG,
196 "Configuring serial proxy [%" PRIu32 "]: baud=%" PRIu32 ", flow_ctrl=%s, parity=%" PRIu8 ", stop=%" PRIu8
197 ", data=%" PRIu8,
198 this->instance_index_, baudrate, YESNO(flow_control), parity, stop_bits, data_size);
199
200 auto *uart_comp = this->parent_;
201 if (uart_comp == nullptr) {
202 ESP_LOGE(TAG, "UART component not available");
204 }
205
206 // Validate all parameters before applying any (values come from a remote client)
207 if (baudrate == 0) {
208 ESP_LOGW(TAG, "Invalid baud rate: 0");
210 }
212 ESP_LOGW(TAG, "Invalid stop bits: %u (must be 1 or 2)", stop_bits);
214 }
215 if (data_size < 5 || data_size > 8) {
216 ESP_LOGW(TAG, "Invalid data bits: %u (must be 5-8)", data_size);
218 }
219 if (parity > 2) {
220 ESP_LOGW(TAG, "Invalid parity: %u (must be 0-2)", parity);
222 }
223 if (flow_control) {
224 ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
226 }
227
228 // Skip a no-op reconfigure. Clients routinely re-send identical settings on every
229 // port open, and on a USB UART each apply is a CDC SET_LINE_CODING control transfer.
230 // Some bridges watch line-coding changes as a signalling channel (a magic baud
231 // sequence to enter a bootloader, say), so redundant applies are not harmless.
232 static const uart::UARTParityOptions PARITY_MAP[] = {
236 };
237 if (uart_comp->get_baud_rate() == baudrate && uart_comp->get_stop_bits() == stop_bits &&
238 uart_comp->get_data_bits() == data_size && uart_comp->get_parity() == PARITY_MAP[parity]) {
239 ESP_LOGV(TAG, "Settings unchanged, skipping reconfigure [%" PRIu32 "]", this->instance_index_);
241 }
242
243 // Apply validated parameters
244 uart_comp->set_baud_rate(baudrate);
245 uart_comp->set_stop_bits(stop_bits);
246 uart_comp->set_data_bits(data_size);
247
248 uart_comp->set_parity(PARITY_MAP[parity]);
249
250 // load_settings() is available on ESP8266 and ESP32 platforms
251#if defined(USE_ESP8266) || defined(USE_ESP32)
252 uart_comp->load_settings(true);
253#endif
255}
256
259#ifdef USE_API
260 // Only the live subscriber may change the mode, so the mode cannot outlive a session
261 if (!this->is_subscriber_(api_connection)) {
262 ESP_LOGW(TAG, "Ignoring mode request from client without port subscription [%" PRIu32 "]", this->instance_index_);
264 }
265#endif
266 // Values come from a remote client
268 ESP_LOGW(TAG, "Invalid mode: %" PRIu32, static_cast<uint32_t>(mode));
270 }
271 // PROTOCOL on a port with no tap would be a silent no-op; refuse so the client knows
272#ifdef USE_SERIAL_PROXY_TAP
273 const bool has_tap = this->tap_ != nullptr;
274#else
275 const bool has_tap = false;
276#endif
277 if (mode == api::enums::SERIAL_PROXY_MODE_PROTOCOL && !has_tap) {
278 ESP_LOGW(TAG, "No tap on serial proxy [%" PRIu32 "]; PROTOCOL mode unavailable", this->instance_index_);
280 }
281 ESP_LOGD(TAG, "Serial proxy [%" PRIu32 "] mode set to %s", this->instance_index_,
282 mode == api::enums::SERIAL_PROXY_MODE_PROTOCOL ? LOG_STR_LITERAL("PROTOCOL") : LOG_STR_LITERAL("RAW"));
283#ifdef USE_SERIAL_PROXY_TAP
284 const bool entering_protocol_mode =
286 const bool leaving_protocol_mode =
288 this->mode_ = mode;
289
290 if (entering_protocol_mode && this->tap_ != nullptr) {
291 this->tap_->on_protocol_enabled();
292 }
293
294 // Only for an explicit client request, not for reset_mode_() at the end of a session:
295 // an ordinary disconnect says nothing about the device, whereas a client deliberately
296 // asking for raw bytes usually precedes changing what the device is.
297 if (leaving_protocol_mode && this->tap_ != nullptr) {
298 this->tap_->on_protocol_disabled();
299 }
300#endif
302}
303
304void SerialProxy::write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len) {
305#ifdef USE_API
306 // Bytes from anyone but the live subscriber would interleave with the subscriber's
307 // traffic -- or with an active tap's -- on the wire
308 if (!this->is_subscriber_(api_connection)) {
309 if (this->api_connection_ != nullptr) {
310 ESP_LOGW(TAG, "Ignoring write from client that does not hold serial proxy [%" PRIu32 "]", this->instance_index_);
311 } else {
312 // A legacy client streaming writes without subscribing would flood WARN, one per
313 // request; writes are the only high-rate, unacknowledged operation, so keep this
314 // visible without drowning the log
315 ESP_LOGV(TAG, "Ignoring write from client without port subscription [%" PRIu32 "]", this->instance_index_);
316 }
317 return;
318 }
319#endif
320 if (data == nullptr || len == 0)
321 return;
322 // Whatever the driver cannot buffer stalls the main loop for its wire time. At high baud
323 // rates that is brief and losing nothing is worth it; at low ones it would trip the
324 // watchdog, so cap the stall and drop the rest. The cap covers the whole loop pass:
325 // several writes can arrive in one, and each alone might stay under it.
326 const size_t free = this->parent_->available_for_write();
327 bool trimmed = false;
328 if (len > free) {
329 const uint32_t loop_time = App.get_loop_component_start_time();
330 if (loop_time != stall_loop_time) {
331 stall_loop_time = loop_time;
332 stall_spent_ms = 0;
333 }
334 const uint32_t stall_ms = this->wire_time_ms_(len - free);
335 trimmed = stall_spent_ms + stall_ms > SERIAL_PROXY_MAX_WRITE_STALL_MS;
336 if (trimmed && !this->trim_warned_) {
337 ESP_LOGW(TAG,
338 "TX buffer full on serial proxy [%" PRIu32 "]: dropping %zu of %zu bytes (would stall %" PRIu32
339 " ms at %" PRIu32 " baud); raise the UART tx_buffer_size or pace writes",
340 this->instance_index_, len - free, len, stall_ms, this->parent_->get_baud_rate());
341 }
342 if (trimmed) {
343 len = free;
344 } else {
345 stall_spent_ms += stall_ms;
346 }
347 }
348 this->trim_warned_ = trimmed;
349 if (len == 0)
350 return;
351 this->write_array(data, len);
352
353#ifdef USE_SERIAL_PROXY_TAP
354 // After the write, so the tap observes the same ordering the device does
355 if (this->tap_observing_()) {
356 this->tap_->on_client_tx(data, len);
357 }
358#endif
359}
360
362 const uint32_t bits_per_byte = 1 + this->parent_->get_data_bits() + this->parent_->get_stop_bits() +
364 return static_cast<uint64_t>(bytes) * bits_per_byte * 1000 / std::max<uint32_t>(this->parent_->get_baud_rate(), 1);
365}
366
368#ifdef USE_API
369 if (!this->is_subscriber_(api_connection)) {
370 ESP_LOGW(TAG, "Ignoring modem pin request from client without port subscription [%" PRIu32 "]",
371 this->instance_index_);
373 }
374#endif
375 // Asserting a pin that is not configured must fail so the client learns the signal never
376 // reached the wire; deasserting an absent pin is harmless and stays allowed. Clients can
377 // avoid this by masking against SerialProxyInfo.configured_line_states.
378 if ((line_states & ~this->get_configured_modem_pins()) != 0) {
379 ESP_LOGW(TAG, "Requested modem pin not configured on serial proxy [%" PRIu32 "]", this->instance_index_);
381 }
382 const bool rts = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_RTS) != 0;
383 const bool dtr = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_DTR) != 0;
384 ESP_LOGV(TAG, "Setting modem pins [%" PRIu32 "]: RTS=%s, DTR=%s", this->instance_index_, ONOFF(rts), ONOFF(dtr));
385
386 // The UART goes first, so a failure leaves every line as it was. A line with a GPIO pin keeps
387 // the UART's current state; with both on GPIO there is nothing to send
388 bool applied = true;
389 if (this->rts_pin_ == nullptr || this->dtr_pin_ == nullptr) {
390 this->with_modem_uart_([this, dtr, rts, &applied](auto *uart) {
391 applied = uart->set_modem_control(this->dtr_pin_ != nullptr ? uart->get_dtr() : dtr,
392 this->rts_pin_ != nullptr ? uart->get_rts() : rts);
393 });
394 }
395 if (!applied) {
396 ESP_LOGW(TAG, "Modem pins did not reach the UART on serial proxy [%" PRIu32 "]", this->instance_index_);
398 }
399 if (this->rts_pin_ != nullptr) {
400 this->rts_state_ = rts;
401 this->rts_pin_->digital_write(rts);
402 }
403 if (this->dtr_pin_ != nullptr) {
404 this->dtr_state_ = dtr;
405 this->dtr_pin_->digital_write(dtr);
406 }
408}
409
410#ifdef USE_API
414 this->fill_identity_(scratch, msg);
415 api_connection->send_serial_proxy_identity(msg);
416}
417
419 msg.instance = this->instance_index_;
420#ifdef USE_SERIAL_PROXY_USB_IDENTITY
421 // The define is global, so a hardware UART port in the same config also gets here
422 if (this->usb_channel_ != nullptr) {
424 // Covers a removed device, a channel the device has no CDC function for and a failed channel setup
425 if (!this->usb_channel_->is_connected()) {
426 return;
427 }
428 auto *client = this->usb_channel_->get_parent();
430 if (!client->get_device_info(scratch)) {
432 return;
433 }
434 msg.usb.vendor_id = scratch.vendor_id;
435 msg.usb.product_id = scratch.product_id;
436 msg.usb.bcd_device = scratch.bcd_device;
440 msg.manufacturer = StringRef(scratch.manufacturer);
441 msg.product = StringRef(scratch.product);
442 msg.serial_number = StringRef(scratch.serial_number);
443 return;
444 }
445#endif
446 // Zero-initialized message: source NONE, no flags
447}
448#endif
449
450#ifdef USE_SERIAL_PROXY_USB_IDENTITY
452 ESP_LOGD(TAG, "USB device %s serial proxy [%" PRIu32 "]",
453 connected ? LOG_STR_LITERAL("attached to") : LOG_STR_LITERAL("removed from"), this->instance_index_);
454#ifdef USE_SERIAL_PROXY_TAP
455 // Before telling clients, so a tap never acknowledges a frame from the old device after
456 // a client has been told it is gone. The subscriber and the mode stay: both belong to
457 // the client's session, and only the client knows whether that session is over.
458 if (!connected && this->tap_ != nullptr) {
460 }
461#endif
462#ifdef USE_API
463 if (api::global_api_server == nullptr) {
464 return;
465 }
468 this->fill_identity_(scratch, msg);
470#endif
471}
472#endif
473
475 bool rts = this->rts_state_;
476 bool dtr = this->dtr_state_;
477 this->with_modem_uart_([this, &rts, &dtr](auto *uart) {
478 if (this->rts_pin_ == nullptr)
479 rts = uart->get_rts();
480 if (this->dtr_pin_ == nullptr)
481 dtr = uart->get_dtr();
482 });
483 return (rts ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_RTS) : 0u) |
484 (dtr ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_DTR) : 0u);
485}
486
488#ifdef USE_API
489 // Flushing stalls the port, so it gets the same ownership check as writes
490 if (!this->is_subscriber_(api_connection)) {
491 ESP_LOGW(TAG, "Ignoring flush from client without port subscription [%" PRIu32 "]", this->instance_index_);
493 }
494#endif
495 ESP_LOGV(TAG, "Flushing serial proxy [%" PRIu32 "]", this->instance_index_);
496 switch (this->flush()) {
505 }
506 return SerialProxyResult::SERIAL_PROXY_RESULT_ERROR; // Unreachable; all enum values handled above
507}
508
509#ifdef USE_API
512 switch (type) {
514 if (this->api_connection_ == api_connection) {
515 ESP_LOGV(TAG, "API connection is already subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
517 }
518 if (this->api_connection_ != nullptr) {
519 // A living subscriber keeps exclusive access. Its connection may be dead without
520 // loop() having noticed yet (e.g. the client crashed and reconnected quickly);
521 // in that case let the new client take over instead of locking it out.
523 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
525 }
526 ESP_LOGW(TAG, "Previous subscriber disconnected; taking over subscription");
527 // End the dead client's session before starting the new one, so its mode
528 // cannot leak into a session that never asked for it
529 this->api_connection_ = nullptr;
530 this->reset_mode_();
531 }
532 this->api_connection_ = api_connection;
533 this->enable_loop();
534 ESP_LOGV(TAG, "API connection subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
537 // Unsubscribe is idempotent: not being subscribed is not an error
538 if (this->api_connection_ != api_connection) {
539 ESP_LOGV(TAG, "API connection is not subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
541 }
542 this->api_connection_ = nullptr;
543 this->reset_mode_();
544#ifdef USE_SERIAL_PROXY_TAP
545 // Keep the loop alive for a tap that still needs the port (mirrors loop())
546 if (this->tap_ == nullptr || !this->tap_->tap_needs_port()) {
547 this->disable_loop();
548 }
549#else
550 this->disable_loop();
551#endif
552 ESP_LOGV(TAG, "API connection unsubscribed from serial proxy [%" PRIu32 "]", this->instance_index_);
554 default:
555 ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(type));
557 }
558}
559#endif
560
561} // namespace esphome::serial_proxy
562
563#endif // USE_SERIAL_PROXY
BedjetMode mode
BedJet operating mode.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
virtual void setup()=0
virtual void digital_write(bool value)=0
T * get_parent() const
Get the parent of this object.
Definition helpers.h:2003
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
void send_serial_proxy_data(const SerialProxyDataReceived &msg)
void send_serial_proxy_identity(const SerialProxyIdentity &msg)
Send a port identity to this client.
void send_serial_proxy_identity(const SerialProxyIdentity &msg)
Tell every subscribed client that a serial proxy port's identity changed.
void set_data(const uint8_t *data, size_t len)
Definition api_pb2.h:3925
enums::SerialProxyIdentitySource source
Definition api_pb2.h:4109
void fill_identity_(IdentityScratch &scratch, api::SerialProxyIdentity &msg) const
Fill an identity message for this port.
SerialProxyResult configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits, uint8_t data_size)
Configure UART parameters and apply them.
void read_and_send_(size_t available)
Read from UART, hand the bytes to any tap, and forward them to a subscriber (slow path with a 256-byt...
api::enums::SerialProxyMode mode_
How the bytes passing through are treated; zero is SERIAL_PROXY_MODE_RAW.
SerialProxyResult serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type)
Handle a subscribe/unsubscribe request from an API client.
uint32_t get_modem_pins() const
Get current modem pin states as a bitmask of SerialProxyLineStateFlag values.
uint32_t instance_index_
Instance index for identifying this proxy in API messages.
bool trim_warned_
Set while writes are being trimmed, so a client streaming into a slow port warns once.
bool rts_state_
Current modem pin states.
SerialProxyResult flush_port(api::APIConnection *api_connection)
Flush the serial port (block until all TX data is sent)
uint32_t get_configured_modem_pins() const
Get the modem pins this instance can drive as a bitmask of SerialProxyLineStateFlag values.
api::SerialProxyDataReceived outgoing_msg_
Pre-allocated outgoing message; instance field is set once in setup()
const char * name_
Human-readable port name (points to a string literal in flash)
api::enums::SerialProxyPortType port_type_
Port type.
GPIOPin * rts_pin_
Optional GPIO pins for modem control.
usb_uart::USBUartChannel * usb_channel_
The USB UART channel behind this port; nullptr on non-USB ports.
void on_usb_connection_changed_(bool connected)
The USB device behind this port was attached or removed; report the port's new identity to every subs...
bool tap_observing_() const
True when the tap should be shown the traffic passing through this port.
SerialProxyResult set_modem_pins(api::APIConnection *api_connection, uint32_t line_states)
Set modem pin states from a bitmask of SerialProxyLineStateFlag values.
SerialProxyResult set_mode_from_client(api::APIConnection *api_connection, api::enums::SerialProxyMode mode)
Handle a mode change requested by an API client.
void write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len)
Write data received from an API client to the serial device.
static uint32_t stall_loop_time
Stall spent by writes in the current loop pass, keyed by the pass's cached start time.
bool is_subscriber_(api::APIConnection *api_connection) const
True when the given connection is the live subscriber.
void tap_pump()
Run one read-and-dispatch cycle immediately.
void send_identity(api::APIConnection *api_connection)
Send this port's identity to one client.
void reset_mode_()
Return the port to RAW when a subscriber goes away, so the mode never outlives it.
api::APIConnection * api_connection_
Subscribed API client (only one allowed at a time)
uint32_t wire_time_ms_(size_t bytes) const
Time the wire needs for the given number of bytes at the current framing.
bool with_modem_uart_(F &&f) const
Call f with the UART behind this port if that UART drives the modem lines itself.
virtual void on_device_disconnected()=0
The device behind the port went away – unplugged, or its power was cut.
virtual void on_protocol_enabled()=0
A client turned protocol handling on for this port.
virtual bool tap_needs_port() const =0
True when the port must keep reading even with no subscriber attached, so a tap can do its own protoc...
virtual void on_device_rx(const uint8_t *data, size_t len)=0
Bytes read from the device, before they are forwarded to any subscriber.
virtual void on_client_tx(const uint8_t *data, size_t len)=0
Bytes a subscriber sent towards the device, after they have been written.
virtual void on_protocol_disabled()=0
A client explicitly turned protocol handling off for this port.
UARTParityOptions get_parity() const
void set_baud_rate(uint32_t baud_rate)
virtual size_t available_for_write()
UARTFlushResult flush()
Definition uart.h:51
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:40
UARTComponent * parent_
Definition uart.h:77
void write_array(const uint8_t *data, size_t len)
Definition uart.h:27
uint8_t UARTParityOptions parity
Definition uart.h:74
uint8_t get_interface_class() const
bInterfaceClass of the interface get_interface_number() names
Definition usb_uart.h:201
uint8_t get_port_number() const
Index of this port among the ports on its interface.
Definition usb_uart.h:208
uint8_t get_interface_number() const
USB interface number a host driver binds to for this channel: the communication interface of a CDC AC...
Definition usb_uart.h:195
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:45
uint8_t type
uint32_t buffer
Definition gif.cpp:50
@ SERIAL_PROXY_PORT_TYPE_RS232
Definition api_pb2.h:24
@ SERIAL_PROXY_PORT_TYPE_RS485
Definition api_pb2.h:25
@ SERIAL_PROXY_PORT_TYPE_USB_SERIAL
Definition api_pb2.h:26
@ SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE
Definition api_pb2.h:356
@ SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE
Definition api_pb2.h:355
@ SERIAL_PROXY_IDENTITY_FLAG_CONNECTED
Definition api_pb2.h:383
@ SERIAL_PROXY_IDENTITY_FLAG_ERROR
Definition api_pb2.h:384
@ SERIAL_PROXY_IDENTITY_SOURCE_USB
Definition api_pb2.h:378
APIServer * global_api_server
@ SERIAL_PROXY_LINE_STATE_FLAG_RTS
RTS (Request To Send)
@ SERIAL_PROXY_LINE_STATE_FLAG_DTR
DTR (Data Terminal Ready)
SerialProxyResult
Result of a client-initiated operation; mapped to api::enums::SerialProxyStatus by the API layer.
@ SERIAL_PROXY_RESULT_TIMEOUT
Timed out before TX completed.
@ SERIAL_PROXY_RESULT_ERROR
Driver or hardware error.
@ SERIAL_PROXY_RESULT_NOT_SUPPORTED
Requested feature is not available on this instance.
@ SERIAL_PROXY_RESULT_PORT_IN_USE
Denied: another live client holds the port.
@ SERIAL_PROXY_RESULT_OK
Operation completed or request accepted.
@ SERIAL_PROXY_RESULT_INVALID_ARGUMENT
A parameter value is out of range.
@ SERIAL_PROXY_RESULT_ASSUMED_SUCCESS
Platform cannot confirm TX drain; success assumed.
constexpr size_t SERIAL_PROXY_MAX_READ_SIZE
Maximum bytes to read from UART in a single loop iteration.
constexpr uint32_t SERIAL_PROXY_MAX_WRITE_STALL_MS
Longest main-loop stall client writes may cause per loop pass, shared by every instance; bytes the UA...
ESPHOME_LOG_TAG(TAG, "serial_proxy")
@ UART_FLUSH_RESULT_ASSUMED_SUCCESS
Platform cannot report result; success is assumed.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_FAILED
Confirmed: driver or hardware error.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
const void size_t len
Definition hal.h:68
ESPHOME_ALWAYS_INLINE bool api_is_connected()
Return whether the node has at least one client connected to the native API.
Definition util.h:20
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
watchdog_hw scratch[0]