ESPHome 2026.3.0
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
5#include "esphome/core/log.h"
6#include "esphome/core/util.h"
7
8#ifdef USE_API
11#endif
12
14
15static const char *const TAG = "serial_proxy";
16
18 // Set up modem control pins if configured
19 if (this->rts_pin_ != nullptr) {
20 this->rts_pin_->setup();
21 this->rts_pin_->digital_write(this->rts_state_);
22 }
23 if (this->dtr_pin_ != nullptr) {
24 this->dtr_pin_->setup();
25 this->dtr_pin_->digital_write(this->dtr_state_);
26 }
27#ifdef USE_API
28 // instance_index_ is fixed at registration time; pre-set it so loop() only needs to update data
30#endif
31 // No subscriber at startup; disable loop until a client subscribes
32 this->disable_loop();
33}
34
36#ifdef USE_API
37 // Safety check — loop should only run when subscribed, but guard against races
38 if (this->api_connection_ == nullptr) [[unlikely]] {
39 this->disable_loop();
40 return;
41 }
42
43 // Detect subscriber disconnect
44 if (this->api_connection_->is_marked_for_removal() || !this->api_connection_->is_connection_setup() ||
46 ESP_LOGW(TAG, "Subscriber disconnected");
47 this->api_connection_ = nullptr;
48 this->disable_loop();
49 return;
50 }
51
52 // Read available data from UART and forward to subscribed client
53 size_t available = this->available();
54 if (available == 0)
55 return;
56
57 this->read_and_send_(available);
58#endif
59}
60
61#ifdef USE_API
62void __attribute__((noinline)) SerialProxy::read_and_send_(size_t available) {
63 // Read in chunks up to SERIAL_PROXY_MAX_READ_SIZE
64 uint8_t buffer[SERIAL_PROXY_MAX_READ_SIZE];
65 size_t to_read = std::min(available, sizeof(buffer));
66
67 if (!this->read_array(buffer, to_read))
68 return;
69
70 this->outgoing_msg_.set_data(buffer, to_read);
72}
73#endif
74
76 ESP_LOGCONFIG(TAG,
77 "Serial Proxy [%u]:\n"
78 " Name: %s\n"
79 " Port Type: %s\n"
80 " RTS Pin: %s\n"
81 " DTR Pin: %s",
82 this->instance_index_, this->name_ != nullptr ? this->name_ : "",
85 : "TTL",
86 this->rts_pin_ != nullptr ? "configured" : "not configured",
87 this->dtr_pin_ != nullptr ? "configured" : "not configured");
88}
89
90void SerialProxy::configure(uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits,
91 uint8_t data_size) {
92 ESP_LOGD(TAG, "Configuring serial proxy [%u]: baud=%u, flow_ctrl=%s, parity=%u, stop=%u, data=%u",
93 this->instance_index_, baudrate, YESNO(flow_control), parity, stop_bits, data_size);
94
95 auto *uart_comp = this->parent_;
96 if (uart_comp == nullptr) {
97 ESP_LOGE(TAG, "UART component not available");
98 return;
99 }
100
101 // Validate all parameters before applying any (values come from a remote client)
102 if (baudrate == 0) {
103 ESP_LOGW(TAG, "Invalid baud rate: 0");
104 return;
105 }
106 if (stop_bits < 1 || stop_bits > 2) {
107 ESP_LOGW(TAG, "Invalid stop bits: %u (must be 1 or 2)", stop_bits);
108 return;
109 }
110 if (data_size < 5 || data_size > 8) {
111 ESP_LOGW(TAG, "Invalid data bits: %u (must be 5-8)", data_size);
112 return;
113 }
114 if (parity > 2) {
115 ESP_LOGW(TAG, "Invalid parity: %u (must be 0-2)", parity);
116 return;
117 }
118
119 // Apply validated parameters
120 uart_comp->set_baud_rate(baudrate);
121 uart_comp->set_stop_bits(stop_bits);
122 uart_comp->set_data_bits(data_size);
123
124 // Map parity value to UARTParityOptions
125 static const uart::UARTParityOptions PARITY_MAP[] = {
129 };
130 uart_comp->set_parity(PARITY_MAP[parity]);
131
132 // load_settings() is available on ESP8266 and ESP32 platforms
133#if defined(USE_ESP8266) || defined(USE_ESP32)
134 uart_comp->load_settings(true);
135#endif
136
137 if (flow_control) {
138 ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
139 }
140}
141
142void SerialProxy::write_from_client(const uint8_t *data, size_t len) {
143 if (data == nullptr || len == 0)
144 return;
145 this->write_array(data, len);
146}
147
149 const bool rts = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_RTS) != 0;
150 const bool dtr = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_DTR) != 0;
151 ESP_LOGV(TAG, "Setting modem pins [%u]: RTS=%s, DTR=%s", this->instance_index_, ONOFF(rts), ONOFF(dtr));
152
153 if (this->rts_pin_ != nullptr) {
154 this->rts_state_ = rts;
155 this->rts_pin_->digital_write(rts);
156 }
157 if (this->dtr_pin_ != nullptr) {
158 this->dtr_state_ = dtr;
159 this->dtr_pin_->digital_write(dtr);
160 }
161}
162
167
169 ESP_LOGV(TAG, "Flushing serial proxy [%u]", this->instance_index_);
170 return this->flush();
171}
172
173#ifdef USE_API
175 switch (type) {
177 if (this->api_connection_ != nullptr) {
178 ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
179 return;
180 }
181 this->api_connection_ = api_connection;
182 this->enable_loop();
183 ESP_LOGV(TAG, "API connection subscribed to serial proxy [%u]", this->instance_index_);
184 break;
186 if (this->api_connection_ != api_connection) {
187 ESP_LOGV(TAG, "API connection is not subscribed to serial proxy [%u]", this->instance_index_);
188 return;
189 }
190 this->api_connection_ = nullptr;
191 this->disable_loop();
192 ESP_LOGV(TAG, "API connection unsubscribed from serial proxy [%u]", this->instance_index_);
193 break;
194 default:
195 ESP_LOGW(TAG, "Unknown serial proxy request type: %u", static_cast<uint32_t>(type));
196 break;
197 }
198}
199#endif
200
201} // namespace esphome::serial_proxy
202
203#endif // USE_SERIAL_PROXY
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
virtual void setup()=0
virtual void digital_write(bool value)=0
void send_serial_proxy_data(const SerialProxyDataReceived &msg)
void set_data(const uint8_t *data, size_t len)
Definition api_pb2.h:3136
void read_and_send_(size_t available)
Read from UART and send to API client (slow path with 256-byte stack buffer)
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 rts_state_
Current modem pin states.
void configure(uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits, uint8_t data_size)
Configure UART parameters and apply them.
void serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type)
Handle a subscribe/unsubscribe request from an API client.
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_modem_pins(uint32_t line_states)
Set modem pin states from a bitmask of SerialProxyLineStateFlag values.
void write_from_client(const uint8_t *data, size_t len)
Write data received from an API client to the serial device.
uart::FlushResult flush_port()
Flush the serial port (block until all TX data is sent)
api::APIConnection * api_connection_
Subscribed API client (only one allowed at a time)
void set_baud_rate(uint32_t baud_rate)
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
UARTComponent * parent_
Definition uart.h:73
FlushResult flush()
Definition uart.h:48
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
struct @65::@66 __attribute__
uint16_t type
@ SERIAL_PROXY_PORT_TYPE_RS232
Definition api_pb2.h:16
@ SERIAL_PROXY_PORT_TYPE_RS485
Definition api_pb2.h:17
@ SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE
Definition api_pb2.h:333
@ SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE
Definition api_pb2.h:332
@ SERIAL_PROXY_LINE_STATE_FLAG_RTS
RTS (Request To Send)
@ SERIAL_PROXY_LINE_STATE_FLAG_DTR
DTR (Data Terminal Ready)
constexpr size_t SERIAL_PROXY_MAX_READ_SIZE
Maximum bytes to read from UART in a single loop iteration.
FlushResult
Result of a flush() call.
bool api_is_connected()
Return whether the node has at least one client connected to the native API.
Definition util.cpp:17
std::string size_t len
Definition helpers.h:892
static void uint32_t