ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
serial_proxy.h
Go to the documentation of this file.
1#pragma once
2
3// WARNING: This component is EXPERIMENTAL. The API may change at any time
4// without following the normal breaking changes policy. Use at your own risk.
5// Once the API is considered stable, this warning will be removed.
6
8
9#ifdef USE_SERIAL_PROXY
10
12#include "esphome/core/hal.h"
14
15// Include api_pb2.h only when the API is enabled. The full include is needed
16// to hold SerialProxyDataReceived by value as a pre-allocated member.
17// Guarding prevents pulling conflicting Zephyr logging macro names into
18// translation units that include this header without USE_API defined.
19#ifdef USE_API
21#endif
22
23#ifdef USE_SERIAL_PROXY_USB_IDENTITY
25class USBUartChannel;
26} // namespace esphome::usb_uart
28struct UsbDeviceInfo;
29} // namespace esphome::usb_host
30#endif
31
32#ifdef USE_SERIAL_PROXY_HOST_UART
33namespace esphome::uart {
35} // namespace esphome::uart
36#endif
37
38// Forward-declare types needed outside the USE_API guard.
39namespace esphome::api {
40class APIConnection;
41namespace enums {
45} // namespace enums
46} // namespace esphome::api
47
48namespace esphome::serial_proxy {
49
56
67
69inline constexpr size_t SERIAL_PROXY_MAX_READ_SIZE = 256;
70
75
76#ifdef USE_SERIAL_PROXY_TAP
86 public:
88 virtual void on_device_rx(const uint8_t *data, size_t len) = 0;
89
91 virtual void on_client_tx(const uint8_t *data, size_t len) = 0;
92
96 virtual bool tap_needs_port() const = 0;
97
100 virtual void on_protocol_enabled() = 0;
101
106 virtual void on_protocol_disabled() = 0;
107
111 virtual void on_device_disconnected() = 0;
112};
113#endif
114
115class SerialProxy final : public uart::UARTDevice, public Component {
116 public:
117 void setup() override;
118 void loop() override;
119 void dump_config() override;
120 float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
121
124
126 void set_instance_index(uint32_t index) { this->instance_index_ = index; }
127
129 void set_name(const char *name) { this->name_ = name; }
130
132 const char *get_name() const { return this->name_; }
133
135 void set_port_type(api::enums::SerialProxyPortType port_type) { this->port_type_ = port_type; }
136
139
142
150 SerialProxyResult configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
151 uint8_t stop_bits, uint8_t data_size);
152
155
158
163 void write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len);
164
166 SerialProxyResult set_modem_pins(api::APIConnection *api_connection, uint32_t line_states);
167
169 uint32_t get_modem_pins() const;
170
174 const bool uart_lines = this->with_modem_uart_([](auto *) {});
175 return (this->rts_pin_ != nullptr || uart_lines ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_RTS) : 0u) |
176 (this->dtr_pin_ != nullptr || uart_lines ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_DTR) : 0u);
177 }
178
182
184 void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
185
187 void set_dtr_pin(GPIOPin *pin) { this->dtr_pin_ = pin; }
188
189#ifdef USE_SERIAL_PROXY_USB_IDENTITY
191 void set_usb_channel(usb_uart::USBUartChannel *channel) { this->usb_channel_ = channel; }
192#endif
193
194#ifdef USE_SERIAL_PROXY_HOST_UART
196 void set_host_uart(uart::HostUartComponent *host_uart) { this->host_uart_ = host_uart; }
197#endif
198
199#ifdef USE_API
201 void send_identity(api::APIConnection *api_connection);
202#endif
203
204#ifdef USE_SERIAL_PROXY_TAP
206 void set_tap(SerialProxyTap *tap) { this->tap_ = tap; }
207
212 bool write_from_tap(const uint8_t *data, size_t len) {
213 if (!this->tap_observing_()) {
214 return false;
215 }
216 this->write_array(data, len);
217 return true;
218 }
219
223 bool tap_is_observed() const { return this->tap_observing_(); }
224
228 void tap_request_port() { this->enable_loop(); }
229
232 bool is_device_connected() const { return this->parent_->is_connected(); }
233
238 void tap_pump();
239#endif
240
241 protected:
242#ifdef USE_API
246
250 bool is_subscriber_(api::APIConnection *api_connection) const { return this->api_connection_ == api_connection; }
251#endif
252
255 template<typename F> bool with_modem_uart_(F &&f) const {
256#ifdef USE_SERIAL_PROXY_USB_IDENTITY
257 if (this->usb_channel_ != nullptr) {
258 f(this->usb_channel_);
259 return true;
260 }
261#endif
262#ifdef USE_SERIAL_PROXY_HOST_UART
263 if (this->host_uart_ != nullptr) {
264 f(this->host_uart_);
265 return true;
266 }
267#endif
268 return false;
269 }
270
272 uint32_t wire_time_ms_(size_t bytes) const;
273
274#ifdef USE_SERIAL_PROXY_TAP
276 void reset_mode_();
277#else
280 void reset_mode_() {}
281#endif
282
283#ifdef USE_SERIAL_PROXY_TAP
285 bool tap_observing_() const;
286#endif
287
288#ifdef USE_API
289#ifdef USE_SERIAL_PROXY_USB_IDENTITY
291#else
293#endif
297#endif
298
299#ifdef USE_SERIAL_PROXY_USB_IDENTITY
302 void on_usb_connection_changed_(bool connected);
303#endif
304
307
313
316
317#ifdef USE_API
320#endif
321
323 const char *name_{nullptr};
324
327
328#ifdef USE_SERIAL_PROXY_TAP
331#endif
332
334 GPIOPin *rts_pin_{nullptr};
335 GPIOPin *dtr_pin_{nullptr};
336
338 bool rts_state_{false};
339 bool dtr_state_{false};
340
342 bool trim_warned_{false};
343
344#ifdef USE_SERIAL_PROXY_TAP
346#endif
347
348#ifdef USE_SERIAL_PROXY_USB_IDENTITY
351#endif
352
353#ifdef USE_SERIAL_PROXY_HOST_UART
356#endif
357};
358
359} // namespace esphome::serial_proxy
360
361#endif // USE_SERIAL_PROXY
BedjetMode mode
BedJet operating mode.
void enable_loop()
Enable this component's loop.
Definition component.h:248
uart::HostUartComponent * host_uart_
The host UART behind this port; nullptr on other ports.
void fill_identity_(IdentityScratch &scratch, api::SerialProxyIdentity &msg) const
Fill an identity message for this port.
bool tap_is_observed() const
Whether the tap is currently being served bytes.
float get_setup_priority() const override
void set_tap(SerialProxyTap *tap)
Attach a traffic observer. At most one, set once at setup time.
void set_dtr_pin(GPIOPin *pin)
Set the DTR GPIO pin (from YAML configuration)
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 write_from_tap(const uint8_t *data, size_t len)
Write bytes originating from the tap rather than from a client.
bool trim_warned_
Set while writes are being trimmed, so a client streaming into a slow port warns once.
void set_port_type(api::enums::SerialProxyPortType port_type)
Set the port type (from YAML configuration)
void set_instance_index(uint32_t index)
Set the instance index (called by Application::register_serial_proxy)
uint32_t get_instance_index() const
Get the instance index (position in Application's serial_proxies_ vector)
bool rts_state_
Current modem pin states.
void set_host_uart(uart::HostUartComponent *host_uart)
Attach the host UART behind this port (from code generation)
void set_name(const char *name)
Set the human-readable port name (from YAML configuration)
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.
void set_rts_pin(GPIOPin *pin)
Set the RTS GPIO pin (from YAML configuration)
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 set_usb_channel(usb_uart::USBUartChannel *channel)
Attach the USB UART channel behind this port (from code generation)
api::APIConnection * get_api_connection()
Get the currently subscribed API connection (nullptr if none)
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.
api::enums::SerialProxyPortType get_port_type() const
Get the port type.
bool is_subscriber_(api::APIConnection *api_connection) const
True when the given connection is the live subscriber.
bool is_device_connected() const
Whether the underlying device is present.
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.
const char * get_name() const
Get the human-readable port name.
void tap_request_port()
Resume reading after a tap's needs change.
Observes a port's traffic without owning it, and may inject bytes of its own.
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.
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 type
SerialProxyLineStateFlag
Bit flags for the line_states field exchanged with API clients.
@ 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...
constexpr float AFTER_CONNECTION
For components that should be initialized after a data connection (API/MQTT) is connected.
Definition component.h:57
const void size_t len
Definition hal.h:68
static void uint32_t
watchdog_hw scratch[0]
Identity of a connected USB device, copied out of the descriptors the USB host stack caches for the l...
Definition usb_host.h:128