mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 16:30:22 +00:00
[api] Acknowledge proxy subscribe and configuration requests
This commit is contained in:
@@ -2622,6 +2622,21 @@ message ZWaveProxyRequest {
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
enum ZWaveProxyStatus {
|
||||
ZWAVE_PROXY_STATUS_OK = 0; // Request completed successfully
|
||||
ZWAVE_PROXY_STATUS_IN_USE = 1; // Denied: another client is already subscribed
|
||||
}
|
||||
|
||||
// Acknowledges a ZWaveProxyRequest (subscribe/unsubscribe). Sent since API 1.16.
|
||||
message ZWaveProxyRequestResponse {
|
||||
option (id) = 151;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||
|
||||
ZWaveProxyRequestType type = 1; // Which request type this responds to
|
||||
ZWaveProxyStatus status = 2; // Result status
|
||||
}
|
||||
|
||||
// ==================== INFRARED ====================
|
||||
// Note: Feature and capability flag enums are defined in
|
||||
// esphome/components/infrared/infrared.h
|
||||
@@ -2771,6 +2786,10 @@ enum SerialProxyRequestType {
|
||||
SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE = 0; // Subscribe to receive data from this serial proxy instance
|
||||
SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1; // Unsubscribe from this serial proxy instance
|
||||
SERIAL_PROXY_REQUEST_TYPE_FLUSH = 2; // Flush the serial port (block until all TX data is sent)
|
||||
// Values below are only valid in SerialProxyRequestResponse.type, identifying which
|
||||
// operation is being acknowledged; they must not be sent in SerialProxyRequest.type.
|
||||
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3; // Acknowledges a SerialProxyConfigureRequest
|
||||
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4; // Acknowledges a SerialProxySetModemPinsRequest
|
||||
}
|
||||
|
||||
enum SerialProxyStatus {
|
||||
@@ -2779,6 +2798,8 @@ enum SerialProxyStatus {
|
||||
SERIAL_PROXY_STATUS_ERROR = 2; // Driver or hardware error
|
||||
SERIAL_PROXY_STATUS_TIMEOUT = 3; // Timed out before TX completed
|
||||
SERIAL_PROXY_STATUS_NOT_SUPPORTED = 4; // Request type not supported by this instance
|
||||
SERIAL_PROXY_STATUS_PORT_IN_USE = 5; // Denied: another client holds the port
|
||||
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6; // Invalid instance index or parameter value
|
||||
}
|
||||
|
||||
// Generic request message for simple serial proxy operations
|
||||
@@ -2791,7 +2812,9 @@ message SerialProxyRequest {
|
||||
SerialProxyRequestType type = 2; // Request type
|
||||
}
|
||||
|
||||
// Response to a SerialProxyRequest (e.g. flush completion or failure)
|
||||
// Acknowledges a serial proxy operation (subscribe, unsubscribe, flush,
|
||||
// configure, or modem pin change). Sent since API 1.16; the type field
|
||||
// identifies which operation is being acknowledged.
|
||||
message SerialProxyRequestResponse {
|
||||
option (id) = 147;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
@@ -1384,7 +1384,17 @@ void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
|
||||
}
|
||||
|
||||
void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||
zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
|
||||
enums::ZWaveProxyStatus status = zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
|
||||
// Only subscribe/unsubscribe are acknowledged (other types are server-to-client notifications)
|
||||
if (msg.type == enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE ||
|
||||
msg.type == enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE) {
|
||||
ZWaveProxyRequestResponse resp{};
|
||||
resp.type = msg.type;
|
||||
resp.status = status;
|
||||
if (!this->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Z-Wave proxy response");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1553,15 +1563,46 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
static enums::SerialProxyStatus serial_proxy_result_to_status(serial_proxy::SerialProxyResult result) {
|
||||
switch (result) {
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_OK:
|
||||
return enums::SERIAL_PROXY_STATUS_OK;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE:
|
||||
return enums::SERIAL_PROXY_STATUS_PORT_IN_USE;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT:
|
||||
return enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED:
|
||||
return enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_ERROR:
|
||||
default:
|
||||
return enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_serial_proxy_ack(APIConnection *conn, uint32_t instance, enums::SerialProxyRequestType type,
|
||||
enums::SerialProxyStatus status) {
|
||||
SerialProxyRequestResponse resp{};
|
||||
resp.instance = instance;
|
||||
resp.type = type;
|
||||
resp.status = status;
|
||||
if (!conn->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
|
||||
}
|
||||
}
|
||||
|
||||
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 %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
|
||||
static_cast<uint32_t>(proxies.size()));
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE,
|
||||
enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->configure(this, msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity),
|
||||
msg.stop_bits, msg.data_size);
|
||||
serial_proxy::SerialProxyResult result = proxies[msg.instance]->configure(
|
||||
this, msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity), msg.stop_bits, msg.data_size);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE,
|
||||
serial_proxy_result_to_status(result));
|
||||
}
|
||||
|
||||
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
|
||||
@@ -1577,9 +1618,13 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS,
|
||||
enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->set_modem_pins(this, msg.line_states);
|
||||
serial_proxy::SerialProxyResult result = proxies[msg.instance]->set_modem_pins(this, msg.line_states);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS,
|
||||
serial_proxy_result_to_status(result));
|
||||
}
|
||||
|
||||
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
|
||||
@@ -1600,40 +1645,44 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
send_serial_proxy_ack(this, msg.instance, msg.type, enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
auto *proxy = proxies[msg.instance];
|
||||
enums::SerialProxyStatus status;
|
||||
switch (msg.type) {
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
proxies[msg.instance]->serial_proxy_request(this, msg.type);
|
||||
status = serial_proxy_result_to_status(proxy->serial_proxy_request(this, msg.type));
|
||||
break;
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH: {
|
||||
SerialProxyRequestResponse resp{};
|
||||
resp.instance = msg.instance;
|
||||
resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
|
||||
switch (proxies[msg.instance]->flush_port()) {
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH:
|
||||
// Flushing stalls the port, so it gets the same ownership check as writes
|
||||
if (proxy->port_claimed_by_other(this)) {
|
||||
status = enums::SERIAL_PROXY_STATUS_PORT_IN_USE;
|
||||
break;
|
||||
}
|
||||
switch (proxy->flush_port()) {
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_OK;
|
||||
status = enums::SERIAL_PROXY_STATUS_OK;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
default:
|
||||
status = enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
if (!this->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
|
||||
status = enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
send_serial_proxy_ack(this, msg.instance, msg.type, status);
|
||||
}
|
||||
|
||||
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) {
|
||||
@@ -1760,7 +1809,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 15;
|
||||
resp.api_version_minor = 16;
|
||||
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||
resp.server_info = ESPHOME_VERSION_REF;
|
||||
resp.name = StringRef(App.get_name());
|
||||
|
||||
@@ -3945,6 +3945,18 @@ uint32_t ZWaveProxyRequest::calculate_size() const {
|
||||
size += ProtoSize::calc_length(1, this->data_len);
|
||||
return size;
|
||||
}
|
||||
uint8_t *ZWaveProxyRequestResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(this->type));
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast<uint32_t>(this->status));
|
||||
return pos;
|
||||
}
|
||||
uint32_t ZWaveProxyRequestResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += this->type ? 2 : 0;
|
||||
size += this->status ? 2 : 0;
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
uint8_t *ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
|
||||
@@ -334,6 +334,10 @@ enum ZWaveProxyRequestType : uint32_t {
|
||||
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
|
||||
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2,
|
||||
};
|
||||
enum ZWaveProxyStatus : uint32_t {
|
||||
ZWAVE_PROXY_STATUS_OK = 0,
|
||||
ZWAVE_PROXY_STATUS_IN_USE = 1,
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
enum SerialProxyParity : uint32_t {
|
||||
@@ -345,6 +349,8 @@ enum SerialProxyRequestType : uint32_t {
|
||||
SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE = 0,
|
||||
SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
|
||||
SERIAL_PROXY_REQUEST_TYPE_FLUSH = 2,
|
||||
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3,
|
||||
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4,
|
||||
};
|
||||
enum SerialProxyStatus : uint32_t {
|
||||
SERIAL_PROXY_STATUS_OK = 0,
|
||||
@@ -352,6 +358,8 @@ enum SerialProxyStatus : uint32_t {
|
||||
SERIAL_PROXY_STATUS_ERROR = 2,
|
||||
SERIAL_PROXY_STATUS_TIMEOUT = 3,
|
||||
SERIAL_PROXY_STATUS_NOT_SUPPORTED = 4,
|
||||
SERIAL_PROXY_STATUS_PORT_IN_USE = 5,
|
||||
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6,
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -3130,6 +3138,23 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
|
||||
};
|
||||
class ZWaveProxyRequestResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 151;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 4;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("z_wave_proxy_request_response"); }
|
||||
#endif
|
||||
enums::ZWaveProxyRequestType type{};
|
||||
enums::ZWaveProxyStatus status{};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
|
||||
@@ -816,6 +816,16 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::ZWaveProxyStatus>(enums::ZWaveProxyStatus value) {
|
||||
switch (value) {
|
||||
case enums::ZWAVE_PROXY_STATUS_OK:
|
||||
return ESPHOME_PSTR("ZWAVE_PROXY_STATUS_OK");
|
||||
case enums::ZWAVE_PROXY_STATUS_IN_USE:
|
||||
return ESPHOME_PSTR("ZWAVE_PROXY_STATUS_IN_USE");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
template<> const char *proto_enum_to_string<enums::SerialProxyParity>(enums::SerialProxyParity value) {
|
||||
@@ -838,6 +848,10 @@ template<> const char *proto_enum_to_string<enums::SerialProxyRequestType>(enums
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_FLUSH");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_CONFIGURE");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -854,6 +868,10 @@ template<> const char *proto_enum_to_string<enums::SerialProxyStatus>(enums::Ser
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_TIMEOUT");
|
||||
case enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_NOT_SUPPORTED");
|
||||
case enums::SERIAL_PROXY_STATUS_PORT_IN_USE:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_PORT_IN_USE");
|
||||
case enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_INVALID_ARGUMENT");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -2644,6 +2662,12 @@ const char *ZWaveProxyRequest::dump_to(DumpBuffer &out) const {
|
||||
dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *ZWaveProxyRequestResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("ZWaveProxyRequestResponse"));
|
||||
dump_field(out, ESPHOME_PSTR("type"), static_cast<enums::ZWaveProxyRequestType>(this->type));
|
||||
dump_field(out, ESPHOME_PSTR("status"), static_cast<enums::ZWaveProxyStatus>(this->status));
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const {
|
||||
|
||||
@@ -89,12 +89,12 @@ void SerialProxy::dump_config() {
|
||||
this->dtr_pin_ != nullptr ? "configured" : "not configured");
|
||||
}
|
||||
|
||||
void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size) {
|
||||
SerialProxyResult SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control,
|
||||
uint8_t parity, uint8_t stop_bits, uint8_t data_size) {
|
||||
#ifdef USE_API
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
if (this->port_claimed_by_other(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring configure request from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG,
|
||||
@@ -105,25 +105,29 @@ void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrat
|
||||
auto *uart_comp = this->parent_;
|
||||
if (uart_comp == nullptr) {
|
||||
ESP_LOGE(TAG, "UART component not available");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// Validate all parameters before applying any (values come from a remote client)
|
||||
if (baudrate == 0) {
|
||||
ESP_LOGW(TAG, "Invalid baud rate: 0");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (stop_bits < 1 || stop_bits > 2) {
|
||||
ESP_LOGW(TAG, "Invalid stop bits: %u (must be 1 or 2)", stop_bits);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (data_size < 5 || data_size > 8) {
|
||||
ESP_LOGW(TAG, "Invalid data bits: %u (must be 5-8)", data_size);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (parity > 2) {
|
||||
ESP_LOGW(TAG, "Invalid parity: %u (must be 0-2)", parity);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (flow_control) {
|
||||
ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// Apply validated parameters
|
||||
@@ -143,17 +147,14 @@ void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrat
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
uart_comp->load_settings(true);
|
||||
#endif
|
||||
|
||||
if (flow_control) {
|
||||
ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
|
||||
}
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
|
||||
void SerialProxy::write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len) {
|
||||
#ifdef USE_API
|
||||
// Bytes from a client other than the live subscriber would interleave with the
|
||||
// subscriber's traffic on the wire
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
if (this->port_claimed_by_other(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring write from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
}
|
||||
@@ -163,13 +164,17 @@ void SerialProxy::write_from_client(api::APIConnection *api_connection, const ui
|
||||
this->write_array(data, len);
|
||||
}
|
||||
|
||||
void SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {
|
||||
SerialProxyResult SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {
|
||||
#ifdef USE_API
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
if (this->port_claimed_by_other(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring modem pin request from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
#endif
|
||||
if (this->rts_pin_ == nullptr && this->dtr_pin_ == nullptr) {
|
||||
ESP_LOGW(TAG, "No modem pins configured on serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
const bool rts = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_RTS) != 0;
|
||||
const bool dtr = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_DTR) != 0;
|
||||
ESP_LOGV(TAG, "Setting modem pins [%" PRIu32 "]: RTS=%s, DTR=%s", this->instance_index_, ONOFF(rts), ONOFF(dtr));
|
||||
@@ -182,6 +187,7 @@ void SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t li
|
||||
this->dtr_state_ = dtr;
|
||||
this->dtr_pin_->digital_write(dtr);
|
||||
}
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
|
||||
uint32_t SerialProxy::get_modem_pins() const {
|
||||
@@ -195,17 +201,18 @@ uart::UARTFlushResult SerialProxy::flush_port() {
|
||||
}
|
||||
|
||||
#ifdef USE_API
|
||||
bool SerialProxy::port_claimed_by_other_(api::APIConnection *api_connection) const {
|
||||
bool SerialProxy::port_claimed_by_other(api::APIConnection *api_connection) const {
|
||||
return this->api_connection_ != nullptr && this->api_connection_ != api_connection &&
|
||||
this->api_connection_->is_connection_setup();
|
||||
}
|
||||
|
||||
void SerialProxy::serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type) {
|
||||
SerialProxyResult SerialProxy::serial_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::SerialProxyRequestType type) {
|
||||
switch (type) {
|
||||
case api::enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
if (this->api_connection_ == api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is already subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// A living subscriber keeps exclusive access. Its connection may be dead without
|
||||
@@ -213,26 +220,27 @@ void SerialProxy::serial_proxy_request(api::APIConnection *api_connection, api::
|
||||
// in that case let the new client take over instead of locking it out.
|
||||
if (this->api_connection_->is_connection_setup()) {
|
||||
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
ESP_LOGW(TAG, "Previous subscriber disconnected; taking over subscription");
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
this->enable_loop();
|
||||
ESP_LOGV(TAG, "API connection subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
case api::enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
// Unsubscribe is idempotent: not being subscribed is not an error
|
||||
if (this->api_connection_ != api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is not subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
this->api_connection_ = nullptr;
|
||||
this->disable_loop();
|
||||
ESP_LOGV(TAG, "API connection unsubscribed from serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(type));
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,15 @@ enum SerialProxyLineStateFlag : uint32_t {
|
||||
SERIAL_PROXY_LINE_STATE_FLAG_DTR = 1 << 1, ///< DTR (Data Terminal Ready)
|
||||
};
|
||||
|
||||
/// Result of a client-initiated operation; mapped to api::enums::SerialProxyStatus by the API layer
|
||||
enum class SerialProxyResult : uint8_t {
|
||||
SERIAL_PROXY_RESULT_OK, ///< Operation completed or request accepted
|
||||
SERIAL_PROXY_RESULT_PORT_IN_USE, ///< Denied: another live client holds the port
|
||||
SERIAL_PROXY_RESULT_INVALID_ARGUMENT, ///< A parameter value is out of range
|
||||
SERIAL_PROXY_RESULT_ERROR, ///< Driver or hardware error
|
||||
SERIAL_PROXY_RESULT_NOT_SUPPORTED, ///< Requested feature is not available on this instance
|
||||
};
|
||||
|
||||
/// Maximum bytes to read from UART in a single loop iteration
|
||||
inline constexpr size_t SERIAL_PROXY_MAX_READ_SIZE = 256;
|
||||
|
||||
@@ -73,14 +82,14 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
/// @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(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size);
|
||||
SerialProxyResult configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size);
|
||||
|
||||
/// Get the currently subscribed API connection (nullptr if none)
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
|
||||
/// Handle a subscribe/unsubscribe request from an API client
|
||||
void serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type);
|
||||
SerialProxyResult serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type);
|
||||
|
||||
/// Write data received from an API client to the serial device
|
||||
/// @param api_connection The API connection sending the data
|
||||
@@ -89,7 +98,7 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
void write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len);
|
||||
|
||||
/// Set modem pin states from a bitmask of SerialProxyLineStateFlag values
|
||||
void set_modem_pins(api::APIConnection *api_connection, uint32_t line_states);
|
||||
SerialProxyResult set_modem_pins(api::APIConnection *api_connection, uint32_t line_states);
|
||||
|
||||
/// Get current modem pin states as a bitmask of SerialProxyLineStateFlag values
|
||||
uint32_t get_modem_pins() const;
|
||||
@@ -97,6 +106,11 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
/// Flush the serial port (block until all TX data is sent)
|
||||
uart::UARTFlushResult flush_port();
|
||||
|
||||
#ifdef USE_API
|
||||
/// True when a live subscriber other than the given connection holds the port
|
||||
bool port_claimed_by_other(api::APIConnection *api_connection) const;
|
||||
#endif
|
||||
|
||||
/// Set the RTS GPIO pin (from YAML configuration)
|
||||
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
|
||||
|
||||
@@ -107,9 +121,6 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
#ifdef USE_API
|
||||
/// Read from UART and send to API client (slow path with 256-byte stack buffer)
|
||||
void read_and_send_(size_t available);
|
||||
|
||||
/// True when a live subscriber other than the given connection holds the port
|
||||
bool port_claimed_by_other_(api::APIConnection *api_connection) const;
|
||||
#endif
|
||||
|
||||
/// Instance index for identifying this proxy in API messages
|
||||
|
||||
@@ -194,12 +194,13 @@ void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) {
|
||||
}
|
||||
}
|
||||
|
||||
void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) {
|
||||
api::enums::ZWaveProxyStatus ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::ZWaveProxyRequestType type) {
|
||||
switch (type) {
|
||||
case api::enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
if (this->api_connection_ == api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is already subscribed");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// A living subscriber keeps exclusive access. Its connection may be dead without
|
||||
@@ -207,25 +208,26 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
|
||||
// in that case let the new client take over instead of locking it out.
|
||||
if (this->api_connection_->is_connection_setup()) {
|
||||
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_IN_USE;
|
||||
}
|
||||
ESP_LOGW(TAG, "Previous subscriber disconnected; taking over subscription");
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
ESP_LOGV(TAG, "API connection is now subscribed");
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
|
||||
case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
// Unsubscribe is idempotent: not being subscribed is not an error
|
||||
if (this->api_connection_ != api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is not subscribed");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
this->api_connection_ = nullptr;
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown request type: %" PRIu32, static_cast<uint32_t>(type));
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ class ZWaveProxy final : public uart::UARTDevice, public Component {
|
||||
bool can_proceed() override;
|
||||
|
||||
void api_connection_authenticated(api::APIConnection *conn);
|
||||
void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type);
|
||||
api::enums::ZWaveProxyStatus zwave_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::ZWaveProxyRequestType type);
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
|
||||
uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; }
|
||||
|
||||
Reference in New Issue
Block a user