Compare commits

..

2 Commits

Author SHA1 Message Date
kbx81
686f59eb48 CODEOWNERS 2026-02-11 19:16:32 -06:00
kbx81
587ea23864 [serial_proxy] New component 2026-02-11 19:12:18 -06:00
31 changed files with 932 additions and 93 deletions

View File

@@ -1 +1 @@
ce05c28e9dc0b12c4f6e7454986ffea5123ac974a949da841be698c535f2083e
74867fc82764102ce1275ea2bc43e3aeee7619679537c6db61114a33342bb4c7

View File

@@ -115,7 +115,6 @@ jobs:
python-version:
- "3.11"
- "3.13"
- "3.14"
os:
- ubuntu-latest
- macOS-latest

View File

@@ -429,6 +429,7 @@ esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras
esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/serial_proxy/* @kbx81
esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @martgras @SenexCrenshaw

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.2.0b1
PROJECT_NUMBER = 2026.2.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -69,6 +69,12 @@ service APIConnection {
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
rpc infrared_rf_transmit_raw_timings(InfraredRFTransmitRawTimingsRequest) returns (void) {}
rpc serial_proxy_configure(SerialProxyConfigureRequest) returns (void) {}
rpc serial_proxy_write(SerialProxyWriteRequest) returns (void) {}
rpc serial_proxy_set_modem_pins(SerialProxySetModemPinsRequest) returns (void) {}
rpc serial_proxy_get_modem_pins(SerialProxyGetModemPinsRequest) returns (void) {}
rpc serial_proxy_flush(SerialProxyFlushRequest) returns (void) {}
}
@@ -260,6 +266,9 @@ message DeviceInfoResponse {
// Indicates if Z-Wave proxy support is available and features supported
uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
// Number of serial proxy instances available on the device
uint32 serial_proxy_count = 25 [(field_ifdef) = "USE_SERIAL_PROXY"];
}
message ListEntitiesRequest {
@@ -2488,3 +2497,87 @@ message InfraredRFReceiveEvent {
fixed32 key = 2; // Key identifying the receiver instance
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
}
// ==================== SERIAL PROXY ====================
enum SerialProxyParity {
SERIAL_PROXY_PARITY_NONE = 0;
SERIAL_PROXY_PARITY_EVEN = 1;
SERIAL_PROXY_PARITY_ODD = 2;
}
// Configure UART parameters for a serial proxy instance
message SerialProxyConfigureRequest {
option (id) = 138;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1; // Instance index (0-based)
uint32 baudrate = 2; // Baud rate in bits per second
bool flow_control = 3; // Enable hardware flow control
SerialProxyParity parity = 4; // Parity setting
uint32 stop_bits = 5; // Number of stop bits (1 or 2)
uint32 data_size = 6; // Number of data bits (5-8)
}
// Data received from a serial device, forwarded to clients
message SerialProxyDataReceived {
option (id) = 139;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SERIAL_PROXY";
option (no_delay) = true;
uint32 instance = 1; // Instance index (0-based)
bytes data = 2; // Raw data received from the serial device
}
// Write data to a serial device
message SerialProxyWriteRequest {
option (id) = 140;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
option (no_delay) = true;
uint32 instance = 1; // Instance index (0-based)
bytes data = 2; // Raw data to write to the serial device
}
// Set modem control pin states (RTS and DTR)
message SerialProxySetModemPinsRequest {
option (id) = 141;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1; // Instance index (0-based)
bool rts = 2; // Desired RTS pin state
bool dtr = 3; // Desired DTR pin state
}
// Request current modem control pin states
message SerialProxyGetModemPinsRequest {
option (id) = 142;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1; // Instance index (0-based)
}
// Response with current modem control pin states
message SerialProxyGetModemPinsResponse {
option (id) = 143;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1; // Instance index (0-based)
bool rts = 2; // Current RTS pin state
bool dtr = 3; // Current DTR pin state
}
// Flush the serial port (block until all TX data is sent)
message SerialProxyFlushRequest {
option (id) = 144;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1; // Instance index (0-based)
}

View File

@@ -1413,6 +1413,66 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
}
#endif
#ifdef USE_SERIAL_PROXY
void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range (max %u)", msg.instance,
static_cast<uint32_t>(proxies.size()));
return;
}
proxies[msg.instance]->configure(msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity), msg.stop_bits,
msg.data_size);
}
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
return;
}
proxies[msg.instance]->write(msg.data, msg.data_len);
}
void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
return;
}
proxies[msg.instance]->set_modem_pins(msg.rts, msg.dtr);
}
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
return;
}
bool rts, dtr;
proxies[msg.instance]->get_modem_pins(rts, dtr);
SerialProxyGetModemPinsResponse resp{};
resp.instance = msg.instance;
resp.rts = rts;
resp.dtr = dtr;
this->send_message(resp, SerialProxyGetModemPinsResponse::MESSAGE_TYPE);
}
void APIConnection::on_serial_proxy_flush_request(const SerialProxyFlushRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
return;
}
proxies[msg.instance]->flush_port();
}
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) {
this->send_message(msg, SerialProxyDataReceived::MESSAGE_TYPE);
}
#endif
#ifdef USE_INFRARED
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
auto *infrared = static_cast<infrared::Infrared *>(entity);
@@ -1627,6 +1687,9 @@ bool APIConnection::send_device_info_response_() {
resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
#endif
#ifdef USE_SERIAL_PROXY
resp.serial_proxy_count = App.get_serial_proxies().size();
#endif
#ifdef USE_API_NOISE
resp.api_encryption_supported = true;
#endif

View File

@@ -182,6 +182,15 @@ class APIConnection final : public APIServerConnectionBase {
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
#endif
#ifdef USE_SERIAL_PROXY
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override;
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override;
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override;
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override;
void on_serial_proxy_flush_request(const SerialProxyFlushRequest &msg) override;
void send_serial_proxy_data(const SerialProxyDataReceived &msg);
#endif
#ifdef USE_EVENT
void send_event(event::Event *event);
#endif

View File

@@ -119,6 +119,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_ZWAVE_PROXY
buffer.encode_uint32(24, this->zwave_home_id);
#endif
#ifdef USE_SERIAL_PROXY
buffer.encode_uint32(25, this->serial_proxy_count);
#endif
}
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->name.size());
@@ -174,6 +177,9 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_ZWAVE_PROXY
size.add_uint32(2, this->zwave_home_id);
#endif
#ifdef USE_SERIAL_PROXY
size.add_uint32(2, this->serial_proxy_count);
#endif
}
#ifdef USE_BINARY_SENSOR
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
@@ -3440,5 +3446,108 @@ void InfraredRFReceiveEvent::calculate_size(ProtoSize &size) const {
}
}
#endif
#ifdef USE_SERIAL_PROXY
bool SerialProxyConfigureRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->instance = value.as_uint32();
break;
case 2:
this->baudrate = value.as_uint32();
break;
case 3:
this->flow_control = value.as_bool();
break;
case 4:
this->parity = static_cast<enums::SerialProxyParity>(value.as_uint32());
break;
case 5:
this->stop_bits = value.as_uint32();
break;
case 6:
this->data_size = value.as_uint32();
break;
default:
return false;
}
return true;
}
void SerialProxyDataReceived::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->instance);
buffer.encode_bytes(2, this->data_ptr_, this->data_len_);
}
void SerialProxyDataReceived::calculate_size(ProtoSize &size) const {
size.add_uint32(1, this->instance);
size.add_length(1, this->data_len_);
}
bool SerialProxyWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->instance = value.as_uint32();
break;
default:
return false;
}
return true;
}
bool SerialProxyWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
bool SerialProxySetModemPinsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->instance = value.as_uint32();
break;
case 2:
this->rts = value.as_bool();
break;
case 3:
this->dtr = value.as_bool();
break;
default:
return false;
}
return true;
}
bool SerialProxyGetModemPinsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->instance = value.as_uint32();
break;
default:
return false;
}
return true;
}
void SerialProxyGetModemPinsResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->instance);
buffer.encode_bool(2, this->rts);
buffer.encode_bool(3, this->dtr);
}
void SerialProxyGetModemPinsResponse::calculate_size(ProtoSize &size) const {
size.add_uint32(1, this->instance);
size.add_bool(1, this->rts);
size.add_bool(1, this->dtr);
}
bool SerialProxyFlushRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->instance = value.as_uint32();
break;
default:
return false;
}
return true;
}
#endif
} // namespace esphome::api

View File

@@ -311,6 +311,13 @@ enum ZWaveProxyRequestType : uint32_t {
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2,
};
#endif
#ifdef USE_SERIAL_PROXY
enum SerialProxyParity : uint32_t {
SERIAL_PROXY_PARITY_NONE = 0,
SERIAL_PROXY_PARITY_EVEN = 1,
SERIAL_PROXY_PARITY_ODD = 2,
};
#endif
} // namespace enums
@@ -474,7 +481,7 @@ class DeviceInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint8_t ESTIMATED_SIZE = 255;
static constexpr uint16_t ESTIMATED_SIZE = 260;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_response"; }
#endif
@@ -526,6 +533,9 @@ class DeviceInfoResponse final : public ProtoMessage {
#endif
#ifdef USE_ZWAVE_PROXY
uint32_t zwave_home_id{0};
#endif
#ifdef USE_SERIAL_PROXY
uint32_t serial_proxy_count{0};
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -3025,5 +3035,132 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
protected:
};
#endif
#ifdef USE_SERIAL_PROXY
class SerialProxyConfigureRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 138;
static constexpr uint8_t ESTIMATED_SIZE = 20;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_configure_request"; }
#endif
uint32_t instance{0};
uint32_t baudrate{0};
bool flow_control{false};
enums::SerialProxyParity parity{};
uint32_t stop_bits{0};
uint32_t data_size{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SerialProxyDataReceived final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 139;
static constexpr uint8_t ESTIMATED_SIZE = 23;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_data_received"; }
#endif
uint32_t instance{0};
const uint8_t *data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class SerialProxyWriteRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 140;
static constexpr uint8_t ESTIMATED_SIZE = 23;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_write_request"; }
#endif
uint32_t instance{0};
const uint8_t *data{nullptr};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 141;
static constexpr uint8_t ESTIMATED_SIZE = 8;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_set_modem_pins_request"; }
#endif
uint32_t instance{0};
bool rts{false};
bool dtr{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 142;
static constexpr uint8_t ESTIMATED_SIZE = 4;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_get_modem_pins_request"; }
#endif
uint32_t instance{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SerialProxyGetModemPinsResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 143;
static constexpr uint8_t ESTIMATED_SIZE = 8;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_get_modem_pins_response"; }
#endif
uint32_t instance{0};
bool rts{false};
bool dtr{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
};
class SerialProxyFlushRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 144;
static constexpr uint8_t ESTIMATED_SIZE = 4;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "serial_proxy_flush_request"; }
#endif
uint32_t instance{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
#endif
} // namespace esphome::api

View File

@@ -736,6 +736,20 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
}
}
#endif
#ifdef USE_SERIAL_PROXY
template<> const char *proto_enum_to_string<enums::SerialProxyParity>(enums::SerialProxyParity value) {
switch (value) {
case enums::SERIAL_PROXY_PARITY_NONE:
return "SERIAL_PROXY_PARITY_NONE";
case enums::SERIAL_PROXY_PARITY_EVEN:
return "SERIAL_PROXY_PARITY_EVEN";
case enums::SERIAL_PROXY_PARITY_ODD:
return "SERIAL_PROXY_PARITY_ODD";
default:
return "UNKNOWN";
}
}
#endif
const char *HelloRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "HelloRequest");
@@ -845,6 +859,9 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
#endif
#ifdef USE_ZWAVE_PROXY
dump_field(out, "zwave_home_id", this->zwave_home_id);
#endif
#ifdef USE_SERIAL_PROXY
dump_field(out, "serial_proxy_count", this->serial_proxy_count);
#endif
return out.c_str();
}
@@ -2469,6 +2486,54 @@ const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const {
return out.c_str();
}
#endif
#ifdef USE_SERIAL_PROXY
const char *SerialProxyConfigureRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyConfigureRequest");
dump_field(out, "instance", this->instance);
dump_field(out, "baudrate", this->baudrate);
dump_field(out, "flow_control", this->flow_control);
dump_field(out, "parity", static_cast<enums::SerialProxyParity>(this->parity));
dump_field(out, "stop_bits", this->stop_bits);
dump_field(out, "data_size", this->data_size);
return out.c_str();
}
const char *SerialProxyDataReceived::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyDataReceived");
dump_field(out, "instance", this->instance);
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
return out.c_str();
}
const char *SerialProxyWriteRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyWriteRequest");
dump_field(out, "instance", this->instance);
dump_bytes_field(out, "data", this->data, this->data_len);
return out.c_str();
}
const char *SerialProxySetModemPinsRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxySetModemPinsRequest");
dump_field(out, "instance", this->instance);
dump_field(out, "rts", this->rts);
dump_field(out, "dtr", this->dtr);
return out.c_str();
}
const char *SerialProxyGetModemPinsRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyGetModemPinsRequest");
dump_field(out, "instance", this->instance);
return out.c_str();
}
const char *SerialProxyGetModemPinsResponse::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyGetModemPinsResponse");
dump_field(out, "instance", this->instance);
dump_field(out, "rts", this->rts);
dump_field(out, "dtr", this->dtr);
return out.c_str();
}
const char *SerialProxyFlushRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "SerialProxyFlushRequest");
dump_field(out, "instance", this->instance);
return out.c_str();
}
#endif
} // namespace esphome::api

View File

@@ -634,6 +634,61 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_infrared_rf_transmit_raw_timings_request(msg);
break;
}
#endif
#ifdef USE_SERIAL_PROXY
case SerialProxyConfigureRequest::MESSAGE_TYPE: {
SerialProxyConfigureRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_configure_request"), msg);
#endif
this->on_serial_proxy_configure_request(msg);
break;
}
#endif
#ifdef USE_SERIAL_PROXY
case SerialProxyWriteRequest::MESSAGE_TYPE: {
SerialProxyWriteRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_write_request"), msg);
#endif
this->on_serial_proxy_write_request(msg);
break;
}
#endif
#ifdef USE_SERIAL_PROXY
case SerialProxySetModemPinsRequest::MESSAGE_TYPE: {
SerialProxySetModemPinsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_set_modem_pins_request"), msg);
#endif
this->on_serial_proxy_set_modem_pins_request(msg);
break;
}
#endif
#ifdef USE_SERIAL_PROXY
case SerialProxyGetModemPinsRequest::MESSAGE_TYPE: {
SerialProxyGetModemPinsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_get_modem_pins_request"), msg);
#endif
this->on_serial_proxy_get_modem_pins_request(msg);
break;
}
#endif
#ifdef USE_SERIAL_PROXY
case SerialProxyFlushRequest::MESSAGE_TYPE: {
SerialProxyFlushRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_flush_request"), msg);
#endif
this->on_serial_proxy_flush_request(msg);
break;
}
#endif
default:
break;

View File

@@ -224,6 +224,23 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_flush_request(const SerialProxyFlushRequest &value){};
#endif
protected:
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};

View File

@@ -370,6 +370,17 @@ void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_
}
#endif
#ifdef USE_SERIAL_PROXY
void APIServer::send_serial_proxy_data(uint32_t instance, const uint8_t *data, size_t len) {
SerialProxyDataReceived msg{};
msg.instance = instance;
msg.set_data(data, len);
for (auto &c : this->clients_)
c->send_serial_proxy_data(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif

View File

@@ -189,6 +189,10 @@ class APIServer : public Component,
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
#endif
#ifdef USE_SERIAL_PROXY
void send_serial_proxy_data(uint32_t instance, const uint8_t *data, size_t len);
#endif
bool is_connected(bool state_subscription_only = false) const;
#ifdef USE_API_HOMEASSISTANT_STATES

View File

@@ -645,12 +645,11 @@ def _is_framework_url(source: str) -> bool:
# The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(3, 3, 7),
"latest": cv.Version(3, 3, 7),
"dev": cv.Version(3, 3, 7),
"recommended": cv.Version(3, 3, 6),
"latest": cv.Version(3, 3, 6),
"dev": cv.Version(3, 3, 6),
}
ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(3, 3, 7): cv.Version(55, 3, 37),
cv.Version(3, 3, 6): cv.Version(55, 3, 36),
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
@@ -669,7 +668,6 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
# These versions correspond to pioarduino/esp-idf releases
# See: https://github.com/pioarduino/esp-idf/releases
ARDUINO_IDF_VERSION_LOOKUP = {
cv.Version(3, 3, 7): cv.Version(5, 5, 2),
cv.Version(3, 3, 6): cv.Version(5, 5, 2),
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
@@ -693,7 +691,7 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"dev": cv.Version(5, 5, 2),
}
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
cv.Version(5, 5, 2): cv.Version(55, 3, 36),
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
@@ -710,8 +708,8 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
# The platform-espressif32 version
# - https://github.com/pioarduino/platform-espressif32/releases
PLATFORM_VERSION_LOOKUP = {
"recommended": cv.Version(55, 3, 37),
"latest": cv.Version(55, 3, 37),
"recommended": cv.Version(55, 3, 36),
"latest": cv.Version(55, 3, 36),
"dev": "https://github.com/pioarduino/platform-espressif32.git#develop",
}

View File

@@ -1686,10 +1686,6 @@ BOARDS = {
"name": "Espressif ESP32-C6-DevKitM-1",
"variant": VARIANT_ESP32C6,
},
"esp32-c61-devkitc1": {
"name": "Espressif ESP32-C61-DevKitC-1 (4 MB Flash)",
"variant": VARIANT_ESP32C61,
},
"esp32-c61-devkitc1-n8r2": {
"name": "Espressif ESP32-C61-DevKitC-1 N8R2 (8 MB Flash Quad, 2 MB PSRAM Quad)",
"variant": VARIANT_ESP32C61,
@@ -1722,10 +1718,6 @@ BOARDS = {
"name": "Espressif ESP32-P4 rev.300 generic",
"variant": VARIANT_ESP32P4,
},
"esp32-p4_r3-evboard": {
"name": "Espressif ESP32-P4 Function EV Board v1.6 (rev.301)",
"variant": VARIANT_ESP32P4,
},
"esp32-pico-devkitm-2": {
"name": "Espressif ESP32-PICO-DevKitM-2",
"variant": VARIANT_ESP32,
@@ -2562,10 +2554,6 @@ BOARDS = {
"name": "XinaBox CW02",
"variant": VARIANT_ESP32,
},
"yb_esp32s3_amp": {
"name": "YelloByte YB-ESP32-S3-AMP",
"variant": VARIANT_ESP32S3,
},
"yb_esp32s3_amp_v2": {
"name": "YelloByte YB-ESP32-S3-AMP (Rev.2)",
"variant": VARIANT_ESP32S3,
@@ -2574,10 +2562,6 @@ BOARDS = {
"name": "YelloByte YB-ESP32-S3-AMP (Rev.3)",
"variant": VARIANT_ESP32S3,
},
"yb_esp32s3_dac": {
"name": "YelloByte YB-ESP32-S3-DAC",
"variant": VARIANT_ESP32S3,
},
"yb_esp32s3_drv": {
"name": "YelloByte YB-ESP32-S3-DRV",
"variant": VARIANT_ESP32S3,

View File

@@ -130,16 +130,11 @@ ETHERNET_TYPES = {
}
# PHY types that need compile-time defines for conditional compilation
# Each RMII PHY type gets a define so unused PHY drivers are excluded by the linker
_PHY_TYPE_TO_DEFINE = {
"LAN8720": "USE_ETHERNET_LAN8720",
"RTL8201": "USE_ETHERNET_RTL8201",
"DP83848": "USE_ETHERNET_DP83848",
"IP101": "USE_ETHERNET_IP101",
"JL1101": "USE_ETHERNET_JL1101",
"KSZ8081": "USE_ETHERNET_KSZ8081",
"KSZ8081RNA": "USE_ETHERNET_KSZ8081",
"LAN8670": "USE_ETHERNET_LAN8670",
# Add other PHY types here only if they need conditional compilation
}
SPI_ETHERNET_TYPES = ["W5500", "DM9051"]

View File

@@ -186,43 +186,31 @@ void EthernetComponent::setup() {
}
#endif
#if CONFIG_ETH_USE_ESP32_EMAC
#ifdef USE_ETHERNET_LAN8720
case ETHERNET_TYPE_LAN8720: {
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_RTL8201
case ETHERNET_TYPE_RTL8201: {
this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_DP83848
case ETHERNET_TYPE_DP83848: {
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_IP101
case ETHERNET_TYPE_IP101: {
this->phy_ = esp_eth_phy_new_ip101(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_JL1101
case ETHERNET_TYPE_JL1101: {
this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_KSZ8081
case ETHERNET_TYPE_KSZ8081:
case ETHERNET_TYPE_KSZ8081RNA: {
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
break;
}
#endif
#ifdef USE_ETHERNET_LAN8670
case ETHERNET_TYPE_LAN8670: {
this->phy_ = esp_eth_phy_new_lan867x(&phy_config);
@@ -355,32 +343,26 @@ void EthernetComponent::loop() {
void EthernetComponent::dump_config() {
const char *eth_type;
switch (this->type_) {
#ifdef USE_ETHERNET_LAN8720
case ETHERNET_TYPE_LAN8720:
eth_type = "LAN8720";
break;
#endif
#ifdef USE_ETHERNET_RTL8201
case ETHERNET_TYPE_RTL8201:
eth_type = "RTL8201";
break;
#endif
#ifdef USE_ETHERNET_DP83848
case ETHERNET_TYPE_DP83848:
eth_type = "DP83848";
break;
#endif
#ifdef USE_ETHERNET_IP101
case ETHERNET_TYPE_IP101:
eth_type = "IP101";
break;
#endif
#ifdef USE_ETHERNET_JL1101
case ETHERNET_TYPE_JL1101:
eth_type = "JL1101";
break;
#endif
#ifdef USE_ETHERNET_KSZ8081
case ETHERNET_TYPE_KSZ8081:
eth_type = "KSZ8081";
break;
@@ -388,22 +370,19 @@ void EthernetComponent::dump_config() {
case ETHERNET_TYPE_KSZ8081RNA:
eth_type = "KSZ8081RNA";
break;
#endif
#if CONFIG_ETH_SPI_ETHERNET_W5500
case ETHERNET_TYPE_W5500:
eth_type = "W5500";
break;
#endif
#if CONFIG_ETH_SPI_ETHERNET_DM9051
case ETHERNET_TYPE_DM9051:
eth_type = "DM9051";
break;
#endif
#ifdef USE_ETHERNET_OPENETH
case ETHERNET_TYPE_OPENETH:
eth_type = "OPENETH";
break;
#endif
case ETHERNET_TYPE_DM9051:
eth_type = "DM9051";
break;
#ifdef USE_ETHERNET_LAN8670
case ETHERNET_TYPE_LAN8670:
eth_type = "LAN8670";
@@ -707,22 +686,16 @@ void EthernetComponent::dump_connect_params_() {
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" IP Address: %s\n"
" Hostname: '%s'\n"
" Subnet: %s\n"
" Gateway: %s\n"
" DNS1: %s\n"
" DNS2: %s\n"
" MAC Address: %s\n"
" Is Full Duplex: %s\n"
" Link Speed: %u",
" DNS2: %s",
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf),
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf));
#if USE_NETWORK_IPV6
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
@@ -733,6 +706,14 @@ void EthernetComponent::dump_connect_params_() {
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
}
#endif /* USE_NETWORK_IPV6 */
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" MAC Address: %s\n"
" Is Full Duplex: %s\n"
" Link Speed: %u",
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
}
#ifdef USE_ETHERNET_SPI
@@ -856,15 +837,13 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
esp_err_t err;
#ifdef USE_ETHERNET_RTL8201
constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
}
#endif
ESP_LOGD(TAG,
"Writing to PHY Register Address: 0x%02" PRIX32 "\n"
@@ -873,13 +852,11 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
#ifdef USE_ETHERNET_RTL8201
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
ESP_LOGD(TAG, "Select PHY Register Page 0x00");
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
}
#endif
}
#endif

View File

@@ -0,0 +1,62 @@
"""
Serial Proxy component for ESPHome.
WARNING: This component is EXPERIMENTAL. The API (both Python configuration
and C++ interfaces) may change at any time without following the normal
breaking changes policy. Use at your own risk.
Once the API is considered stable, this warning will be removed.
Provides a proxy to/from a serial interface on the ESPHome device, allowing
Home Assistant to connect to the serial port and send/receive data to/from
an arbitrary serial device.
"""
from esphome import pins
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["api", "uart"]
MULTI_CONF = True
serial_proxy_ns = cg.esphome_ns.namespace("serial_proxy")
SerialProxy = serial_proxy_ns.class_("SerialProxy", cg.Component, uart.UARTDevice)
CONF_RTS_PIN = "rts_pin"
CONF_DTR_PIN = "dtr_pin"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(SerialProxy),
cv.Optional(CONF_RTS_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_DTR_PIN): pins.gpio_output_pin_schema,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add(cg.App.register_serial_proxy(var))
cg.add_define("USE_SERIAL_PROXY")
if CONF_RTS_PIN in config:
rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN])
cg.add(var.set_rts_pin(rts_pin))
if CONF_DTR_PIN in config:
dtr_pin = await cg.gpio_pin_expression(config[CONF_DTR_PIN])
cg.add(var.set_dtr_pin(dtr_pin))
# Request UART to wake the main loop when data arrives for low-latency processing
uart.request_wake_loop_on_rx()

View File

@@ -0,0 +1,131 @@
#include "serial_proxy.h"
#ifdef USE_SERIAL_PROXY
#include "esphome/core/log.h"
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif
namespace esphome::serial_proxy {
static const char *const TAG = "serial_proxy";
void SerialProxy::setup() {
// Set up modem control pins if configured
if (this->rts_pin_ != nullptr) {
this->rts_pin_->setup();
this->rts_pin_->digital_write(this->rts_state_);
}
if (this->dtr_pin_ != nullptr) {
this->dtr_pin_->setup();
this->dtr_pin_->digital_write(this->dtr_state_);
}
}
void SerialProxy::loop() {
// Read available data from UART and forward to API clients
size_t available = this->available();
if (available == 0)
return;
// Read in chunks up to SERIAL_PROXY_MAX_READ_SIZE
uint8_t buffer[SERIAL_PROXY_MAX_READ_SIZE];
size_t to_read = std::min(available, sizeof(buffer));
if (!this->read_array(buffer, to_read))
return;
#ifdef USE_API
if (api::global_api_server != nullptr) {
api::global_api_server->send_serial_proxy_data(this->instance_index_, buffer, to_read);
}
#endif
}
void SerialProxy::dump_config() {
ESP_LOGCONFIG(TAG,
"Serial Proxy [%u]:\n"
" RTS Pin: %s\n"
" DTR Pin: %s",
this->instance_index_, this->rts_pin_ != nullptr ? "configured" : "not configured",
this->dtr_pin_ != nullptr ? "configured" : "not configured");
}
void SerialProxy::configure(uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits,
uint8_t data_size) {
ESP_LOGD(TAG, "Configuring serial proxy [%u]: baud=%u, flow_ctrl=%s, parity=%u, stop=%u, data=%u",
this->instance_index_, baudrate, YESNO(flow_control), parity, stop_bits, data_size);
auto *uart_comp = this->parent_;
if (uart_comp == nullptr) {
ESP_LOGE(TAG, "UART component not available");
return;
}
// Apply UART parameters
uart_comp->set_baud_rate(baudrate);
uart_comp->set_stop_bits(stop_bits);
uart_comp->set_data_bits(data_size);
// Map parity enum to UARTParityOptions
switch (parity) {
case 0:
uart_comp->set_parity(uart::UART_CONFIG_PARITY_NONE);
break;
case 1:
uart_comp->set_parity(uart::UART_CONFIG_PARITY_EVEN);
break;
case 2:
uart_comp->set_parity(uart::UART_CONFIG_PARITY_ODD);
break;
default:
ESP_LOGW(TAG, "Unknown parity value: %u, using NONE", parity);
uart_comp->set_parity(uart::UART_CONFIG_PARITY_NONE);
break;
}
// Apply the new settings
// load_settings() is available on ESP8266 and ESP32 platforms
#if defined(USE_ESP8266) || defined(USE_ESP32)
uart_comp->load_settings(true);
#endif
// Note: Hardware flow control configuration is stored but not yet applied
// to the UART hardware - this requires additional platform support
(void) flow_control;
}
void SerialProxy::write(const uint8_t *data, size_t len) {
if (data == nullptr || len == 0)
return;
this->write_array(data, len);
}
void SerialProxy::set_modem_pins(bool rts, bool dtr) {
ESP_LOGV(TAG, "Setting modem pins [%u]: RTS=%s, DTR=%s", this->instance_index_, ONOFF(rts), ONOFF(dtr));
if (this->rts_pin_ != nullptr) {
this->rts_state_ = rts;
this->rts_pin_->digital_write(rts);
}
if (this->dtr_pin_ != nullptr) {
this->dtr_state_ = dtr;
this->dtr_pin_->digital_write(dtr);
}
}
void SerialProxy::get_modem_pins(bool &rts, bool &dtr) const {
rts = this->rts_state_;
dtr = this->dtr_state_;
}
void SerialProxy::flush_port() {
ESP_LOGV(TAG, "Flushing serial proxy [%u]", this->instance_index_);
this->flush();
}
} // namespace esphome::serial_proxy
#endif // USE_SERIAL_PROXY

View File

@@ -0,0 +1,80 @@
#pragma once
// WARNING: This component is EXPERIMENTAL. The API may change at any time
// without following the normal breaking changes policy. Use at your own risk.
// Once the API is considered stable, this warning will be removed.
#include "esphome/core/defines.h"
#ifdef USE_SERIAL_PROXY
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/uart/uart.h"
namespace esphome::serial_proxy {
/// Maximum bytes to read from UART in a single loop iteration
static constexpr size_t SERIAL_PROXY_MAX_READ_SIZE = 256;
class SerialProxy : public uart::UARTDevice, public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
/// Get the instance index (position in Application's serial_proxies_ vector)
uint32_t get_instance_index() const { return this->instance_index_; }
/// Set the instance index (called by Application::register_serial_proxy)
void set_instance_index(uint32_t index) { this->instance_index_ = index; }
/// Configure UART parameters and apply them
/// @param baudrate Baud rate in bits per second
/// @param flow_control True to enable hardware flow control
/// @param parity Parity setting (0=none, 1=even, 2=odd)
/// @param stop_bits Number of stop bits (1 or 2)
/// @param data_size Number of data bits (5-8)
void configure(uint32_t baudrate, bool flow_control, uint8_t parity, uint8_t stop_bits, uint8_t data_size);
/// Write data to the serial device
/// @param data Pointer to data buffer
/// @param len Number of bytes to write
void write(const uint8_t *data, size_t len);
/// Set modem pin states (RTS and DTR)
/// @param rts Desired RTS pin state
/// @param dtr Desired DTR pin state
void set_modem_pins(bool rts, bool dtr);
/// Get current modem pin states
/// @param[out] rts Current RTS pin state
/// @param[out] dtr Current DTR pin state
void get_modem_pins(bool &rts, bool &dtr) const;
/// Flush the serial port (block until all TX data is sent)
void flush_port();
/// Set the RTS GPIO pin (from YAML configuration)
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
/// Set the DTR GPIO pin (from YAML configuration)
void set_dtr_pin(GPIOPin *pin) { this->dtr_pin_ = pin; }
protected:
/// Instance index for identifying this proxy in API messages
uint32_t instance_index_{0};
/// Optional GPIO pins for modem control
GPIOPin *rts_pin_{nullptr};
GPIOPin *dtr_pin_{nullptr};
/// Current modem pin states
bool rts_state_{false};
bool dtr_state_{false};
};
} // namespace esphome::serial_proxy
#endif // USE_SERIAL_PROXY

View File

@@ -288,6 +288,11 @@ def _validate(config):
config = config.copy()
config[CONF_NETWORKS] = []
if config.get(CONF_FAST_CONNECT, False):
networks = config.get(CONF_NETWORKS, [])
if not networks:
raise cv.Invalid("At least one network required for fast_connect!")
if CONF_USE_ADDRESS not in config:
use_address = CORE.name + config[CONF_DOMAIN]
if CONF_MANUAL_IP in config:

View File

@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2026.2.0b1"
__version__ = "2026.2.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -94,6 +94,9 @@
#ifdef USE_INFRARED
#include "esphome/components/infrared/infrared.h"
#endif
#ifdef USE_SERIAL_PROXY
#include "esphome/components/serial_proxy/serial_proxy.h"
#endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
@@ -234,6 +237,13 @@ class Application {
void register_infrared(infrared::Infrared *infrared) { this->infrareds_.push_back(infrared); }
#endif
#ifdef USE_SERIAL_PROXY
void register_serial_proxy(serial_proxy::SerialProxy *proxy) {
proxy->set_instance_index(this->serial_proxies_.size());
this->serial_proxies_.push_back(proxy);
}
#endif
#ifdef USE_EVENT
void register_event(event::Event *event) { this->events_.push_back(event); }
#endif
@@ -473,6 +483,10 @@ class Application {
GET_ENTITY_METHOD(infrared::Infrared, infrared, infrareds)
#endif
#ifdef USE_SERIAL_PROXY
auto &get_serial_proxies() const { return this->serial_proxies_; }
#endif
#ifdef USE_EVENT
auto &get_events() const { return this->events_; }
GET_ENTITY_METHOD(event::Event, event, events)
@@ -690,6 +704,9 @@ class Application {
#ifdef USE_INFRARED
StaticVector<infrared::Infrared *, ESPHOME_ENTITY_INFRARED_COUNT> infrareds_{};
#endif
#ifdef USE_SERIAL_PROXY
std::vector<serial_proxy::SerialProxy *> serial_proxies_{};
#endif
#ifdef USE_UPDATE
StaticVector<update::UpdateEntity *, ESPHOME_ENTITY_UPDATE_COUNT> updates_{};
#endif

View File

@@ -109,6 +109,7 @@
#define USE_SAFE_MODE_CALLBACK
#define USE_SELECT
#define USE_SENSOR
#define USE_SERIAL_PROXY
#define USE_STATUS_LED
#define USE_STATUS_SENSOR
#define USE_SWITCH
@@ -239,15 +240,9 @@
#define USB_HOST_MAX_REQUESTS 16
#ifdef USE_ARDUINO
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 7)
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 6)
#define USE_ETHERNET
#define USE_ETHERNET_LAN8720
#define USE_ETHERNET_RTL8201
#define USE_ETHERNET_DP83848
#define USE_ETHERNET_IP101
#define USE_ETHERNET_JL1101
#define USE_ETHERNET_KSZ8081
#define USE_ETHERNET_LAN8670
#define USE_ETHERNET_MANUAL_IP
#define USE_ETHERNET_IP_STATE_LISTENERS
#define USE_ETHERNET_CONNECT_TRIGGER

View File

@@ -133,9 +133,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script
; This are common settings for the ESP32 (all variants) using Arduino.
[common:esp32-arduino]
extends = common:arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.36/platform-espressif32.zip
platform_packages =
pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.7/esp32-core-3.3.7.tar.xz
pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.6/esp32-core-3.3.6.tar.xz
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.2/esp-idf-v5.5.2.tar.xz
framework = arduino, espidf ; Arduino as an ESP-IDF component
@@ -169,7 +169,7 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf]
extends = common:idf
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.36/platform-espressif32.zip
platform_packages =
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.2/esp-idf-v5.5.2.tar.xz

View File

@@ -20,8 +20,8 @@ classifiers = [
"Topic :: Home Automation",
]
# Python 3.14 is not supported on Windows, see https://github.com/zephyrproject-rtos/windows-curses/issues/76
requires-python = ">=3.11.0,<3.15"
# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502
requires-python = ">=3.11.0,<3.14"
dynamic = ["dependencies", "optional-dependencies", "version"]

View File

@@ -0,0 +1,8 @@
wifi:
ssid: MySSID
password: password1
api:
serial_proxy:
- id: serial_proxy_1

View File

@@ -0,0 +1,8 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
packages:
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,8 @@
substitutions:
tx_pin: GPIO0
rx_pin: GPIO2
packages:
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,8 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
packages:
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
<<: !include common.yaml