This commit is contained in:
puddly
2026-02-19 18:22:03 -05:00
parent 917af8ff31
commit 4cfb794b62
9 changed files with 867 additions and 1012 deletions
+2 -2
View File
@@ -2450,7 +2450,7 @@ message ZWaveProxyRequest {
// ==================== ZIGBEE ====================
message ZigbeeProxyFrame {
option (id) = 130;
option (id) = 138;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_ZIGBEE_PROXY";
option (no_delay) = true;
@@ -2465,7 +2465,7 @@ enum ZigbeeProxyRequestType {
}
message ZigbeeProxyRequest {
option (id) = 131;
option (id) = 139;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_ZIGBEE_PROXY";
File diff suppressed because it is too large Load Diff
+35 -35
View File
@@ -669,7 +669,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
bool supports_speed{false};
bool supports_direction{false};
int32_t supported_speed_count{0};
const std::vector<const char *> *supported_preset_modes{};
const std::vector<const char *>* supported_preset_modes{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -733,10 +733,10 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_light_response"; }
#endif
const light::ColorModeMask *supported_color_modes{};
const light::ColorModeMask* supported_color_modes{};
float min_mireds{0.0f};
float max_mireds{0.0f};
const FixedVector<const char *> *effects{};
const FixedVector<const char *>* effects{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -964,9 +964,9 @@ class SubscribeLogsResponse final : public ProtoMessage {
const char *message_name() const override { return "subscribe_logs_response"; }
#endif
enums::LogLevel level{};
const uint8_t *message_ptr_{nullptr};
const uint8_t* message_ptr_{nullptr};
size_t message_len_{0};
void set_message(const uint8_t *data, size_t len) {
void set_message(const uint8_t* data, size_t len) {
this->message_ptr_ = data;
this->message_len_ = len;
}
@@ -986,7 +986,7 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "noise_encryption_set_key_request"; }
#endif
const uint8_t *key{nullptr};
const uint8_t* key{nullptr};
uint16_t key_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
@@ -1067,7 +1067,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
bool success{false};
StringRef error_message{};
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
const uint8_t* response_data{nullptr};
uint16_t response_data_len{0};
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1237,7 +1237,7 @@ class ExecuteServiceResponse final : public ProtoMessage {
bool success{false};
StringRef error_message{};
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
const uint8_t* response_data{nullptr};
uint16_t response_data_len{0};
#endif
void encode(ProtoWriteBuffer buffer) const override;
@@ -1272,9 +1272,9 @@ class CameraImageResponse final : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; }
#endif
const uint8_t *data_ptr_{nullptr};
const uint8_t* data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
void set_data(const uint8_t* data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
@@ -1314,16 +1314,16 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
#endif
bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false};
const climate::ClimateModeMask *supported_modes{};
const climate::ClimateModeMask* supported_modes{};
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_target_temperature_step{0.0f};
bool supports_action{false};
const climate::ClimateFanModeMask *supported_fan_modes{};
const climate::ClimateSwingModeMask *supported_swing_modes{};
const std::vector<const char *> *supported_custom_fan_modes{};
const climate::ClimatePresetMask *supported_presets{};
const std::vector<const char *> *supported_custom_presets{};
const climate::ClimateFanModeMask* supported_fan_modes{};
const climate::ClimateSwingModeMask* supported_swing_modes{};
const std::vector<const char *>* supported_custom_fan_modes{};
const climate::ClimatePresetMask* supported_presets{};
const std::vector<const char *>* supported_custom_presets{};
float visual_current_temperature_step{0.0f};
bool supports_current_humidity{false};
bool supports_target_humidity{false};
@@ -1414,7 +1414,7 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
float min_temperature{0.0f};
float max_temperature{0.0f};
float target_temperature_step{0.0f};
const water_heater::WaterHeaterModeMask *supported_modes{};
const water_heater::WaterHeaterModeMask* supported_modes{};
uint32_t supported_features{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -1531,7 +1531,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_select_response"; }
#endif
const FixedVector<const char *> *options{};
const FixedVector<const char *>* options{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1583,7 +1583,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; }
#endif
const FixedVector<const char *> *tones{};
const FixedVector<const char *>* tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -2001,9 +2001,9 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
#endif
uint64_t address{0};
uint32_t handle{0};
const uint8_t *data_ptr_{nullptr};
const uint8_t* data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
void set_data(const uint8_t* data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
@@ -2025,7 +2025,7 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
uint64_t address{0};
uint32_t handle{0};
bool response{false};
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
@@ -2060,7 +2060,7 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
#endif
uint64_t address{0};
uint32_t handle{0};
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
@@ -2096,9 +2096,9 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
#endif
uint64_t address{0};
uint32_t handle{0};
const uint8_t *data_ptr_{nullptr};
const uint8_t* data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
void set_data(const uint8_t* data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
@@ -2369,7 +2369,7 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; }
#endif
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -2491,7 +2491,7 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_configuration_response"; }
#endif
std::vector<VoiceAssistantWakeWord> available_wake_words{};
const std::vector<std::string> *active_wake_words{};
const std::vector<std::string>* active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2743,7 +2743,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_event_response"; }
#endif
StringRef device_class{};
const FixedVector<const char *> *event_types{};
const FixedVector<const char *>* event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2941,7 +2941,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "z_wave_proxy_frame"; }
#endif
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2960,7 +2960,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
const char *message_name() const override { return "z_wave_proxy_request"; }
#endif
enums::ZWaveProxyRequestType type{};
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2976,12 +2976,12 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
#ifdef USE_ZIGBEE_PROXY
class ZigbeeProxyFrame final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 130;
static constexpr uint8_t MESSAGE_TYPE = 138;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "zigbee_proxy_frame"; }
#endif
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2994,13 +2994,13 @@ class ZigbeeProxyFrame final : public ProtoDecodableMessage {
};
class ZigbeeProxyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 131;
static constexpr uint8_t MESSAGE_TYPE = 139;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "zigbee_proxy_request"; }
#endif
enums::ZigbeeProxyRequestType type{};
const uint8_t *data{nullptr};
const uint8_t* data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -3068,7 +3068,7 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
uint32_t device_id{0};
#endif
uint32_t key{0};
const std::vector<int32_t> *timings{};
const std::vector<int32_t>* timings{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
+7 -9
View File
@@ -85,7 +85,8 @@ static void dump_field(DumpBuffer &out, const char *field_name, const char *valu
out.append("\n");
}
template<typename T> static void dump_field(DumpBuffer &out, const char *field_name, T value, int indent = 2) {
template<typename T>
static void dump_field(DumpBuffer &out, const char *field_name, T value, int indent = 2) {
append_field_prefix(out, field_name, indent);
out.append(proto_enum_to_string<T>(value));
out.append("\n");
@@ -370,8 +371,7 @@ template<> const char *proto_enum_to_string<enums::WaterHeaterMode>(enums::Water
}
}
#endif
template<>
const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
template<> const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
switch (value) {
case enums::WATER_HEATER_COMMAND_HAS_NONE:
return "WATER_HEATER_COMMAND_HAS_NONE";
@@ -506,8 +506,7 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerFormatPurpose>(enu
}
#endif
#ifdef USE_BLUETOOTH_PROXY
template<>
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
template<> const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
switch (value) {
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
@@ -556,8 +555,7 @@ template<> const char *proto_enum_to_string<enums::BluetoothScannerMode>(enums::
}
}
#endif
template<>
const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::VoiceAssistantSubscribeFlag value) {
template<> const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::VoiceAssistantSubscribeFlag value) {
switch (value) {
case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE:
return "VOICE_ASSISTANT_SUBSCRIBE_NONE";
@@ -660,8 +658,7 @@ template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums
return "UNKNOWN";
}
}
template<>
const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
template<> const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
switch (value) {
case enums::ALARM_CONTROL_PANEL_DISARM:
return "ALARM_CONTROL_PANEL_DISARM";
@@ -751,6 +748,7 @@ template<> const char *proto_enum_to_string<enums::ZigbeeProxyRequestType>(enums
}
#endif
const char *HelloRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, "HelloRequest");
dump_field(out, "client_info", this->client_info);
+29 -18
View File
@@ -23,9 +23,9 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
case HelloRequest::MESSAGE_TYPE: // No setup required
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break;
case 9 /* DeviceInfoRequest is empty */: // Connection setup only
if (!this->check_connection_setup_()) {
@@ -602,25 +602,14 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
#ifdef USE_ZIGBEE_PROXY
case ZigbeeProxyFrame::MESSAGE_TYPE: {
ZigbeeProxyFrame msg;
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
case HomeassistantActionResponse::MESSAGE_TYPE: {
HomeassistantActionResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_zigbee_proxy_frame"), msg);
this->log_receive_message_(LOG_STR("on_homeassistant_action_response"), msg);
#endif
this->on_zigbee_proxy_frame(msg);
break;
}
#endif
#ifdef USE_ZIGBEE_PROXY
case ZigbeeProxyRequest::MESSAGE_TYPE: {
ZigbeeProxyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_zigbee_proxy_request"), msg);
#endif
this->on_zigbee_proxy_request(msg);
this->on_homeassistant_action_response(msg);
break;
}
#endif
@@ -645,6 +634,28 @@ 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_ZIGBEE_PROXY
case ZigbeeProxyFrame::MESSAGE_TYPE: {
ZigbeeProxyFrame msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_zigbee_proxy_frame"), msg);
#endif
this->on_zigbee_proxy_frame(msg);
break;
}
#endif
#ifdef USE_ZIGBEE_PROXY
case ZigbeeProxyRequest::MESSAGE_TYPE: {
ZigbeeProxyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_zigbee_proxy_request"), msg);
#endif
this->on_zigbee_proxy_request(msg);
break;
}
#endif
default:
break;
+35 -3
View File
@@ -15,7 +15,6 @@ class APIServerConnectionBase : public ProtoService {
void log_send_message_(const char *name, const char *dump);
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
void log_receive_message_(const LogString *name);
public:
#endif
@@ -39,22 +38,31 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_subscribe_states_request(){};
#ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){};
#endif
#ifdef USE_FAN
virtual void on_fan_command_request(const FanCommandRequest &value){};
#endif
#ifdef USE_LIGHT
virtual void on_light_command_request(const LightCommandRequest &value){};
#endif
#ifdef USE_SWITCH
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
#ifdef USE_API_NOISE
@@ -82,30 +90,38 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
#ifdef USE_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_WATER_HEATER
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_SIREN
virtual void on_siren_command_request(const SirenCommandRequest &value){};
#endif
#ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){};
#endif
@@ -114,12 +130,12 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif
#ifdef USE_MEDIA_PLAYER
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &value){};
virtual void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
@@ -130,6 +146,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif
@@ -151,10 +168,16 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_subscribe_bluetooth_connections_free_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_unsubscribe_bluetooth_le_advertisements_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
#endif
@@ -186,30 +209,39 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif
#ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATE
virtual void on_date_command_request(const DateCommandRequest &value){};
#endif
#ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif
#ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif
#ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif
@@ -131,11 +131,9 @@ void ZigbeeProxy::parse_control_byte_(uint8_t control) {
ESP_LOGD(TAG, "ACK received (piggybacked in DATA), RTT: %u ms", rtt);
}
// Send ACK immediately
this->send_ack_frame_(frame_num);
// Increment RX sequence for next frame
// Increment RX sequence and send ACK (ack_num = next expected frame)
this->increment_rx_sequence_();
this->send_ack_frame_(this->rx_sequence_);
// Extract payload (skip control byte, exclude CRC)
size_t payload_length = this->rx_buffer_index_ > 3 ? this->rx_buffer_index_ - 3 : 0;
@@ -145,10 +143,8 @@ void ZigbeeProxy::parse_control_byte_(uint8_t control) {
if (this->boot_sequence_active_ && payload_length > 0) {
this->handle_boot_data_frame_(payload, payload_length);
} else if (this->api_connection_ != nullptr && payload_length > 0) {
// Forward EZSP payload to API client
this->outgoing_proto_msg_.data = payload;
this->outgoing_proto_msg_.data_len = payload_length;
this->api_connection_->send_zigbee_proxy_frame(this->outgoing_proto_msg_);
// Forward EZSP payload to client via client-side ASH DATA frame
this->forward_ncp_data_to_client_(payload, payload_length);
}
break;
}
@@ -350,9 +346,9 @@ size_t ZigbeeProxy::build_frame_(uint8_t *output, const uint8_t *data, size_t le
break;
}
// Add control byte with stuffing
if (control == ASH_FLAG_BYTE || control == ASH_ESCAPE_BYTE || control == 0x11 || control == 0x13 || control == 0x93 ||
control == 0xA3) {
// Add control byte with stuffing (reserved: FLAG, ESCAPE, XON, XOFF, SUB, CAN)
if (control == ASH_FLAG_BYTE || control == ASH_ESCAPE_BYTE || control == 0x11 || control == 0x13 ||
control == 0x18 || control == 0x1A) {
output[pos++] = ASH_ESCAPE_BYTE;
output[pos++] = control ^ ASH_XOR_BYTE;
} else {
@@ -369,8 +365,8 @@ size_t ZigbeeProxy::build_frame_(uint8_t *output, const uint8_t *data, size_t le
// Add data payload with stuffing
for (size_t i = 0; i < length; i++) {
uint8_t byte = data[i];
if (byte == ASH_FLAG_BYTE || byte == ASH_ESCAPE_BYTE || byte == 0x11 || byte == 0x13 || byte == 0x93 ||
byte == 0xA3) {
if (byte == ASH_FLAG_BYTE || byte == ASH_ESCAPE_BYTE || byte == 0x11 || byte == 0x13 ||
byte == 0x18 || byte == 0x1A) {
output[pos++] = ASH_ESCAPE_BYTE;
output[pos++] = byte ^ ASH_XOR_BYTE;
} else {
@@ -386,15 +382,15 @@ size_t ZigbeeProxy::build_frame_(uint8_t *output, const uint8_t *data, size_t le
uint8_t crc_low = crc & 0xFF;
if (crc_high == ASH_FLAG_BYTE || crc_high == ASH_ESCAPE_BYTE || crc_high == 0x11 || crc_high == 0x13 ||
crc_high == 0x93 || crc_high == 0xA3) {
crc_high == 0x18 || crc_high == 0x1A) {
output[pos++] = ASH_ESCAPE_BYTE;
output[pos++] = crc_high ^ ASH_XOR_BYTE;
} else {
output[pos++] = crc_high;
}
if (crc_low == ASH_FLAG_BYTE || crc_low == ASH_ESCAPE_BYTE || crc_low == 0x11 || crc_low == 0x13 || crc_low == 0x93 ||
crc_low == 0xA3) {
if (crc_low == ASH_FLAG_BYTE || crc_low == ASH_ESCAPE_BYTE || crc_low == 0x11 || crc_low == 0x13 ||
crc_low == 0x18 || crc_low == 0x1A) {
output[pos++] = ASH_ESCAPE_BYTE;
output[pos++] = crc_low ^ ASH_XOR_BYTE;
} else {
+278 -10
View File
@@ -116,6 +116,7 @@ void ZigbeeProxy::zigbee_proxy_request(api::APIConnection *api_connection, const
}
ESP_LOGI(TAG, "Client subscribed");
this->api_connection_ = api_connection;
this->client_reset_session_();
break;
case api::enums::ZIGBEE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
@@ -137,13 +138,10 @@ void ZigbeeProxy::zigbee_proxy_frame(api::APIConnection *api_connection, const a
return;
}
if (this->ash_state_ != AshState::CONNECTED) {
ESP_LOGW(TAG, "Cannot send frame, NCP not connected");
return;
// Feed raw bytes into the client-side ASH parser
for (size_t i = 0; i < msg.data_len; i++) {
this->client_parse_byte_(msg.data[i]);
}
// Forward EZSP payload to NCP
this->send_frame(msg.data, msg.data_len);
}
uint64_t ZigbeeProxy::get_ieee_address() const {
@@ -259,9 +257,18 @@ void ZigbeeProxy::handle_rstack_frame_(const uint8_t *data, size_t length) {
// Now check for WiFi/Zigbee channel conflicts
this->check_wifi_zigbee_conflict_();
} else if (this->ash_state_ == AshState::CONNECTING) {
// Unexpected but valid RSTACK during connecting
// RSTACK during connecting (triggered by client RST forwarding)
ESP_LOGI(TAG, "Received RSTACK, NCP ready");
this->ash_state_ = AshState::CONNECTED;
// Forward to client if subscribed
if (this->api_connection_ != nullptr) {
this->forward_ncp_rstack_to_client_(this->rx_buffer_.data() + 1, this->rx_buffer_index_ - 3);
}
} else if (this->api_connection_ != nullptr) {
// Client is subscribed, forward RSTACK
ESP_LOGI(TAG, "Forwarding RSTACK to client");
this->ash_state_ = AshState::CONNECTED;
this->forward_ncp_rstack_to_client_(this->rx_buffer_.data() + 1, this->rx_buffer_index_ - 3);
} else {
ESP_LOGW(TAG, "Unexpected RSTACK received (boot_state=%d)", static_cast<int>(this->boot_state_));
}
@@ -308,9 +315,14 @@ void ZigbeeProxy::handle_error_frame_(const uint8_t *data, size_t length) {
ESP_LOGE(TAG, "NCP ERROR: %s (0x%02X)", error_str, error_code);
// Attempt recovery
ESP_LOGI(TAG, "Attempting recovery...");
this->reset_ash_protocol_();
if (this->api_connection_ != nullptr) {
// Forward error to client
this->forward_ncp_error_to_client_(data, length);
} else {
// No client, attempt recovery ourselves
ESP_LOGI(TAG, "Attempting recovery...");
this->reset_ash_protocol_();
}
}
bool ZigbeeProxy::send_ack_frame_(uint8_t ack_num) {
@@ -860,6 +872,262 @@ void ZigbeeProxy::process_uart_() {
}
}
// ==================== Client-side ASH session ====================
void ZigbeeProxy::client_reset_session_() {
this->client_tx_sequence_ = 0;
this->client_rx_sequence_ = 0;
this->client_rx_buffer_index_ = 0;
this->client_escape_next_byte_ = false;
this->client_ash_state_ = AshState::DISCONNECTED;
this->client_parsing_state_ = ParsingState::WAIT_FLAG_START;
ESP_LOGD(TAG, "Client ASH session reset");
}
void ZigbeeProxy::send_to_client_(const uint8_t *data, size_t length) {
if (this->api_connection_ == nullptr) {
return;
}
this->outgoing_proto_msg_.data = data;
this->outgoing_proto_msg_.data_len = length;
this->api_connection_->send_zigbee_proxy_frame(this->outgoing_proto_msg_);
}
void ZigbeeProxy::client_send_raw_frame_(const uint8_t *frame, size_t length) {
this->send_to_client_(frame, length);
}
void ZigbeeProxy::client_send_ack_frame_(uint8_t ack_num) {
uint8_t frame[8];
size_t length = this->build_frame_(frame, nullptr, 0, AshFrameType::ACK, 0, ack_num);
this->client_send_raw_frame_(frame, length);
ESP_LOGV(TAG, "Sent client ACK for frame %d", ack_num);
}
void ZigbeeProxy::client_send_rstack_frame_(uint8_t reset_code) {
// RSTACK payload: [version] [reset_code]
uint8_t payload[] = {0x02, reset_code};
uint8_t frame[16];
size_t length = this->build_frame_(frame, payload, sizeof(payload), AshFrameType::RSTACK);
this->client_send_raw_frame_(frame, length);
ESP_LOGD(TAG, "Sent client RSTACK (code=0x%02X)", reset_code);
}
void ZigbeeProxy::client_send_data_frame_(const uint8_t *data, size_t length) {
uint8_t frame[MAX_ASH_FRAME_SIZE];
size_t frame_length = this->build_frame_(frame, data, length, AshFrameType::DATA,
this->client_tx_sequence_, this->client_rx_sequence_);
this->client_tx_sequence_ = (this->client_tx_sequence_ + 1) & ASH_MAX_SEQUENCE;
this->client_send_raw_frame_(frame, frame_length);
ESP_LOGV(TAG, "Sent client DATA frame, payload %u bytes", length);
}
void ZigbeeProxy::client_send_error_frame_(uint8_t error_code) {
uint8_t payload[] = {0x02, error_code};
uint8_t frame[16];
size_t length = this->build_frame_(frame, payload, sizeof(payload), AshFrameType::ERROR);
this->client_send_raw_frame_(frame, length);
ESP_LOGD(TAG, "Sent client ERROR (code=0x%02X)", error_code);
}
void ZigbeeProxy::forward_ncp_data_to_client_(const uint8_t *payload, size_t length) {
this->client_send_data_frame_(payload, length);
}
void ZigbeeProxy::forward_ncp_rstack_to_client_(const uint8_t *data, size_t length) {
// Build and send an RSTACK frame to the client with NCP's RSTACK data
uint8_t frame[16];
size_t frame_length = this->build_frame_(frame, data, length, AshFrameType::RSTACK);
this->client_send_raw_frame_(frame, frame_length);
// Reset client-side sequence numbers since RSTACK means new session
this->client_tx_sequence_ = 0;
this->client_rx_sequence_ = 0;
this->client_ash_state_ = AshState::CONNECTED;
ESP_LOGD(TAG, "Forwarded RSTACK to client");
}
void ZigbeeProxy::forward_ncp_error_to_client_(const uint8_t *data, size_t length) {
uint8_t frame[16];
size_t frame_length = this->build_frame_(frame, data, length, AshFrameType::ERROR);
this->client_send_raw_frame_(frame, frame_length);
ESP_LOGD(TAG, "Forwarded ERROR to client");
}
void ZigbeeProxy::client_parse_byte_(uint8_t byte) {
static constexpr uint8_t ASH_CAN_BYTE = 0x1A;
// CAN byte resets parser
if (byte == ASH_CAN_BYTE) {
this->client_rx_buffer_index_ = 0;
this->client_escape_next_byte_ = false;
this->client_parsing_state_ = ParsingState::WAIT_FLAG_START;
return;
}
switch (this->client_parsing_state_) {
case ParsingState::WAIT_FLAG_START:
if (byte == ASH_ESCAPE_BYTE) {
this->client_escape_next_byte_ = true;
return;
}
if (this->client_escape_next_byte_) {
byte ^= ASH_XOR_BYTE;
this->client_escape_next_byte_ = false;
}
if (byte == ASH_FLAG_BYTE) {
this->client_rx_buffer_index_ = 0;
this->client_escape_next_byte_ = false;
this->client_parsing_state_ = ParsingState::WAIT_CONTROL;
} else if ((byte & 0x80) != 0 || this->client_ash_state_ == AshState::CONNECTED) {
// Accept control byte without leading FLAG
this->client_rx_buffer_index_ = 0;
this->client_rx_buffer_[this->client_rx_buffer_index_++] = byte;
this->client_parsing_state_ = ParsingState::WAIT_DATA;
}
break;
case ParsingState::WAIT_CONTROL:
if (byte == ASH_FLAG_BYTE) {
// Empty frame or repeated FLAG
this->client_rx_buffer_index_ = 0;
return;
}
if (byte == ASH_ESCAPE_BYTE) {
this->client_escape_next_byte_ = true;
return;
}
if (this->client_escape_next_byte_) {
byte ^= ASH_XOR_BYTE;
this->client_escape_next_byte_ = false;
}
this->client_rx_buffer_[this->client_rx_buffer_index_++] = byte;
this->client_parsing_state_ = ParsingState::WAIT_DATA;
break;
case ParsingState::WAIT_DATA:
if (byte == ASH_FLAG_BYTE) {
// End of frame - validate and process
if (this->client_validate_frame_crc_()) {
this->client_parse_control_byte_(this->client_rx_buffer_[0]);
} else {
ESP_LOGW(TAG, "Client frame CRC failed (%u bytes)", this->client_rx_buffer_index_);
}
this->client_parsing_state_ = ParsingState::WAIT_FLAG_START;
return;
}
if (byte == ASH_ESCAPE_BYTE) {
this->client_escape_next_byte_ = true;
return;
}
if (this->client_escape_next_byte_) {
byte ^= ASH_XOR_BYTE;
this->client_escape_next_byte_ = false;
}
if (this->client_rx_buffer_index_ >= MAX_ASH_FRAME_SIZE) {
ESP_LOGE(TAG, "Client RX buffer overflow");
this->client_parsing_state_ = ParsingState::WAIT_FLAG_START;
return;
}
this->client_rx_buffer_[this->client_rx_buffer_index_++] = byte;
break;
default:
this->client_parsing_state_ = ParsingState::WAIT_FLAG_START;
break;
}
}
bool ZigbeeProxy::client_validate_frame_crc_() {
if (this->client_rx_buffer_index_ < 3) {
return false;
}
uint16_t calculated = this->calculate_crc_(this->client_rx_buffer_.data(), this->client_rx_buffer_index_ - 2);
uint16_t received = (static_cast<uint16_t>(this->client_rx_buffer_[this->client_rx_buffer_index_ - 2]) << 8) |
this->client_rx_buffer_[this->client_rx_buffer_index_ - 1];
return calculated == received;
}
void ZigbeeProxy::client_parse_control_byte_(uint8_t control) {
AshFrameType frame_type;
if ((control & 0x80) == 0) {
frame_type = AshFrameType::DATA;
} else if ((control & 0xC0) == 0x80) {
frame_type = ((control & 0x20) == 0) ? AshFrameType::ACK : AshFrameType::NAK;
} else {
uint8_t control_bits = control & 0x07;
if (control_bits == 0x00) {
frame_type = AshFrameType::RST;
} else if (control_bits == 0x01) {
frame_type = AshFrameType::RSTACK;
} else if (control_bits == 0x02) {
frame_type = AshFrameType::ERROR;
} else {
ESP_LOGW(TAG, "Client: unknown control frame type: 0x%02X", control);
return;
}
}
uint8_t frame_num = (control >> 4) & 0x07;
uint8_t ack_num = control & 0x07;
switch (frame_type) {
case AshFrameType::DATA: {
// Verify sequence number
if (frame_num != this->client_rx_sequence_) {
ESP_LOGW(TAG, "Client: out of sequence DATA frame: expected %d, got %d", this->client_rx_sequence_, frame_num);
return;
}
this->client_rx_sequence_ = (this->client_rx_sequence_ + 1) & ASH_MAX_SEQUENCE;
// Extract EZSP payload (skip control byte, exclude CRC)
size_t payload_length = this->client_rx_buffer_index_ > 3 ? this->client_rx_buffer_index_ - 3 : 0;
const uint8_t *payload = this->client_rx_buffer_.data() + 1;
if (payload_length > 0) {
// Forward EZSP payload to NCP via right-side ASH
ESP_LOGV(TAG, "Client DATA → NCP, EZSP payload %u bytes", payload_length);
this->send_frame(payload, payload_length);
}
break;
}
case AshFrameType::ACK:
// Client ACKed our data - nothing to retransmit on client side for now
ESP_LOGV(TAG, "Client ACK received for frame %d", ack_num);
break;
case AshFrameType::NAK:
ESP_LOGW(TAG, "Client NAK received for frame %d", ack_num);
break;
case AshFrameType::RST:
// Client wants to reset - forward RST to NCP
// Don't use reset_ash_protocol_() as that enters boot sequence
ESP_LOGI(TAG, "Client RST → forwarding to NCP");
this->ash_state_ = AshState::CONNECTING;
this->tx_sequence_ = 0;
this->rx_sequence_ = 0;
this->tx_buffer_pending_ = false;
this->tx_retry_count_ = 0;
this->parsing_state_ = ParsingState::WAIT_FLAG_START;
this->client_reset_session_();
this->send_rst_frame_();
break;
case AshFrameType::RSTACK:
// Client shouldn't send RSTACK, ignore
ESP_LOGW(TAG, "Client sent unexpected RSTACK");
break;
case AshFrameType::ERROR:
// Client sent error, log it
ESP_LOGW(TAG, "Client sent ERROR frame");
break;
}
}
} // namespace zigbee_proxy
} // namespace esphome
+44 -9
View File
@@ -142,14 +142,37 @@ class ZigbeeProxy : public uart::UARTDevice, public Component {
// UART processing
void process_uart_();
// Client-side (left) ASH session
void client_parse_byte_(uint8_t byte);
void client_parse_control_byte_(uint8_t control);
bool client_validate_frame_crc_();
void client_send_ack_frame_(uint8_t ack_num);
void client_send_rstack_frame_(uint8_t reset_code);
void client_send_data_frame_(const uint8_t *data, size_t length);
void client_send_error_frame_(uint8_t error_code);
void client_send_raw_frame_(const uint8_t *frame, size_t length);
void client_reset_session_();
// Send raw bytes to API client
void send_to_client_(const uint8_t *data, size_t length);
// Forward NCP frames to client
void forward_ncp_data_to_client_(const uint8_t *payload, size_t length);
void forward_ncp_rstack_to_client_(const uint8_t *data, size_t length);
void forward_ncp_error_to_client_(const uint8_t *data, size_t length);
// Pre-allocated message - always ready to send
api::ZigbeeProxyFrame outgoing_proto_msg_;
// Fixed-size buffers for incoming and outgoing data
// NCP-side (right) ASH buffers
std::array<uint8_t, MAX_ASH_FRAME_SIZE> rx_buffer_;
std::array<uint8_t, MAX_ASH_FRAME_SIZE> tx_buffer_;
std::array<uint8_t, MAX_ASH_FRAME_SIZE> tx_pending_buffer_; // For retransmission
// Client-side (left) ASH buffers
std::array<uint8_t, MAX_ASH_FRAME_SIZE> client_rx_buffer_;
std::array<uint8_t, MAX_ASH_FRAME_SIZE> client_tx_buffer_;
// Network information
NetworkInfo network_info_;
@@ -159,37 +182,49 @@ class ZigbeeProxy : public uart::UARTDevice, public Component {
// Pointers (aligned together)
api::APIConnection *api_connection_{nullptr}; // Current subscribed client
// 32-bit values
// NCP-side (right) 32-bit values
uint32_t setup_time_{0}; // Time when setup() was called
uint32_t ack_timer_start_{0}; // Time when ACK timer started
uint32_t last_rtt_ms_{0}; // Last measured round-trip time
// 16-bit values
// NCP-side (right) 16-bit values
uint16_t rx_buffer_index_{0}; // Index for populating rx_buffer_
uint16_t tx_pending_length_{0}; // Length of pending TX frame for retransmission
uint16_t calculated_crc_{0}; // CRC calculated during frame reception
// 8-bit values (grouped together to minimize padding)
// Client-side (left) 16-bit values
uint16_t client_rx_buffer_index_{0};
// NCP-side (right) 8-bit values
uint8_t tx_sequence_{0}; // TX sequence number (0-7)
uint8_t rx_sequence_{0}; // RX sequence number (0-7)
uint8_t tx_retry_count_{0}; // Number of retransmission attempts
uint8_t tx_pending_frame_num_{0}; // Frame number of pending TX frame
uint8_t last_ack_sent_{0}; // Last ACK number sent
// Enums and booleans
// Client-side (left) 8-bit values
uint8_t client_tx_sequence_{0}; // Client-facing TX sequence (proxy → client)
uint8_t client_rx_sequence_{0}; // Client-facing RX sequence (client → proxy)
// NCP-side enums and booleans
AshState ash_state_{AshState::DISCONNECTED};
ParsingState parsing_state_{ParsingState::WAIT_FLAG_START};
BootloaderState bootloader_state_{BootloaderState::NORMAL};
BootState boot_state_{BootState::IDLE};
// Client-side enums and booleans
AshState client_ash_state_{AshState::DISCONNECTED};
ParsingState client_parsing_state_{ParsingState::WAIT_FLAG_START};
uint8_t ezsp_version_{0}; // NCP's EZSP protocol version
uint8_t ezsp_sequence_{0}; // EZSP frame sequence number
uint8_t ezsp_requested_version_{0}; // Version we last requested (for re-negotiation)
bool tx_buffer_pending_{false}; // True if waiting for ACK
bool escape_next_byte_{false}; // True if next byte should be unescaped
bool network_info_ready_{false}; // True when network info retrieved
bool boot_sequence_active_{false}; // True during boot-time init
bool tx_buffer_pending_{false}; // True if waiting for ACK from NCP
bool escape_next_byte_{false}; // True if next NCP byte should be unescaped
bool client_escape_next_byte_{false}; // True if next client byte should be unescaped
bool network_info_ready_{false}; // True when network info retrieved
bool boot_sequence_active_{false}; // True during boot-time init
};
extern ZigbeeProxy *global_zigbee_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)