diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b30b060156..0b6690dcb6 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -100,14 +100,22 @@ const FieldMetaV3 DeviceInfoResponse::FIELDS_V3[20] = { {16, 40, {.offset = PROTO_FIELD_OFFSET(DeviceInfoResponse, suggested_area)}}, {18, 40, {.offset = PROTO_FIELD_OFFSET(DeviceInfoResponse, bluetooth_mac_address)}}, {19, 32, {.offset = PROTO_FIELD_OFFSET(DeviceInfoResponse, api_encryption_supported)}}, - {22, 42, {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, area)), .message_type_id = 9}}}; + {22, + 42, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, area) & 0xFF), + .message_type_id = + static_cast(9 | (((PROTO_FIELD_OFFSET(DeviceInfoResponse, area) >> 8) & 0x0F) << 4))}}}; const RepeatedFieldMetaV3 DeviceInfoResponse::REPEATED_FIELDS_V3[2] = { {20, 42, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, devices)), .message_type_id = 0}}, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, devices) & 0xFF), + .message_type_id = + static_cast(0 | (((PROTO_FIELD_OFFSET(DeviceInfoResponse, devices) >> 8) & 0x0F) << 4))}}, {21, 42, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, areas)), .message_type_id = 1}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(DeviceInfoResponse, areas) & 0xFF), + .message_type_id = + static_cast(1 | (((PROTO_FIELD_OFFSET(DeviceInfoResponse, areas) >> 8) & 0x0F) << 4))}}}; #ifdef USE_BINARY_SENSOR const FieldMetaV3 ListEntitiesBinarySensorResponse::FIELDS_V3[10] = { {1, 8, {.offset = PROTO_FIELD_OFFSET(ListEntitiesBinarySensorResponse, object_id)}}, @@ -342,16 +350,19 @@ const FieldMetaV3 HomeassistantServiceResponse::FIELDS_V3[2] = { const RepeatedFieldMetaV3 HomeassistantServiceResponse::REPEATED_FIELDS_V3[3] = { {2, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data)), - .message_type_id = 2}}, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data) & 0xFF), + .message_type_id = + static_cast(2 | (((PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data) >> 8) & 0x0F) << 4))}}, {3, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data_template)), - .message_type_id = 2}}, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data_template) & 0xFF), + .message_type_id = static_cast( + 2 | (((PROTO_FIELD_OFFSET(HomeassistantServiceResponse, data_template) >> 8) & 0x0F) << 4))}}, {4, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, variables)), - .message_type_id = 2}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(HomeassistantServiceResponse, variables) & 0xFF), + .message_type_id = static_cast( + 2 | (((PROTO_FIELD_OFFSET(HomeassistantServiceResponse, variables) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 SubscribeHomeAssistantStateResponse::FIELDS_V3[3] = { {1, 8, {.offset = PROTO_FIELD_OFFSET(SubscribeHomeAssistantStateResponse, entity_id)}}, {2, 8, {.offset = PROTO_FIELD_OFFSET(SubscribeHomeAssistantStateResponse, attribute)}}, @@ -371,8 +382,9 @@ const FieldMetaV3 ListEntitiesServicesResponse::FIELDS_V3[2] = { const RepeatedFieldMetaV3 ListEntitiesServicesResponse::REPEATED_FIELDS_V3[1] = { {3, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(ListEntitiesServicesResponse, args)), - .message_type_id = 3}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(ListEntitiesServicesResponse, args) & 0xFF), + .message_type_id = + static_cast(3 | (((PROTO_FIELD_OFFSET(ListEntitiesServicesResponse, args) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 ExecuteServiceArgument::FIELDS_V3[5] = { {1, 0, {.offset = PROTO_FIELD_OFFSET(ExecuteServiceArgument, bool_)}}, {2, 1, {.offset = PROTO_FIELD_OFFSET(ExecuteServiceArgument, legacy_int)}}, @@ -389,7 +401,9 @@ const FieldMetaV3 ExecuteServiceRequest::FIELDS_V3[1] = { const RepeatedFieldMetaV3 ExecuteServiceRequest::REPEATED_FIELDS_V3[1] = { {2, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(ExecuteServiceRequest, args)), .message_type_id = 4}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(ExecuteServiceRequest, args) & 0xFF), + .message_type_id = + static_cast(4 | (((PROTO_FIELD_OFFSET(ExecuteServiceRequest, args) >> 8) & 0x0F) << 4))}}}; #ifdef USE_CAMERA const FieldMetaV3 ListEntitiesCameraResponse::FIELDS_V3[8] = { {1, 8, {.offset = PROTO_FIELD_OFFSET(ListEntitiesCameraResponse, object_id)}}, @@ -612,8 +626,9 @@ const FieldMetaV3 ListEntitiesMediaPlayerResponse::FIELDS_V3[9] = { const RepeatedFieldMetaV3 ListEntitiesMediaPlayerResponse::REPEATED_FIELDS_V3[1] = { {9, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(ListEntitiesMediaPlayerResponse, supported_formats)), - .message_type_id = 5}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(ListEntitiesMediaPlayerResponse, supported_formats) & 0xFF), + .message_type_id = static_cast( + 5 | (((PROTO_FIELD_OFFSET(ListEntitiesMediaPlayerResponse, supported_formats) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 MediaPlayerStateResponse::FIELDS_V3[5] = { {1, 12, {.offset = PROTO_FIELD_OFFSET(MediaPlayerStateResponse, key)}}, {2, 7, {.offset = PROTO_FIELD_OFFSET(MediaPlayerStateResponse, state)}}, @@ -648,12 +663,15 @@ const RepeatedFieldMetaV3 BluetoothLEAdvertisementResponse::REPEATED_FIELDS_V3[3 {4, 8, {.offset = PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, service_uuids)}}, {5, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, service_data)), - .message_type_id = 6}}, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, service_data) & 0xFF), + .message_type_id = static_cast( + 6 | (((PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, service_data) >> 8) & 0x0F) << 4))}}, {6, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, manufacturer_data)), - .message_type_id = 6}}}; + {.offset_low = + static_cast(PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, manufacturer_data) & 0xFF), + .message_type_id = static_cast( + 6 | (((PROTO_FIELD_OFFSET(BluetoothLEAdvertisementResponse, manufacturer_data) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 BluetoothLERawAdvertisement::FIELDS_V3[4] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothLERawAdvertisement, address)}}, {2, 5, {.offset = PROTO_FIELD_OFFSET(BluetoothLERawAdvertisement, rssi)}}, @@ -662,8 +680,10 @@ const FieldMetaV3 BluetoothLERawAdvertisement::FIELDS_V3[4] = { const RepeatedFieldMetaV3 BluetoothLERawAdvertisementsResponse::REPEATED_FIELDS_V3[1] = { {1, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothLERawAdvertisementsResponse, advertisements)), - .message_type_id = 7}}}; + {.offset_low = + static_cast(PROTO_FIELD_OFFSET(BluetoothLERawAdvertisementsResponse, advertisements) & 0xFF), + .message_type_id = static_cast( + 7 | (((PROTO_FIELD_OFFSET(BluetoothLERawAdvertisementsResponse, advertisements) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 BluetoothDeviceRequest::FIELDS_V3[4] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothDeviceRequest, address)}}, {2, 7, {.offset = PROTO_FIELD_OFFSET(BluetoothDeviceRequest, request_type)}}, @@ -687,23 +707,26 @@ const RepeatedFieldMetaV3 BluetoothGATTCharacteristic::REPEATED_FIELDS_V3[2] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothGATTCharacteristic, uuid)}}, {4, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTCharacteristic, descriptors)), - .message_type_id = 8}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTCharacteristic, descriptors) & 0xFF), + .message_type_id = static_cast( + 8 | (((PROTO_FIELD_OFFSET(BluetoothGATTCharacteristic, descriptors) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 BluetoothGATTService::FIELDS_V3[1] = { {2, 2, {.offset = PROTO_FIELD_OFFSET(BluetoothGATTService, handle)}}}; const RepeatedFieldMetaV3 BluetoothGATTService::REPEATED_FIELDS_V3[2] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothGATTService, uuid)}}, {3, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTService, characteristics)), - .message_type_id = 9}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTService, characteristics) & 0xFF), + .message_type_id = + static_cast(9 | (((PROTO_FIELD_OFFSET(BluetoothGATTService, characteristics) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 BluetoothGATTGetServicesResponse::FIELDS_V3[1] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothGATTGetServicesResponse, address)}}}; const RepeatedFieldMetaV3 BluetoothGATTGetServicesResponse::REPEATED_FIELDS_V3[1] = { {2, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTGetServicesResponse, services)), - .message_type_id = 10}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(BluetoothGATTGetServicesResponse, services) & 0xFF), + .message_type_id = static_cast( + 10 | (((PROTO_FIELD_OFFSET(BluetoothGATTGetServicesResponse, services) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 BluetoothGATTGetServicesDoneResponse::FIELDS_V3[1] = { {1, 4, {.offset = PROTO_FIELD_OFFSET(BluetoothGATTGetServicesDoneResponse, address)}}}; const FieldMetaV3 BluetoothGATTReadRequest::FIELDS_V3[2] = { @@ -780,8 +803,9 @@ const FieldMetaV3 VoiceAssistantRequest::FIELDS_V3[5] = { {3, 2, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantRequest, flags)}}, {4, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(VoiceAssistantRequest, audio_settings)), - .message_type_id = 105}}, + {.offset_low = static_cast(PROTO_FIELD_OFFSET(VoiceAssistantRequest, audio_settings) & 0xFF), + .message_type_id = static_cast( + 105 | (((PROTO_FIELD_OFFSET(VoiceAssistantRequest, audio_settings) >> 8) & 0x0F) << 4))}}, {5, 8, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantRequest, wake_word_phrase)}}}; const FieldMetaV3 VoiceAssistantResponse::FIELDS_V3[2] = { {1, 2, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantResponse, port)}}, @@ -794,8 +818,9 @@ const FieldMetaV3 VoiceAssistantEventResponse::FIELDS_V3[1] = { const RepeatedFieldMetaV3 VoiceAssistantEventResponse::REPEATED_FIELDS_V3[1] = { {2, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(VoiceAssistantEventResponse, data)), - .message_type_id = 11}}}; + {.offset_low = static_cast(PROTO_FIELD_OFFSET(VoiceAssistantEventResponse, data) & 0xFF), + .message_type_id = + static_cast(11 | (((PROTO_FIELD_OFFSET(VoiceAssistantEventResponse, data) >> 8) & 0x0F) << 4))}}}; const FieldMetaV3 VoiceAssistantAudio::FIELDS_V3[2] = { {1, 9, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantAudio, data)}}, {2, 0, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantAudio, end)}}}; @@ -823,8 +848,10 @@ const FieldMetaV3 VoiceAssistantConfigurationResponse::FIELDS_V3[1] = { const RepeatedFieldMetaV3 VoiceAssistantConfigurationResponse::REPEATED_FIELDS_V3[2] = { {1, 10, - {.offset_low = static_cast(PROTO_FIELD_OFFSET(VoiceAssistantConfigurationResponse, available_wake_words)), - .message_type_id = 12}}, + {.offset_low = + static_cast(PROTO_FIELD_OFFSET(VoiceAssistantConfigurationResponse, available_wake_words) & 0xFF), + .message_type_id = static_cast( + 12 | (((PROTO_FIELD_OFFSET(VoiceAssistantConfigurationResponse, available_wake_words) >> 8) & 0x0F) << 4))}}, {2, 8, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantConfigurationResponse, active_wake_words)}}}; const RepeatedFieldMetaV3 VoiceAssistantSetConfiguration::REPEATED_FIELDS_V3[1] = { {1, 8, {.offset = PROTO_FIELD_OFFSET(VoiceAssistantSetConfiguration, active_wake_words)}}}; diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 6cf5820ea8..b6e0848d98 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -284,9 +284,10 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { case ProtoFieldType::TYPE_MESSAGE: { // Use repeated message handler registry uint8_t handler_id = repeated_fields[j].get_message_type_id(); - ESP_LOGD(TAG, "Repeated TYPE_MESSAGE field %d, handler_id=%d, REPEATED_MESSAGE_HANDLER_COUNT=%zu", + ESP_LOGD(TAG, "Repeated TYPE_MESSAGE field %d, handler_id=%d, REPEATED_MESSAGE_HANDLER_COUNT=%zu", field_id, handler_id, REPEATED_MESSAGE_HANDLER_COUNT); - if (handler_id < REPEATED_MESSAGE_HANDLER_COUNT && REPEATED_MESSAGE_HANDLERS[handler_id].decode != nullptr) { + if (handler_id < REPEATED_MESSAGE_HANDLER_COUNT && + REPEATED_MESSAGE_HANDLERS[handler_id].decode != nullptr) { decoded = REPEATED_MESSAGE_HANDLERS[handler_id].decode(field_addr, value); ESP_LOGD(TAG, "Decoded repeated message field %d: %s", field_id, decoded ? "success" : "failed"); } else { @@ -583,8 +584,9 @@ void ProtoMessage::encode(ProtoWriteBuffer buffer) const { } case ProtoFieldType::TYPE_MESSAGE: { // Use message handler registry - if (fields[i].get_message_type_id() < MESSAGE_HANDLER_COUNT) { - MESSAGE_HANDLERS[fields[i].get_message_type_id()].encode(buffer, field_addr, fields[i].field_num); + uint8_t handler_id = fields[i].get_message_type_id(); + if (handler_id < MESSAGE_HANDLER_COUNT && MESSAGE_HANDLERS[handler_id].encode != nullptr) { + MESSAGE_HANDLERS[handler_id].encode(buffer, field_addr, fields[i].field_num); } break; } @@ -821,9 +823,9 @@ void ProtoMessage::calculate_size(uint32_t &total_size) const { } case ProtoFieldType::TYPE_MESSAGE: { // Use message handler registry - if (fields[i].get_message_type_id() < MESSAGE_HANDLER_COUNT) { - MESSAGE_HANDLERS[fields[i].get_message_type_id()].size(total_size, field_addr, - fields[i].get_precalced_size(), false); + uint8_t handler_id = fields[i].get_message_type_id(); + if (handler_id < MESSAGE_HANDLER_COUNT && MESSAGE_HANDLERS[handler_id].size != nullptr) { + MESSAGE_HANDLERS[handler_id].size(total_size, field_addr, fields[i].get_precalced_size(), false); } break; } @@ -834,8 +836,8 @@ void ProtoMessage::calculate_size(uint32_t &total_size) const { const RepeatedFieldMetaV3 *repeated_fields = get_repeated_field_metadata_v3(); size_t repeated_count = get_repeated_field_count_v3(); - for (size_t i = 0; i < repeated_count; i++) { - if (repeated_fields != nullptr) { + if (repeated_fields != nullptr) { + for (size_t i = 0; i < repeated_count; i++) { const void *field_addr = base + repeated_fields[i].get_offset(); switch (repeated_fields[i].get_type()) { diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 516655a75e..eab927b396 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -298,11 +298,13 @@ struct FieldMetaV3 { uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { - return offset_low; // Limited to 255 for messages + // Reconstruct full offset from packed fields + // Bits 0-7 from offset_low, bits 8-11 from upper nibble of message_type_id + return static_cast(offset_low) | (static_cast(message_type_id & 0xF0) << 4); } return offset; } - uint8_t get_message_type_id() const { return message_type_id; } + uint8_t get_message_type_id() const { return message_type_id & 0x0F; } }; // V2 structures removed - we only use V3 now @@ -484,11 +486,13 @@ struct RepeatedFieldMetaV3 { uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { - return offset_low; // Limited to 255 for messages + // Reconstruct full offset from packed fields + // Bits 0-7 from offset_low, bits 8-11 from upper nibble of message_type_id + return static_cast(offset_low) | (static_cast(message_type_id & 0xF0) << 4); } return offset; } - uint8_t get_message_type_id() const { return message_type_id; } + uint8_t get_message_type_id() const { return message_type_id & 0x0F; } }; // V2 structures removed - we only use V3 now diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index bf4988582b..b44259237c 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1344,11 +1344,11 @@ def build_message_type( type_and_size = (type_num & 0x1F) | ((field_tag_size - 1) << 5) if field.type == descriptor.FieldDescriptorProto.TYPE_MESSAGE: - # For messages, use offset_low and message_type_id + # For messages, use offset_low and message_type_id with offset extension message_type_id = get_repeated_message_type_id(ti._ti.type_name) offset = f"PROTO_FIELD_OFFSET({desc.name}, {ti.field_name})" repeated_fields_v3.append( - f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset}), .message_type_id = {message_type_id}}}}}" + f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset} & 0xFF), .message_type_id = static_cast({message_type_id} | ((({offset} >> 8) & 0x0F) << 4))}}}}" ) else: # Non-message types use full offset @@ -1369,9 +1369,13 @@ def build_message_type( # For messages, use offset_low and message_type_id message_type_id = get_message_type_id(ti.type_name) offset = f"PROTO_FIELD_OFFSET({desc.name}, {ti.field_name})" - # Check if offset fits in 8 bits + + # Since we have so few message types, we can use the upper bits of + # message_type_id to extend the offset range + # Bits 0-3: actual message type ID (supports 16 types) + # Bits 4-7: bits 8-11 of offset (extends offset to 12 bits = 4096) regular_fields_v3.append( - f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset}), .message_type_id = {message_type_id}}}}}" + f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset} & 0xFF), .message_type_id = static_cast({message_type_id} | ((({offset} >> 8) & 0x0F) << 4))}}}}" ) else: # Non-message types use full offset @@ -1391,8 +1395,9 @@ def build_message_type( type_and_size = (10 & 0x1F) | ((field_tag_size - 1) << 5) message_type_id = get_message_type_id(ti.type_name) offset = f"PROTO_FIELD_OFFSET({desc.name}, {ti.field_name})" + # Same encoding as above for large offsets regular_fields_v3.append( - f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset}), .message_type_id = {message_type_id}}}}}" + f"{{{field.number}, {type_and_size}, {{.offset_low = static_cast({offset} & 0xFF), .message_type_id = static_cast({message_type_id} | ((({offset} >> 8) & 0x0F) << 4))}}}}" ) # Store metadata info for later generation outside the class