mirror of
https://github.com/esphome/esphome.git
synced 2025-07-31 07:36:35 +00:00
Merge remote-tracking branch 'upstream/dev' into frame_helper_dupe_name_storage
This commit is contained in:
commit
72fcb29fd2
95
.github/workflows/codeowner-review-request.yml
vendored
95
.github/workflows/codeowner-review-request.yml
vendored
@ -178,6 +178,51 @@ jobs:
|
||||
reviewedUsers.add(review.user.login);
|
||||
});
|
||||
|
||||
// Check for previous comments from this workflow to avoid duplicate pings
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number
|
||||
});
|
||||
|
||||
const previouslyPingedUsers = new Set();
|
||||
const previouslyPingedTeams = new Set();
|
||||
|
||||
// Look for comments from github-actions bot that contain codeowner pings
|
||||
const workflowComments = comments.filter(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.user.login === 'github-actions[bot]' &&
|
||||
comment.body.includes("I've automatically requested reviews from codeowners")
|
||||
);
|
||||
|
||||
// Extract previously mentioned users and teams from workflow comments
|
||||
for (const comment of workflowComments) {
|
||||
// Match @username patterns (not team mentions)
|
||||
const userMentions = comment.body.match(/@([a-zA-Z0-9_.-]+)(?![/])/g) || [];
|
||||
userMentions.forEach(mention => {
|
||||
const username = mention.slice(1); // remove @
|
||||
previouslyPingedUsers.add(username);
|
||||
});
|
||||
|
||||
// Match @org/team patterns
|
||||
const teamMentions = comment.body.match(/@[a-zA-Z0-9_.-]+\/([a-zA-Z0-9_.-]+)/g) || [];
|
||||
teamMentions.forEach(mention => {
|
||||
const teamName = mention.split('/')[1];
|
||||
previouslyPingedTeams.add(teamName);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Found ${previouslyPingedUsers.size} previously pinged users and ${previouslyPingedTeams.size} previously pinged teams`);
|
||||
|
||||
// Remove users who have already been pinged in previous workflow comments
|
||||
previouslyPingedUsers.forEach(user => {
|
||||
matchedOwners.delete(user);
|
||||
});
|
||||
|
||||
previouslyPingedTeams.forEach(team => {
|
||||
matchedTeams.delete(team);
|
||||
});
|
||||
|
||||
// Remove only users who have already submitted reviews (not just requested reviewers)
|
||||
reviewedUsers.forEach(reviewer => {
|
||||
matchedOwners.delete(reviewer);
|
||||
@ -192,7 +237,7 @@ jobs:
|
||||
const teamsList = Array.from(matchedTeams);
|
||||
|
||||
if (reviewersList.length === 0 && teamsList.length === 0) {
|
||||
console.log('No eligible reviewers found (all may already be requested or reviewed)');
|
||||
console.log('No eligible reviewers found (all may already be requested, reviewed, or previously pinged)');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -227,31 +272,41 @@ jobs:
|
||||
console.log('All codeowners are already requested reviewers or have reviewed');
|
||||
}
|
||||
|
||||
// Add a comment to the PR mentioning what happened (include all matched codeowners)
|
||||
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, true);
|
||||
// Only add a comment if there are new codeowners to mention (not previously pinged)
|
||||
if (reviewersList.length > 0 || teamsList.length > 0) {
|
||||
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, true);
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
body: commentBody
|
||||
});
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
body: commentBody
|
||||
});
|
||||
console.log(`Added comment mentioning ${reviewersList.length} users and ${teamsList.length} teams`);
|
||||
} else {
|
||||
console.log('No new codeowners to mention in comment (all previously pinged)');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.status === 422) {
|
||||
console.log('Some reviewers may already be requested or unavailable:', error.message);
|
||||
|
||||
// Try to add a comment even if review request failed
|
||||
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, false);
|
||||
// Only try to add a comment if there are new codeowners to mention
|
||||
if (reviewersList.length > 0 || teamsList.length > 0) {
|
||||
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, false);
|
||||
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
body: commentBody
|
||||
});
|
||||
} catch (commentError) {
|
||||
console.log('Failed to add comment:', commentError.message);
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
body: commentBody
|
||||
});
|
||||
console.log(`Added fallback comment mentioning ${reviewersList.length} users and ${teamsList.length} teams`);
|
||||
} catch (commentError) {
|
||||
console.log('Failed to add comment:', commentError.message);
|
||||
}
|
||||
} else {
|
||||
console.log('No new codeowners to mention in fallback comment');
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
|
45
.github/workflows/issue-codeowner-notify.yml
vendored
45
.github/workflows/issue-codeowner-notify.yml
vendored
@ -92,10 +92,49 @@ jobs:
|
||||
mention !== `@${issueAuthor}`
|
||||
);
|
||||
|
||||
const allMentions = [...filteredUserOwners, ...teamOwners];
|
||||
// Check for previous comments from this workflow to avoid duplicate pings
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue_number
|
||||
});
|
||||
|
||||
const previouslyPingedUsers = new Set();
|
||||
const previouslyPingedTeams = new Set();
|
||||
|
||||
// Look for comments from github-actions bot that contain codeowner pings for this component
|
||||
const workflowComments = comments.filter(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.user.login === 'github-actions[bot]' &&
|
||||
comment.body.includes(`component: ${componentName}`) &&
|
||||
comment.body.includes("you've been identified as a codeowner")
|
||||
);
|
||||
|
||||
// Extract previously mentioned users and teams from workflow comments
|
||||
for (const comment of workflowComments) {
|
||||
// Match @username patterns (not team mentions)
|
||||
const userMentions = comment.body.match(/@([a-zA-Z0-9_.-]+)(?![/])/g) || [];
|
||||
userMentions.forEach(mention => {
|
||||
previouslyPingedUsers.add(mention); // Keep @ prefix for easy comparison
|
||||
});
|
||||
|
||||
// Match @org/team patterns
|
||||
const teamMentions = comment.body.match(/@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+/g) || [];
|
||||
teamMentions.forEach(mention => {
|
||||
previouslyPingedTeams.add(mention);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Found ${previouslyPingedUsers.size} previously pinged users and ${previouslyPingedTeams.size} previously pinged teams for component ${componentName}`);
|
||||
|
||||
// Remove previously pinged users and teams
|
||||
const newUserOwners = filteredUserOwners.filter(mention => !previouslyPingedUsers.has(mention));
|
||||
const newTeamOwners = teamOwners.filter(mention => !previouslyPingedTeams.has(mention));
|
||||
|
||||
const allMentions = [...newUserOwners, ...newTeamOwners];
|
||||
|
||||
if (allMentions.length === 0) {
|
||||
console.log('No codeowners to notify (issue author is the only codeowner)');
|
||||
console.log('No new codeowners to notify (all previously pinged or issue author is the only codeowner)');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -111,7 +150,7 @@ jobs:
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
console.log(`Successfully notified codeowners: ${mentionString}`);
|
||||
console.log(`Successfully notified new codeowners: ${mentionString}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Failed to process codeowner notifications:', error.message);
|
||||
|
@ -230,14 +230,16 @@ message DeviceInfoResponse {
|
||||
|
||||
uint32 webserver_port = 10 [(field_ifdef) = "USE_WEBSERVER"];
|
||||
|
||||
uint32 legacy_bluetooth_proxy_version = 11 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
||||
// Deprecated in API version 1.9
|
||||
uint32 legacy_bluetooth_proxy_version = 11 [deprecated=true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
||||
uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
||||
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
|
||||
uint32 legacy_voice_assistant_version = 14 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
|
||||
// Deprecated in API version 1.10
|
||||
uint32 legacy_voice_assistant_version = 14 [deprecated=true, (field_ifdef) = "USE_VOICE_ASSISTANT"];
|
||||
uint32 voice_assistant_feature_flags = 17 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
|
||||
|
||||
string suggested_area = 16 [(field_ifdef) = "USE_AREAS"];
|
||||
@ -337,7 +339,9 @@ message ListEntitiesCoverResponse {
|
||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
// Deprecated in API version 1.1
|
||||
enum LegacyCoverState {
|
||||
option deprecated = true;
|
||||
LEGACY_COVER_STATE_OPEN = 0;
|
||||
LEGACY_COVER_STATE_CLOSED = 1;
|
||||
}
|
||||
@ -356,7 +360,8 @@ message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
LegacyCoverState legacy_state = 2;
|
||||
// Deprecated in API version 1.1
|
||||
LegacyCoverState legacy_state = 2 [deprecated=true];
|
||||
|
||||
float position = 3;
|
||||
float tilt = 4;
|
||||
@ -364,7 +369,9 @@ message CoverStateResponse {
|
||||
uint32 device_id = 6 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
// Deprecated in API version 1.1
|
||||
enum LegacyCoverCommand {
|
||||
option deprecated = true;
|
||||
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||
LEGACY_COVER_COMMAND_STOP = 2;
|
||||
@ -380,8 +387,10 @@ message CoverCommandRequest {
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
bool has_legacy_command = 2;
|
||||
LegacyCoverCommand legacy_command = 3;
|
||||
// Deprecated in API version 1.1
|
||||
bool has_legacy_command = 2 [deprecated=true];
|
||||
// Deprecated in API version 1.1
|
||||
LegacyCoverCommand legacy_command = 3 [deprecated=true];
|
||||
|
||||
bool has_position = 4;
|
||||
float position = 5;
|
||||
@ -413,7 +422,9 @@ message ListEntitiesFanResponse {
|
||||
repeated string supported_preset_modes = 12;
|
||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
// Deprecated in API version 1.6 - only used in deprecated fields
|
||||
enum FanSpeed {
|
||||
option deprecated = true;
|
||||
FAN_SPEED_LOW = 0;
|
||||
FAN_SPEED_MEDIUM = 1;
|
||||
FAN_SPEED_HIGH = 2;
|
||||
@ -432,7 +443,8 @@ message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4 [deprecated = true];
|
||||
// Deprecated in API version 1.6
|
||||
FanSpeed speed = 4 [deprecated=true];
|
||||
FanDirection direction = 5;
|
||||
int32 speed_level = 6;
|
||||
string preset_mode = 7;
|
||||
@ -448,8 +460,10 @@ message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4 [deprecated = true];
|
||||
FanSpeed speed = 5 [deprecated = true];
|
||||
// Deprecated in API version 1.6
|
||||
bool has_speed = 4 [deprecated=true];
|
||||
// Deprecated in API version 1.6
|
||||
FanSpeed speed = 5 [deprecated=true];
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
@ -488,9 +502,13 @@ message ListEntitiesLightResponse {
|
||||
|
||||
repeated ColorMode supported_color_modes = 12;
|
||||
// next four supports_* are for legacy clients, newer clients should use color modes
|
||||
// Deprecated in API version 1.6
|
||||
bool legacy_supports_brightness = 5 [deprecated=true];
|
||||
// Deprecated in API version 1.6
|
||||
bool legacy_supports_rgb = 6 [deprecated=true];
|
||||
// Deprecated in API version 1.6
|
||||
bool legacy_supports_white_value = 7 [deprecated=true];
|
||||
// Deprecated in API version 1.6
|
||||
bool legacy_supports_color_temperature = 8 [deprecated=true];
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
@ -567,7 +585,9 @@ enum SensorStateClass {
|
||||
STATE_CLASS_TOTAL = 3;
|
||||
}
|
||||
|
||||
// Deprecated in API version 1.5
|
||||
enum SensorLastResetType {
|
||||
option deprecated = true;
|
||||
LAST_RESET_NONE = 0;
|
||||
LAST_RESET_NEVER = 1;
|
||||
LAST_RESET_AUTO = 2;
|
||||
@ -591,7 +611,8 @@ message ListEntitiesSensorResponse {
|
||||
string device_class = 9;
|
||||
SensorStateClass state_class = 10;
|
||||
// Last reset type removed in 2021.9.0
|
||||
SensorLastResetType legacy_last_reset_type = 11;
|
||||
// Deprecated in API version 1.5
|
||||
SensorLastResetType legacy_last_reset_type = 11 [deprecated=true];
|
||||
bool disabled_by_default = 12;
|
||||
EntityCategory entity_category = 13;
|
||||
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
|
||||
@ -947,7 +968,8 @@ message ListEntitiesClimateResponse {
|
||||
float visual_target_temperature_step = 10;
|
||||
// for older peer versions - in new system this
|
||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||
bool legacy_supports_away = 11;
|
||||
// Deprecated in API version 1.5
|
||||
bool legacy_supports_away = 11 [deprecated=true];
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
@ -978,7 +1000,8 @@ message ClimateStateResponse {
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
|
||||
bool unused_legacy_away = 7;
|
||||
// Deprecated in API version 1.5
|
||||
bool unused_legacy_away = 7 [deprecated=true];
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
@ -1006,8 +1029,10 @@ message ClimateCommandRequest {
|
||||
bool has_target_temperature_high = 8;
|
||||
float target_temperature_high = 9;
|
||||
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
|
||||
bool unused_has_legacy_away = 10;
|
||||
bool unused_legacy_away = 11;
|
||||
// Deprecated in API version 1.5
|
||||
bool unused_has_legacy_away = 10 [deprecated=true];
|
||||
// Deprecated in API version 1.5
|
||||
bool unused_legacy_away = 11 [deprecated=true];
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
@ -1354,12 +1379,17 @@ message SubscribeBluetoothLEAdvertisementsRequest {
|
||||
uint32 flags = 1;
|
||||
}
|
||||
|
||||
// Deprecated - only used by deprecated BluetoothLEAdvertisementResponse
|
||||
message BluetoothServiceData {
|
||||
option deprecated = true;
|
||||
string uuid = 1;
|
||||
repeated uint32 legacy_data = 2 [deprecated = true]; // Removed in api version 1.7
|
||||
// Deprecated in API version 1.7
|
||||
repeated uint32 legacy_data = 2 [deprecated=true]; // Removed in api version 1.7
|
||||
bytes data = 3; // Added in api version 1.7
|
||||
}
|
||||
// Removed in ESPHome 2025.8.0 - use BluetoothLERawAdvertisementsResponse instead
|
||||
message BluetoothLEAdvertisementResponse {
|
||||
option deprecated = true;
|
||||
option (id) = 67;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
@ -363,8 +363,6 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *
|
||||
auto *cover = static_cast<cover::Cover *>(entity);
|
||||
CoverStateResponse msg;
|
||||
auto traits = cover->get_traits();
|
||||
msg.legacy_state =
|
||||
(cover->position == cover::COVER_OPEN) ? enums::LEGACY_COVER_STATE_OPEN : enums::LEGACY_COVER_STATE_CLOSED;
|
||||
msg.position = cover->position;
|
||||
if (traits.get_supports_tilt())
|
||||
msg.tilt = cover->tilt;
|
||||
@ -386,19 +384,6 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
|
||||
if (msg.has_legacy_command) {
|
||||
switch (msg.legacy_command) {
|
||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msg.has_position)
|
||||
call.set_position(msg.position);
|
||||
if (msg.has_tilt)
|
||||
@ -496,14 +481,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
auto traits = light->get_traits();
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
|
||||
msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
|
||||
msg.legacy_supports_white_value =
|
||||
msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
|
||||
msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
|
||||
if (msg.legacy_supports_color_temperature) {
|
||||
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
@ -693,7 +672,6 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
for (auto fan_mode : traits.get_supported_fan_modes())
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
@ -1114,21 +1092,6 @@ void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoo
|
||||
void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||
}
|
||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
|
||||
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
|
||||
BluetoothLEAdvertisementResponse resp = msg;
|
||||
for (auto &service : resp.service_data) {
|
||||
service.legacy_data.assign(service.data.begin(), service.data.end());
|
||||
service.data.clear();
|
||||
}
|
||||
for (auto &manufacturer_data : resp.manufacturer_data) {
|
||||
manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
|
||||
manufacturer_data.data.clear();
|
||||
}
|
||||
return this->send_message(resp, BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
||||
}
|
||||
return this->send_message(msg, BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
||||
}
|
||||
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
|
||||
}
|
||||
@ -1499,12 +1462,10 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
|
||||
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
|
||||
resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
|
||||
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
@ -1671,6 +1632,10 @@ ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
|
||||
}
|
||||
|
||||
void APIConnection::process_batch_() {
|
||||
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||
"PacketInfo must remain trivially destructible with this placement-new approach");
|
||||
|
||||
if (this->deferred_batch_.empty()) {
|
||||
this->flags_.batch_scheduled = false;
|
||||
return;
|
||||
@ -1708,9 +1673,12 @@ void APIConnection::process_batch_() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-allocate storage for packet info
|
||||
std::vector<PacketInfo> packet_info;
|
||||
packet_info.reserve(num_items);
|
||||
size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH);
|
||||
|
||||
// Stack-allocated array for packet info
|
||||
alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)];
|
||||
PacketInfo *packet_info = reinterpret_cast<PacketInfo *>(packet_info_storage);
|
||||
size_t packet_count = 0;
|
||||
|
||||
// Cache these values to avoid repeated virtual calls
|
||||
const uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
@ -1742,8 +1710,8 @@ void APIConnection::process_batch_() {
|
||||
// The actual message data follows after the header padding
|
||||
uint32_t current_offset = 0;
|
||||
|
||||
// Process items and encode directly to buffer
|
||||
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
||||
// Process items and encode directly to buffer (up to our limit)
|
||||
for (size_t i = 0; i < packets_to_process; i++) {
|
||||
const auto &item = this->deferred_batch_[i];
|
||||
// Try to encode message
|
||||
// The creator will calculate overhead to determine if the message fits
|
||||
@ -1757,7 +1725,11 @@ void APIConnection::process_batch_() {
|
||||
// Message was encoded successfully
|
||||
// payload_size is header_padding + actual payload size + footer_size
|
||||
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
|
||||
packet_info.emplace_back(item.message_type, current_offset, proto_payload_size);
|
||||
// Use placement new to construct PacketInfo in pre-allocated stack array
|
||||
// This avoids default-constructing all MAX_PACKETS_PER_BATCH elements
|
||||
// Explicit destruction is not needed because PacketInfo is trivially destructible,
|
||||
// as ensured by the static_assert in its definition.
|
||||
new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size);
|
||||
|
||||
// Update tracking variables
|
||||
items_processed++;
|
||||
@ -1783,8 +1755,8 @@ void APIConnection::process_batch_() {
|
||||
}
|
||||
|
||||
// Send all collected packets
|
||||
APIError err =
|
||||
this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info);
|
||||
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()},
|
||||
std::span<const PacketInfo>(packet_info, packet_count));
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
|
||||
|
@ -33,7 +33,17 @@ struct ClientInfo {
|
||||
// Keepalive timeout in milliseconds
|
||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||
// Maximum number of entities to process in a single batch during initial state/info sending
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 20;
|
||||
// This was increased from 20 to 24 after removing the unique_id field from entity info messages,
|
||||
// which reduced message sizes allowing more entities per batch without exceeding packet limits
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 24;
|
||||
// Maximum number of packets to process in a single batch (platform-dependent)
|
||||
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
|
||||
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty
|
||||
#else
|
||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||
#endif
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
@ -130,7 +140,6 @@ class APIConnection : public APIServerConnection {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
|
||||
|
||||
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
|
||||
|
@ -79,33 +79,56 @@ APIError APIFrameHelper::loop() {
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
}
|
||||
|
||||
// Common socket write error handling
|
||||
APIError APIFrameHelper::handle_socket_write_error_() {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
|
||||
uint16_t offset) {
|
||||
SendBuffer buffer;
|
||||
buffer.data.reserve(total_write_len);
|
||||
buffer.size = total_write_len - offset;
|
||||
buffer.data = std::make_unique<uint8_t[]>(buffer.size);
|
||||
|
||||
uint16_t to_skip = offset;
|
||||
uint16_t write_pos = 0;
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base);
|
||||
buffer.data.insert(buffer.data.end(), data, data + iov[i].iov_len);
|
||||
if (to_skip >= iov[i].iov_len) {
|
||||
// Skip this entire segment
|
||||
to_skip -= static_cast<uint16_t>(iov[i].iov_len);
|
||||
} else {
|
||||
// Include this segment (partially or fully)
|
||||
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
|
||||
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
|
||||
std::memcpy(buffer.data.get() + write_pos, src, len);
|
||||
write_pos += len;
|
||||
to_skip = 0;
|
||||
}
|
||||
}
|
||||
this->tx_buf_.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
// This method writes data to socket or buffers it
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
// Returns APIError::OK if successful (or would block, but data has been buffered)
|
||||
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
|
||||
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK; // Nothing to do, success
|
||||
|
||||
uint16_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += static_cast<uint16_t>(iov[i].iov_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try to send any existing buffered data first if there is any
|
||||
if (!this->tx_buf_.empty()) {
|
||||
@ -118,7 +141,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
// If there is still data in the buffer, we can't send, buffer
|
||||
// the new data and return
|
||||
if (!this->tx_buf_.empty()) {
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
}
|
||||
@ -127,37 +150,16 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||
|
||||
if (sent == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
APIError err = this->handle_socket_write_error_();
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// Socket would block, buffer the data
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len);
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
// Socket error
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
return err; // Socket write failed
|
||||
} else if (static_cast<uint16_t>(sent) < total_write_len) {
|
||||
// Partially sent, buffer the remaining data
|
||||
SendBuffer buffer;
|
||||
uint16_t to_consume = static_cast<uint16_t>(sent);
|
||||
uint16_t remaining = total_write_len - static_cast<uint16_t>(sent);
|
||||
|
||||
buffer.data.reserve(remaining);
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
// This segment was fully sent
|
||||
to_consume -= static_cast<uint16_t>(iov[i].iov_len);
|
||||
} else {
|
||||
// This segment was partially sent or not sent at all
|
||||
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume;
|
||||
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_consume;
|
||||
buffer.data.insert(buffer.data.end(), data, data + len);
|
||||
to_consume = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this->tx_buf_.push_back(std::move(buffer));
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
|
||||
}
|
||||
|
||||
return APIError::OK; // Success, all data sent or buffered
|
||||
@ -176,14 +178,7 @@ APIError APIFrameHelper::try_send_tx_buf_() {
|
||||
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
|
||||
|
||||
if (sent == -1) {
|
||||
if (errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
// Real socket error (not just would block)
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
}
|
||||
// Socket would block, we'll try again later
|
||||
return APIError::WOULD_BLOCK;
|
||||
return this->handle_socket_write_error_();
|
||||
} else if (sent == 0) {
|
||||
// Nothing sent but not an error
|
||||
return APIError::WOULD_BLOCK;
|
||||
@ -299,6 +294,26 @@ APIError APINoiseFrameHelper::init() {
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
}
|
||||
// Helper for handling handshake frame errors
|
||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
|
||||
// Helper for handling noise library errors
|
||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
||||
return api_err;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
// During handshake phase, process as many actions as possible until we can't progress
|
||||
@ -306,12 +321,12 @@ APIError APINoiseFrameHelper::loop() {
|
||||
// WOULD_BLOCK when no more data is available to read
|
||||
while (state_ != State::DATA && this->socket_->ready()) {
|
||||
APIError err = state_action_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Use base class implementation for buffer sending
|
||||
@ -332,7 +347,7 @@ APIError APINoiseFrameHelper::loop() {
|
||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||
*/
|
||||
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
@ -395,7 +410,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
*frame = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
@ -421,24 +436,17 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::CLIENT_HELLO) {
|
||||
// waiting for client hello
|
||||
ParsedFrame frame;
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
return aerr;
|
||||
if (aerr != APIError::OK) {
|
||||
return handle_handshake_frame_error_(aerr);
|
||||
}
|
||||
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// ignore contents, may be used in future for flags
|
||||
// Reserve space for: existing prologue + 2 size bytes + frame data
|
||||
prologue_.reserve(prologue_.size() + 2 + frame.msg.size());
|
||||
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
|
||||
prologue_.push_back((uint8_t) frame.msg.size());
|
||||
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
|
||||
prologue_.reserve(prologue_.size() + 2 + frame.size());
|
||||
prologue_.push_back((uint8_t) (frame.size() >> 8));
|
||||
prologue_.push_back((uint8_t) frame.size());
|
||||
prologue_.insert(prologue_.end(), frame.begin(), frame.end());
|
||||
|
||||
state_ = State::SERVER_HELLO;
|
||||
}
|
||||
@ -476,41 +484,29 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
int action = noise_handshakestate_get_action(handshake_);
|
||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||
// waiting for handshake msg
|
||||
ParsedFrame frame;
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
send_explicit_handshake_reject_("Bad indicator byte");
|
||||
return aerr;
|
||||
if (aerr != APIError::OK) {
|
||||
return handle_handshake_frame_error_(aerr);
|
||||
}
|
||||
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||
return aerr;
|
||||
}
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
if (frame.msg.empty()) {
|
||||
if (frame.empty()) {
|
||||
send_explicit_handshake_reject_("Empty handshake message");
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
} else if (frame.msg[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
|
||||
} else if (frame[0] != 0x00) {
|
||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||
}
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
|
||||
noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
|
||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
|
||||
if (err == NOISE_ERROR_MAC_FAILURE) {
|
||||
send_explicit_handshake_reject_("Handshake MAC failure");
|
||||
} else {
|
||||
send_explicit_handshake_reject_("Handshake error");
|
||||
}
|
||||
return APIError::HANDSHAKESTATE_READ_FAILED;
|
||||
// Special handling for MAC failure
|
||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
||||
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
||||
}
|
||||
|
||||
aerr = check_handshake_finished_();
|
||||
@ -523,11 +519,10 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||
|
||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_WRITE_FAILED;
|
||||
}
|
||||
APIError aerr_write =
|
||||
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||
if (aerr_write != APIError::OK)
|
||||
return aerr_write;
|
||||
buffer[0] = 0x00; // success
|
||||
|
||||
aerr = write_frame_(buffer, mbuf.size + 1);
|
||||
@ -576,23 +571,21 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
ParsedFrame frame;
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size());
|
||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_DECRYPT_FAILED;
|
||||
}
|
||||
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||
if (decrypt_err != APIError::OK)
|
||||
return decrypt_err;
|
||||
|
||||
uint16_t msg_size = mbuf.size;
|
||||
uint8_t *msg_data = frame.msg.data();
|
||||
uint8_t *msg_data = frame.data();
|
||||
if (msg_size < 4) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||
@ -607,7 +600,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
buffer->container = std::move(frame.msg);
|
||||
buffer->container = std::move(frame);
|
||||
buffer->data_offset = 4;
|
||||
buffer->data_len = data_len;
|
||||
buffer->type = type;
|
||||
@ -640,6 +633,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
// We need to encrypt each packet in place
|
||||
for (const auto &packet : packets) {
|
||||
@ -668,23 +662,22 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
4 + packet.payload_size + frame_footer_size_);
|
||||
|
||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
|
||||
}
|
||||
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
// Fill in the encrypted size
|
||||
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
|
||||
buf_start[2] = static_cast<uint8_t>(mbuf.size);
|
||||
|
||||
// Add iovec for this encrypted packet
|
||||
this->reusable_iovs_.push_back(
|
||||
{buf_start, static_cast<size_t>(3 + mbuf.size)}); // indicator + size + encrypted data
|
||||
size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||
this->reusable_iovs_.push_back({buf_start, packet_len});
|
||||
total_write_len += packet_len;
|
||||
}
|
||||
|
||||
// Send all encrypted packets in one writev call
|
||||
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
|
||||
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
@ -697,12 +690,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return this->write_raw_(iov, 1);
|
||||
return this->write_raw_(iov, 1, 3); // Just header
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
return this->write_raw_(iov, 2);
|
||||
return this->write_raw_(iov, 2, 3 + len); // Header + data
|
||||
}
|
||||
|
||||
/** Initiate the data structures for the handshake.
|
||||
@ -723,35 +716,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
|
||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||
|
||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
const auto &psk = ctx_->get_psk();
|
||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||
prologue_ = {};
|
||||
|
||||
err = noise_handshakestate_start(handshake_);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SETUP_FAILED;
|
||||
}
|
||||
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
@ -767,11 +752,9 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||
}
|
||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
||||
}
|
||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
||||
|
||||
@ -838,7 +821,7 @@ APIError APIPlaintextFrameHelper::loop() {
|
||||
*
|
||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||
*/
|
||||
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||
if (frame == nullptr) {
|
||||
HELPER_LOG("Bad argument for try_read_frame_");
|
||||
return APIError::BAD_ARG;
|
||||
@ -956,7 +939,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
*frame = std::move(rx_buf_);
|
||||
// consume msg
|
||||
rx_buf_ = {};
|
||||
rx_buf_len_ = 0;
|
||||
@ -971,7 +954,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
ParsedFrame frame;
|
||||
std::vector<uint8_t> frame;
|
||||
aerr = try_read_frame_(&frame);
|
||||
if (aerr != APIError::OK) {
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
@ -991,12 +974,12 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
"Bad indicator byte";
|
||||
iov[0].iov_base = (void *) msg;
|
||||
iov[0].iov_len = 19;
|
||||
this->write_raw_(iov, 1);
|
||||
this->write_raw_(iov, 1, 19);
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
|
||||
buffer->container = std::move(frame.msg);
|
||||
buffer->container = std::move(frame);
|
||||
buffer->data_offset = 0;
|
||||
buffer->data_len = rx_header_parsed_len_;
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
@ -1021,6 +1004,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
for (const auto &packet : packets) {
|
||||
// Calculate varint sizes for header layout
|
||||
@ -1065,12 +1049,13 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
||||
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
// Add iovec for this packet (header + payload)
|
||||
this->reusable_iovs_.push_back(
|
||||
{buf_start + header_offset, static_cast<size_t>(total_header_len + packet.payload_size)});
|
||||
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||
this->reusable_iovs_.push_back({buf_start + header_offset, packet_len});
|
||||
total_write_len += packet_len;
|
||||
}
|
||||
|
||||
// Send all packets in one writev call
|
||||
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
|
||||
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||
}
|
||||
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
@ -111,29 +111,28 @@ class APIFrameHelper {
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
|
||||
protected:
|
||||
// Struct for holding parsed frame data
|
||||
struct ParsedFrame {
|
||||
std::vector<uint8_t> msg;
|
||||
};
|
||||
|
||||
// Buffer containing data to be sent
|
||||
struct SendBuffer {
|
||||
std::vector<uint8_t> data;
|
||||
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
uint16_t size{0}; // Total size of the buffer
|
||||
uint16_t offset{0}; // Current offset within the buffer
|
||||
|
||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
|
||||
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
|
||||
const uint8_t *current_data() const { return data.data() + offset; }
|
||||
uint16_t remaining() const { return size - offset; }
|
||||
const uint8_t *current_data() const { return data.get() + offset; }
|
||||
};
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||
|
||||
// Try to send data from the tx buffer
|
||||
APIError try_send_tx_buf_();
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
|
||||
|
||||
// Common socket write error handling
|
||||
APIError handle_socket_write_error_();
|
||||
template<typename StateEnum>
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
||||
const std::string &info, StateEnum &state, StateEnum failed_state);
|
||||
@ -210,11 +209,13 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
|
||||
protected:
|
||||
APIError state_action_();
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
APIError handle_handshake_frame_error_(APIError aerr);
|
||||
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
||||
|
||||
// Pointers first (4 bytes each)
|
||||
NoiseHandshakeState *handshake_{nullptr};
|
||||
@ -263,7 +264,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||
|
||||
// Group 2-byte aligned types
|
||||
uint16_t rx_header_parsed_type_ = 0;
|
||||
|
@ -94,17 +94,11 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
#ifdef USE_WEBSERVER
|
||||
buffer.encode_uint32(10, this->webserver_port);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
buffer.encode_uint32(11, this->legacy_bluetooth_proxy_version);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags);
|
||||
#endif
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
buffer.encode_uint32(14, this->legacy_voice_assistant_version);
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
|
||||
#endif
|
||||
@ -150,17 +144,11 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
#ifdef USE_WEBSERVER
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->webserver_port);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->legacy_bluetooth_proxy_version);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->bluetooth_proxy_feature_flags);
|
||||
#endif
|
||||
ProtoSize::add_string_field(total_size, 1, this->manufacturer);
|
||||
ProtoSize::add_string_field(total_size, 1, this->friendly_name);
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->legacy_voice_assistant_version);
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->voice_assistant_feature_flags);
|
||||
#endif
|
||||
@ -270,7 +258,6 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
|
||||
}
|
||||
void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->legacy_state));
|
||||
buffer.encode_float(3, this->position);
|
||||
buffer.encode_float(4, this->tilt);
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->current_operation));
|
||||
@ -280,7 +267,6 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
void CoverStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed32_field(total_size, 1, this->key);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_state));
|
||||
ProtoSize::add_float_field(total_size, 1, this->position);
|
||||
ProtoSize::add_float_field(total_size, 1, this->tilt);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->current_operation));
|
||||
@ -290,12 +276,6 @@ void CoverStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
}
|
||||
bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->has_legacy_command = value.as_bool();
|
||||
break;
|
||||
case 3:
|
||||
this->legacy_command = static_cast<enums::LegacyCoverCommand>(value.as_uint32());
|
||||
break;
|
||||
case 4:
|
||||
this->has_position = value.as_bool();
|
||||
break;
|
||||
@ -379,7 +359,6 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->speed));
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->direction));
|
||||
buffer.encode_int32(6, this->speed_level);
|
||||
buffer.encode_string(7, this->preset_mode);
|
||||
@ -391,7 +370,6 @@ void FanStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed32_field(total_size, 1, this->key);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->state);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->oscillating);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->speed));
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->direction));
|
||||
ProtoSize::add_int32_field(total_size, 1, this->speed_level);
|
||||
ProtoSize::add_string_field(total_size, 1, this->preset_mode);
|
||||
@ -407,12 +385,6 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
case 3:
|
||||
this->state = value.as_bool();
|
||||
break;
|
||||
case 4:
|
||||
this->has_speed = value.as_bool();
|
||||
break;
|
||||
case 5:
|
||||
this->speed = static_cast<enums::FanSpeed>(value.as_uint32());
|
||||
break;
|
||||
case 6:
|
||||
this->has_oscillating = value.as_bool();
|
||||
break;
|
||||
@ -473,10 +445,6 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_color_modes) {
|
||||
buffer.encode_uint32(12, static_cast<uint32_t>(it), true);
|
||||
}
|
||||
buffer.encode_bool(5, this->legacy_supports_brightness);
|
||||
buffer.encode_bool(6, this->legacy_supports_rgb);
|
||||
buffer.encode_bool(7, this->legacy_supports_white_value);
|
||||
buffer.encode_bool(8, this->legacy_supports_color_temperature);
|
||||
buffer.encode_float(9, this->min_mireds);
|
||||
buffer.encode_float(10, this->max_mireds);
|
||||
for (auto &it : this->effects) {
|
||||
@ -500,10 +468,6 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_enum_field_repeated(total_size, 1, static_cast<uint32_t>(it));
|
||||
}
|
||||
}
|
||||
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_brightness);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_rgb);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_white_value);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_color_temperature);
|
||||
ProtoSize::add_float_field(total_size, 1, this->min_mireds);
|
||||
ProtoSize::add_float_field(total_size, 1, this->max_mireds);
|
||||
if (!this->effects.empty()) {
|
||||
@ -677,7 +641,6 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
buffer.encode_uint32(10, static_cast<uint32_t>(this->state_class));
|
||||
buffer.encode_uint32(11, static_cast<uint32_t>(this->legacy_last_reset_type));
|
||||
buffer.encode_bool(12, this->disabled_by_default);
|
||||
buffer.encode_uint32(13, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
@ -696,7 +659,6 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->force_update);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->state_class));
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type));
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
@ -1105,7 +1067,6 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(8, this->visual_min_temperature);
|
||||
buffer.encode_float(9, this->visual_max_temperature);
|
||||
buffer.encode_float(10, this->visual_target_temperature_step);
|
||||
buffer.encode_bool(11, this->legacy_supports_away);
|
||||
buffer.encode_bool(12, this->supports_action);
|
||||
for (auto &it : this->supported_fan_modes) {
|
||||
buffer.encode_uint32(13, static_cast<uint32_t>(it), true);
|
||||
@ -1150,7 +1111,6 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_float_field(total_size, 1, this->visual_min_temperature);
|
||||
ProtoSize::add_float_field(total_size, 1, this->visual_max_temperature);
|
||||
ProtoSize::add_float_field(total_size, 1, this->visual_target_temperature_step);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_away);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_action);
|
||||
if (!this->supported_fan_modes.empty()) {
|
||||
for (const auto &it : this->supported_fan_modes) {
|
||||
@ -1198,7 +1158,6 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(4, this->target_temperature);
|
||||
buffer.encode_float(5, this->target_temperature_low);
|
||||
buffer.encode_float(6, this->target_temperature_high);
|
||||
buffer.encode_bool(7, this->unused_legacy_away);
|
||||
buffer.encode_uint32(8, static_cast<uint32_t>(this->action));
|
||||
buffer.encode_uint32(9, static_cast<uint32_t>(this->fan_mode));
|
||||
buffer.encode_uint32(10, static_cast<uint32_t>(this->swing_mode));
|
||||
@ -1218,7 +1177,6 @@ void ClimateStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_float_field(total_size, 1, this->target_temperature);
|
||||
ProtoSize::add_float_field(total_size, 1, this->target_temperature_low);
|
||||
ProtoSize::add_float_field(total_size, 1, this->target_temperature_high);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->unused_legacy_away);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->action));
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->fan_mode));
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->swing_mode));
|
||||
@ -1248,12 +1206,6 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
case 8:
|
||||
this->has_target_temperature_high = value.as_bool();
|
||||
break;
|
||||
case 10:
|
||||
this->unused_has_legacy_away = value.as_bool();
|
||||
break;
|
||||
case 11:
|
||||
this->unused_legacy_away = value.as_bool();
|
||||
break;
|
||||
case 12:
|
||||
this->has_fan_mode = value.as_bool();
|
||||
break;
|
||||
@ -1869,50 +1821,6 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id,
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->uuid);
|
||||
for (auto &it : this->legacy_data) {
|
||||
buffer.encode_uint32(2, it, true);
|
||||
}
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
}
|
||||
void BluetoothServiceData::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->uuid);
|
||||
if (!this->legacy_data.empty()) {
|
||||
for (const auto &it : this->legacy_data) {
|
||||
ProtoSize::add_uint32_field_repeated(total_size, 1, it);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_string_field(total_size, 1, this->data);
|
||||
}
|
||||
void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bytes(2, reinterpret_cast<const uint8_t *>(this->name.data()), this->name.size());
|
||||
buffer.encode_sint32(3, this->rssi);
|
||||
for (auto &it : this->service_uuids) {
|
||||
buffer.encode_string(4, it, true);
|
||||
}
|
||||
for (auto &it : this->service_data) {
|
||||
buffer.encode_message(5, it, true);
|
||||
}
|
||||
for (auto &it : this->manufacturer_data) {
|
||||
buffer.encode_message(6, it, true);
|
||||
}
|
||||
buffer.encode_uint32(7, this->address_type);
|
||||
}
|
||||
void BluetoothLEAdvertisementResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint64_field(total_size, 1, this->address);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name);
|
||||
ProtoSize::add_sint32_field(total_size, 1, this->rssi);
|
||||
if (!this->service_uuids.empty()) {
|
||||
for (const auto &it : this->service_uuids) {
|
||||
ProtoSize::add_string_field_repeated(total_size, 1, it);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_repeated_message(total_size, 1, this->service_data);
|
||||
ProtoSize::add_repeated_message(total_size, 1, this->manufacturer_data);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->address_type);
|
||||
}
|
||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_sint32(2, this->rssi);
|
||||
|
@ -17,27 +17,13 @@ enum EntityCategory : uint32_t {
|
||||
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
||||
};
|
||||
#ifdef USE_COVER
|
||||
enum LegacyCoverState : uint32_t {
|
||||
LEGACY_COVER_STATE_OPEN = 0,
|
||||
LEGACY_COVER_STATE_CLOSED = 1,
|
||||
};
|
||||
enum CoverOperation : uint32_t {
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
COVER_OPERATION_IS_OPENING = 1,
|
||||
COVER_OPERATION_IS_CLOSING = 2,
|
||||
};
|
||||
enum LegacyCoverCommand : uint32_t {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
enum FanSpeed : uint32_t {
|
||||
FAN_SPEED_LOW = 0,
|
||||
FAN_SPEED_MEDIUM = 1,
|
||||
FAN_SPEED_HIGH = 2,
|
||||
};
|
||||
enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
@ -65,11 +51,6 @@ enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||
STATE_CLASS_TOTAL = 3,
|
||||
};
|
||||
enum SensorLastResetType : uint32_t {
|
||||
LAST_RESET_NONE = 0,
|
||||
LAST_RESET_NEVER = 1,
|
||||
LAST_RESET_AUTO = 2,
|
||||
};
|
||||
#endif
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
@ -485,7 +466,7 @@ class DeviceInfo : public ProtoMessage {
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 10;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 219;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 211;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "device_info_response"; }
|
||||
#endif
|
||||
@ -507,17 +488,11 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
#ifdef USE_WEBSERVER
|
||||
uint32_t webserver_port{0};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
uint32_t legacy_bluetooth_proxy_version{0};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
uint32_t bluetooth_proxy_feature_flags{0};
|
||||
#endif
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
uint32_t legacy_voice_assistant_version{0};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
uint32_t voice_assistant_feature_flags{0};
|
||||
#endif
|
||||
@ -646,11 +621,10 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
||||
class CoverStateResponse : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 22;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 23;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 21;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "cover_state_response"; }
|
||||
#endif
|
||||
enums::LegacyCoverState legacy_state{};
|
||||
float position{0.0f};
|
||||
float tilt{0.0f};
|
||||
enums::CoverOperation current_operation{};
|
||||
@ -665,12 +639,10 @@ class CoverStateResponse : public StateResponseProtoMessage {
|
||||
class CoverCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 30;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 29;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 25;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "cover_command_request"; }
|
||||
#endif
|
||||
bool has_legacy_command{false};
|
||||
enums::LegacyCoverCommand legacy_command{};
|
||||
bool has_position{false};
|
||||
float position{0.0f};
|
||||
bool has_tilt{false};
|
||||
@ -709,13 +681,12 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
||||
class FanStateResponse : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 23;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 30;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 28;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "fan_state_response"; }
|
||||
#endif
|
||||
bool state{false};
|
||||
bool oscillating{false};
|
||||
enums::FanSpeed speed{};
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
std::string preset_mode{};
|
||||
@ -730,14 +701,12 @@ class FanStateResponse : public StateResponseProtoMessage {
|
||||
class FanCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 31;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 42;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 38;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "fan_command_request"; }
|
||||
#endif
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_speed{false};
|
||||
enums::FanSpeed speed{};
|
||||
bool has_oscillating{false};
|
||||
bool oscillating{false};
|
||||
bool has_direction{false};
|
||||
@ -760,15 +729,11 @@ class FanCommandRequest : public CommandProtoMessage {
|
||||
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 15;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 81;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 73;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_light_response"; }
|
||||
#endif
|
||||
std::vector<enums::ColorMode> supported_color_modes{};
|
||||
bool legacy_supports_brightness{false};
|
||||
bool legacy_supports_rgb{false};
|
||||
bool legacy_supports_white_value{false};
|
||||
bool legacy_supports_color_temperature{false};
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
@ -854,7 +819,7 @@ class LightCommandRequest : public CommandProtoMessage {
|
||||
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 16;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 66;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_sensor_response"; }
|
||||
#endif
|
||||
@ -863,7 +828,6 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||
bool force_update{false};
|
||||
std::string device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
enums::SensorLastResetType legacy_last_reset_type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@ -1289,7 +1253,7 @@ class CameraImageRequest : public ProtoDecodableMessage {
|
||||
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 46;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 147;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 145;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_climate_response"; }
|
||||
#endif
|
||||
@ -1299,7 +1263,6 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
float visual_min_temperature{0.0f};
|
||||
float visual_max_temperature{0.0f};
|
||||
float visual_target_temperature_step{0.0f};
|
||||
bool legacy_supports_away{false};
|
||||
bool supports_action{false};
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
|
||||
@ -1322,7 +1285,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
class ClimateStateResponse : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 47;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 70;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "climate_state_response"; }
|
||||
#endif
|
||||
@ -1331,7 +1294,6 @@ class ClimateStateResponse : public StateResponseProtoMessage {
|
||||
float target_temperature{0.0f};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
bool unused_legacy_away{false};
|
||||
enums::ClimateAction action{};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
@ -1351,7 +1313,7 @@ class ClimateStateResponse : public StateResponseProtoMessage {
|
||||
class ClimateCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 48;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 88;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 84;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "climate_command_request"; }
|
||||
#endif
|
||||
@ -1363,8 +1325,6 @@ class ClimateCommandRequest : public CommandProtoMessage {
|
||||
float target_temperature_low{0.0f};
|
||||
bool has_target_temperature_high{false};
|
||||
float target_temperature_high{0.0f};
|
||||
bool unused_has_legacy_away{false};
|
||||
bool unused_legacy_away{false};
|
||||
bool has_fan_mode{false};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
bool has_swing_mode{false};
|
||||
@ -1736,41 +1696,6 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoDecodableMessage {
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothServiceData : public ProtoMessage {
|
||||
public:
|
||||
std::string uuid{};
|
||||
std::vector<uint32_t> legacy_data{};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class BluetoothLEAdvertisementResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 67;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 107;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_le_advertisement_response"; }
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
std::string name{};
|
||||
int32_t rssi{0};
|
||||
std::vector<std::string> service_uuids{};
|
||||
std::vector<BluetoothServiceData> service_data{};
|
||||
std::vector<BluetoothServiceData> manufacturer_data{};
|
||||
uint32_t address_type{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class BluetoothLERawAdvertisement : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
|
@ -23,16 +23,6 @@ template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::Entity
|
||||
}
|
||||
}
|
||||
#ifdef USE_COVER
|
||||
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
|
||||
switch (value) {
|
||||
case enums::LEGACY_COVER_STATE_OPEN:
|
||||
return "LEGACY_COVER_STATE_OPEN";
|
||||
case enums::LEGACY_COVER_STATE_CLOSED:
|
||||
return "LEGACY_COVER_STATE_CLOSED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverOperation value) {
|
||||
switch (value) {
|
||||
case enums::COVER_OPERATION_IDLE:
|
||||
@ -45,32 +35,8 @@ template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverO
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LegacyCoverCommand>(enums::LegacyCoverCommand value) {
|
||||
switch (value) {
|
||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||
return "LEGACY_COVER_COMMAND_OPEN";
|
||||
case enums::LEGACY_COVER_COMMAND_CLOSE:
|
||||
return "LEGACY_COVER_COMMAND_CLOSE";
|
||||
case enums::LEGACY_COVER_COMMAND_STOP:
|
||||
return "LEGACY_COVER_COMMAND_STOP";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed value) {
|
||||
switch (value) {
|
||||
case enums::FAN_SPEED_LOW:
|
||||
return "FAN_SPEED_LOW";
|
||||
case enums::FAN_SPEED_MEDIUM:
|
||||
return "FAN_SPEED_MEDIUM";
|
||||
case enums::FAN_SPEED_HIGH:
|
||||
return "FAN_SPEED_HIGH";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
|
||||
switch (value) {
|
||||
case enums::FAN_DIRECTION_FORWARD:
|
||||
@ -127,18 +93,6 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
|
||||
switch (value) {
|
||||
case enums::LAST_RESET_NONE:
|
||||
return "LAST_RESET_NONE";
|
||||
case enums::LAST_RESET_NEVER:
|
||||
return "LAST_RESET_NEVER";
|
||||
case enums::LAST_RESET_AUTO:
|
||||
return "LAST_RESET_AUTO";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||
switch (value) {
|
||||
@ -737,13 +691,6 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
out.append(" legacy_bluetooth_proxy_version: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->legacy_bluetooth_proxy_version);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
out.append(" bluetooth_proxy_feature_flags: ");
|
||||
@ -760,13 +707,6 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(this->friendly_name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
out.append(" legacy_voice_assistant_version: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->legacy_voice_assistant_version);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
out.append(" voice_assistant_feature_flags: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->voice_assistant_feature_flags);
|
||||
@ -961,10 +901,6 @@ void CoverStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_state: ");
|
||||
out.append(proto_enum_to_string<enums::LegacyCoverState>(this->legacy_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" position: ");
|
||||
snprintf(buffer, sizeof(buffer), "%g", this->position);
|
||||
out.append(buffer);
|
||||
@ -996,14 +932,6 @@ void CoverCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_legacy_command: ");
|
||||
out.append(YESNO(this->has_legacy_command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_command: ");
|
||||
out.append(proto_enum_to_string<enums::LegacyCoverCommand>(this->legacy_command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_position: ");
|
||||
out.append(YESNO(this->has_position));
|
||||
out.append("\n");
|
||||
@ -1115,10 +1043,6 @@ void FanStateResponse::dump_to(std::string &out) const {
|
||||
out.append(YESNO(this->oscillating));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" speed: ");
|
||||
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
out.append("\n");
|
||||
@ -1157,14 +1081,6 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(YESNO(this->state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_speed: ");
|
||||
out.append(YESNO(this->has_speed));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" speed: ");
|
||||
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_oscillating: ");
|
||||
out.append(YESNO(this->has_oscillating));
|
||||
out.append("\n");
|
||||
@ -1231,22 +1147,6 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" legacy_supports_brightness: ");
|
||||
out.append(YESNO(this->legacy_supports_brightness));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_supports_rgb: ");
|
||||
out.append(YESNO(this->legacy_supports_rgb));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_supports_white_value: ");
|
||||
out.append(YESNO(this->legacy_supports_white_value));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_supports_color_temperature: ");
|
||||
out.append(YESNO(this->legacy_supports_color_temperature));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" min_mireds: ");
|
||||
snprintf(buffer, sizeof(buffer), "%g", this->min_mireds);
|
||||
out.append(buffer);
|
||||
@ -1537,10 +1437,6 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_last_reset_type: ");
|
||||
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->legacy_last_reset_type));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
@ -2107,10 +2003,6 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_supports_away: ");
|
||||
out.append(YESNO(this->legacy_supports_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_action: ");
|
||||
out.append(YESNO(this->supports_action));
|
||||
out.append("\n");
|
||||
@ -2223,10 +2115,6 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unused_legacy_away: ");
|
||||
out.append(YESNO(this->unused_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" action: ");
|
||||
out.append(proto_enum_to_string<enums::ClimateAction>(this->action));
|
||||
out.append("\n");
|
||||
@ -2313,14 +2201,6 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unused_has_legacy_away: ");
|
||||
out.append(YESNO(this->unused_has_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unused_legacy_away: ");
|
||||
out.append(YESNO(this->unused_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_fan_mode: ");
|
||||
out.append(YESNO(this->has_fan_mode));
|
||||
out.append("\n");
|
||||
@ -3053,66 +2933,6 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void BluetoothServiceData::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothServiceData {\n");
|
||||
out.append(" uuid: ");
|
||||
out.append("'").append(this->uuid).append("'");
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->legacy_data) {
|
||||
out.append(" legacy_data: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothLEAdvertisementResponse {\n");
|
||||
out.append(" address: ");
|
||||
snprintf(buffer, sizeof(buffer), "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append(format_hex_pretty(this->name));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" rssi: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRId32, this->rssi);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->service_uuids) {
|
||||
out.append(" service_uuids: ");
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->service_data) {
|
||||
out.append(" service_data: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->manufacturer_data) {
|
||||
out.append(" manufacturer_data: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" address_type: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->address_type);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothLERawAdvertisement {\n");
|
||||
|
@ -13,11 +13,180 @@ namespace bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy.connection";
|
||||
|
||||
static std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
|
||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||
return std::vector<uint64_t>{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]),
|
||||
((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])};
|
||||
}
|
||||
|
||||
void BluetoothConnection::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Connection:");
|
||||
BLEClientBase::dump_config();
|
||||
}
|
||||
|
||||
void BluetoothConnection::loop() {
|
||||
BLEClientBase::loop();
|
||||
|
||||
// Early return if no active connection or not in service discovery phase
|
||||
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle service discovery
|
||||
this->send_service_for_discovery_();
|
||||
}
|
||||
|
||||
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||
// Send disconnection notification
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, reason);
|
||||
|
||||
// Important: If we were in the middle of sending services, we do NOT send
|
||||
// send_gatt_services_done() here. This ensures the client knows that
|
||||
// the service discovery was interrupted and can retry. The client
|
||||
// (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT)
|
||||
// to detect incomplete service discovery rather than relying on us to
|
||||
// tell them about a partial list.
|
||||
this->set_address(0);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_connections_free();
|
||||
}
|
||||
|
||||
void BluetoothConnection::send_service_for_discovery_() {
|
||||
if (this->send_service_ == this->service_count_) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_gatt_services_done(this->address_);
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
this->release_services();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Early return if no API connection
|
||||
auto *api_conn = this->proxy_->get_api_connection();
|
||||
if (api_conn == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send next service
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t service_count = 1;
|
||||
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
|
||||
&service_result, &service_count, this->send_service_);
|
||||
this->send_service_++;
|
||||
|
||||
if (service_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), this->send_service_ - 1, service_status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service_count == 0) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->connection_index_,
|
||||
this->address_str().c_str(), service_count);
|
||||
return;
|
||||
}
|
||||
|
||||
api::BluetoothGATTGetServicesResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.services.reserve(1); // Always one service per response in this implementation
|
||||
api::BluetoothGATTService service_resp;
|
||||
service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
|
||||
service_resp.handle = service_result.start_handle;
|
||||
|
||||
// Get the number of characteristics directly with one call
|
||||
uint16_t total_char_count = 0;
|
||||
esp_gatt_status_t char_count_status =
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||
|
||||
if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
|
||||
// Only reserve if we successfully got a count
|
||||
service_resp.characteristics.reserve(total_char_count);
|
||||
} else if (char_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), char_count_status);
|
||||
}
|
||||
|
||||
// Now process characteristics
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
while (true) { // characteristics
|
||||
uint16_t char_count = 1;
|
||||
esp_gatt_status_t char_status =
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
|
||||
service_result.end_handle, &char_result, &char_count, char_offset);
|
||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), char_status);
|
||||
break;
|
||||
}
|
||||
if (char_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
api::BluetoothGATTCharacteristic characteristic_resp;
|
||||
characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
char_offset++;
|
||||
|
||||
// Get the number of descriptors directly with one call
|
||||
uint16_t total_desc_count = 0;
|
||||
esp_gatt_status_t desc_count_status =
|
||||
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle,
|
||||
service_result.end_handle, 0, &total_desc_count);
|
||||
|
||||
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
|
||||
// Only reserve if we successfully got a count
|
||||
characteristic_resp.descriptors.reserve(total_desc_count);
|
||||
} else if (desc_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
||||
}
|
||||
|
||||
// Now process descriptors
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (true) { // descriptors
|
||||
uint16_t desc_count = 1;
|
||||
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
|
||||
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), desc_status);
|
||||
break;
|
||||
}
|
||||
if (desc_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
api::BluetoothGATTDescriptor descriptor_resp;
|
||||
descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
||||
desc_offset++;
|
||||
}
|
||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
||||
}
|
||||
resp.services.push_back(std::move(service_resp));
|
||||
|
||||
// Send the message (we already checked api_conn is not null at the beginning)
|
||||
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||
@ -25,22 +194,16 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
||||
this->set_address(0);
|
||||
this->proxy_->send_connections_free();
|
||||
this->reset_connection_(param->disconnect.reason);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, param->close.reason);
|
||||
this->set_address(0);
|
||||
this->proxy_->send_connections_free();
|
||||
this->reset_connection_(param->close.reason);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
|
||||
this->set_address(0);
|
||||
this->proxy_->send_connections_free();
|
||||
this->reset_connection_(param->open.status);
|
||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
||||
this->proxy_->send_connections_free();
|
||||
|
@ -12,6 +12,7 @@ class BluetoothProxy;
|
||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
@ -27,6 +28,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
protected:
|
||||
friend class BluetoothProxy;
|
||||
|
||||
void send_service_for_discovery_();
|
||||
void reset_connection_(esp_err_t reason);
|
||||
|
||||
// Memory optimized layout for 32-bit systems
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
BluetoothProxy *proxy_;
|
||||
|
@ -11,19 +11,6 @@ namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy";
|
||||
static const int DONE_SENDING_SERVICES = -2;
|
||||
|
||||
std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
|
||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||
return std::vector<uint64_t>{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]),
|
||||
((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])};
|
||||
}
|
||||
|
||||
// Batch size for BLE advertisements to maximize WiFi efficiency
|
||||
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||
@ -140,46 +127,6 @@ void BluetoothProxy::flush_pending_advertisements() {
|
||||
this->advertisement_count_ = 0;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
api::BluetoothLEAdvertisementResponse resp;
|
||||
resp.address = device.address_uint64();
|
||||
resp.address_type = device.get_address_type();
|
||||
if (!device.get_name().empty())
|
||||
resp.name = device.get_name();
|
||||
resp.rssi = device.get_rssi();
|
||||
|
||||
// Pre-allocate vectors based on known sizes
|
||||
auto service_uuids = device.get_service_uuids();
|
||||
resp.service_uuids.reserve(service_uuids.size());
|
||||
for (auto &uuid : service_uuids) {
|
||||
resp.service_uuids.emplace_back(uuid.to_string());
|
||||
}
|
||||
|
||||
// Pre-allocate service data vector
|
||||
auto service_datas = device.get_service_datas();
|
||||
resp.service_data.reserve(service_datas.size());
|
||||
for (auto &data : service_datas) {
|
||||
resp.service_data.emplace_back();
|
||||
auto &service_data = resp.service_data.back();
|
||||
service_data.uuid = data.uuid.to_string();
|
||||
service_data.data.assign(data.data.begin(), data.data.end());
|
||||
}
|
||||
|
||||
// Pre-allocate manufacturer data vector
|
||||
auto manufacturer_datas = device.get_manufacturer_datas();
|
||||
resp.manufacturer_data.reserve(manufacturer_datas.size());
|
||||
for (auto &data : manufacturer_datas) {
|
||||
resp.manufacturer_data.emplace_back();
|
||||
auto &manufacturer_data = resp.manufacturer_data.back();
|
||||
manufacturer_data.uuid = data.uuid.to_string();
|
||||
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
||||
}
|
||||
|
||||
this->api_connection_->send_message(resp, api::BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
||||
}
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@ -213,130 +160,12 @@ void BluetoothProxy::loop() {
|
||||
}
|
||||
|
||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||
static uint32_t last_flush_time = 0;
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Flush accumulated advertisements every 100ms
|
||||
if (now - last_flush_time >= 100) {
|
||||
if (now - this->last_advertisement_flush_time_ >= 100) {
|
||||
this->flush_pending_advertisements();
|
||||
last_flush_time = now;
|
||||
}
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->send_service_ == connection->service_count_) {
|
||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->send_gatt_services_done(connection->get_address());
|
||||
if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
connection->release_services();
|
||||
}
|
||||
} else if (connection->send_service_ >= 0) {
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t service_count = 1;
|
||||
esp_gatt_status_t service_status =
|
||||
esp_ble_gattc_get_service(connection->get_gattc_if(), connection->get_conn_id(), nullptr, &service_result,
|
||||
&service_count, connection->send_service_);
|
||||
connection->send_service_++;
|
||||
if (service_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d",
|
||||
connection->get_connection_index(), connection->address_str().c_str(), connection->send_service_ - 1,
|
||||
service_status);
|
||||
continue;
|
||||
}
|
||||
if (service_count == 0) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d",
|
||||
connection->get_connection_index(), connection->address_str().c_str(), service_count);
|
||||
continue;
|
||||
}
|
||||
api::BluetoothGATTGetServicesResponse resp;
|
||||
resp.address = connection->get_address();
|
||||
resp.services.reserve(1); // Always one service per response in this implementation
|
||||
api::BluetoothGATTService service_resp;
|
||||
service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
|
||||
service_resp.handle = service_result.start_handle;
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
// Get the number of characteristics directly with one call
|
||||
uint16_t total_char_count = 0;
|
||||
esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
|
||||
connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
|
||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||
|
||||
if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
|
||||
// Only reserve if we successfully got a count
|
||||
service_resp.characteristics.reserve(total_char_count);
|
||||
} else if (char_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), char_count_status);
|
||||
}
|
||||
|
||||
// Now process characteristics
|
||||
while (true) { // characteristics
|
||||
uint16_t char_count = 1;
|
||||
esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
|
||||
connection->get_gattc_if(), connection->get_conn_id(), service_result.start_handle,
|
||||
service_result.end_handle, &char_result, &char_count, char_offset);
|
||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), char_status);
|
||||
break;
|
||||
}
|
||||
if (char_count == 0) {
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTCharacteristic characteristic_resp;
|
||||
characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
char_offset++;
|
||||
|
||||
// Get the number of descriptors directly with one call
|
||||
uint16_t total_desc_count = 0;
|
||||
esp_gatt_status_t desc_count_status =
|
||||
esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
|
||||
char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
|
||||
|
||||
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
|
||||
// Only reserve if we successfully got a count
|
||||
characteristic_resp.descriptors.reserve(total_desc_count);
|
||||
} else if (desc_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
||||
connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
|
||||
desc_count_status);
|
||||
}
|
||||
|
||||
// Now process descriptors
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (true) { // descriptors
|
||||
uint16_t desc_count = 1;
|
||||
esp_gatt_status_t desc_status =
|
||||
esp_ble_gattc_get_all_descr(connection->get_gattc_if(), connection->get_conn_id(),
|
||||
char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), desc_status);
|
||||
break;
|
||||
}
|
||||
if (desc_count == 0) {
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTDescriptor descriptor_resp;
|
||||
descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
||||
desc_offset++;
|
||||
}
|
||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
||||
}
|
||||
resp.services.push_back(std::move(service_resp));
|
||||
this->api_connection_->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||
}
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
static const int DONE_SENDING_SERVICES = -2;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
@ -131,9 +132,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
}
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
#endif
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
@ -149,7 +147,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
||||
std::unique_ptr<api::BluetoothLERawAdvertisementsResponse> response_;
|
||||
|
||||
// Group 3: 1-byte types grouped together
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
|
||||
// Group 4: 1-byte types grouped together
|
||||
bool active_;
|
||||
uint8_t advertisement_count_{0};
|
||||
// 2 bytes used, 2 bytes padding
|
||||
|
@ -31,6 +31,7 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
CoreModel,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
@ -713,6 +714,7 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||
cg.add_define(CoreModel.MULTI_ATOMICS)
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
@ -128,46 +128,53 @@ void ESP32BLETracker::loop() {
|
||||
uint8_t write_idx = this->ring_write_index_.load(std::memory_order_acquire);
|
||||
|
||||
while (read_idx != write_idx) {
|
||||
// Process one result at a time directly from ring buffer
|
||||
BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx];
|
||||
// Calculate how many contiguous results we can process in one batch
|
||||
// If write > read: process all results from read to write
|
||||
// If write <= read (wraparound): process from read to end of buffer first
|
||||
size_t batch_size = (write_idx > read_idx) ? (write_idx - read_idx) : (SCAN_RESULT_BUFFER_SIZE - read_idx);
|
||||
|
||||
// Process the batch for raw advertisements
|
||||
if (this->raw_advertisements_) {
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(&scan_result, 1);
|
||||
listener->parse_devices(&this->scan_ring_buffer_[read_idx], batch_size);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(&scan_result, 1);
|
||||
client->parse_devices(&this->scan_ring_buffer_[read_idx], batch_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Process individual results for parsed advertisements
|
||||
if (this->parse_advertisements_) {
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(scan_result);
|
||||
for (size_t i = 0; i < batch_size; i++) {
|
||||
BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx + i];
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(scan_result);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||
promote_to_connecting = true;
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||
promote_to_connecting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
}
|
||||
|
||||
// Move to next entry in ring buffer
|
||||
read_idx = (read_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
||||
// Update read index for entire batch
|
||||
read_idx = (read_idx + batch_size) % SCAN_RESULT_BUFFER_SIZE;
|
||||
|
||||
// Store with release to ensure reads complete before index update
|
||||
this->ring_read_index_.store(read_idx, std::memory_order_release);
|
||||
|
@ -15,6 +15,7 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP8266,
|
||||
CoreModel,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
@ -187,6 +188,7 @@ async def to_code(config):
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
cg.add_define(CoreModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
|
@ -7,6 +7,7 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_HOST,
|
||||
CoreModel,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
@ -43,6 +44,7 @@ async def to_code(config):
|
||||
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
|
||||
cg.add_build_flag("-std=gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_define(CoreModel.MULTI_ATOMICS)
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
@ -20,6 +20,7 @@ from esphome.const import (
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
CoreModel,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
@ -260,6 +261,7 @@ async def component_to_code(config):
|
||||
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
||||
cg.add_define(CoreModel.MULTI_NO_ATOMICS)
|
||||
|
||||
# force using arduino framework
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
|
@ -3,6 +3,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import display, spi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FLIP_X,
|
||||
CONF_ID,
|
||||
CONF_INTENSITY,
|
||||
CONF_LAMBDA,
|
||||
@ -14,7 +15,6 @@ CODEOWNERS = ["@rspaargaren"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
CONF_ROTATE_CHIP = "rotate_chip"
|
||||
CONF_FLIP_X = "flip_x"
|
||||
CONF_SCROLL_SPEED = "scroll_speed"
|
||||
CONF_SCROLL_DWELL = "scroll_dwell"
|
||||
CONF_SCROLL_DELAY = "scroll_delay"
|
||||
|
@ -16,6 +16,7 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_RP2040,
|
||||
CoreModel,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine_with_priority
|
||||
from esphome.helpers import copy_file_if_changed, mkdir_p, read_file, write_file
|
||||
@ -171,6 +172,7 @@ async def to_code(config):
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "RP2040")
|
||||
cg.add_define(CoreModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
|
@ -6,6 +6,8 @@ from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_CONTRAST,
|
||||
CONF_EXTERNAL_VCC,
|
||||
CONF_FLIP_X,
|
||||
CONF_FLIP_Y,
|
||||
CONF_INVERT,
|
||||
CONF_LAMBDA,
|
||||
CONF_MODEL,
|
||||
@ -18,9 +20,6 @@ ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
|
||||
SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer)
|
||||
SSD1306Model = ssd1306_base_ns.enum("SSD1306Model")
|
||||
|
||||
CONF_FLIP_X = "flip_x"
|
||||
CONF_FLIP_Y = "flip_y"
|
||||
|
||||
MODELS = {
|
||||
"SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
|
||||
"SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64,
|
||||
|
@ -35,6 +35,14 @@ class Framework(StrEnum):
|
||||
ZEPHYR = "zephyr"
|
||||
|
||||
|
||||
class CoreModel(StrEnum):
|
||||
"""Core model identifiers for ESPHome scheduler."""
|
||||
|
||||
SINGLE = "ESPHOME_CORES_SINGLE"
|
||||
MULTI_NO_ATOMICS = "ESPHOME_CORES_MULTI_NO_ATOMICS"
|
||||
MULTI_ATOMICS = "ESPHOME_CORES_MULTI_ATOMICS"
|
||||
|
||||
|
||||
class PlatformFramework(Enum):
|
||||
"""Combined platform-framework identifiers with tuple values."""
|
||||
|
||||
@ -375,6 +383,8 @@ CONF_FINGER_ID = "finger_id"
|
||||
CONF_FINGERPRINT_COUNT = "fingerprint_count"
|
||||
CONF_FLASH_LENGTH = "flash_length"
|
||||
CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length"
|
||||
CONF_FLIP_X = "flip_x"
|
||||
CONF_FLIP_Y = "flip_y"
|
||||
CONF_FLOW = "flow"
|
||||
CONF_FLOW_CONTROL_PIN = "flow_control_pin"
|
||||
CONF_FONT = "font"
|
||||
|
@ -15,6 +15,9 @@
|
||||
#define ESPHOME_VARIANT "ESP32"
|
||||
#define ESPHOME_DEBUG_SCHEDULER
|
||||
|
||||
// Default threading model for static analysis (ESP32 is multi-core with atomics)
|
||||
#define ESPHOME_CORES_MULTI_ATOMICS
|
||||
|
||||
// logger
|
||||
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
|
||||
|
@ -54,7 +54,7 @@ static void validate_static_string(const char *name) {
|
||||
ESP_LOGW(TAG, "WARNING: Scheduler name '%s' at %p might be on heap (static ref at %p)", name, name, static_str);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
// A note on locking: the `lock_` lock protects the `items_` and `to_add_` containers. It must be taken when writing to
|
||||
// them (i.e. when adding/removing items, but not when changing items). As items are only deleted from the loop task,
|
||||
@ -82,9 +82,9 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
item->callback = std::move(func);
|
||||
item->remove = false;
|
||||
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||
#ifndef ESPHOME_CORES_SINGLE
|
||||
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
||||
// ESP8266 and RP2040 are excluded because they don't need thread-safe defer handling
|
||||
// Single-core platforms don't need thread-safe defer handling
|
||||
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
||||
// Put in defer queue for guaranteed FIFO execution
|
||||
LockGuard guard{this->lock_};
|
||||
@ -92,7 +92,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
this->defer_queue_.push_back(std::move(item));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif /* not ESPHOME_CORES_SINGLE */
|
||||
|
||||
// Get fresh timestamp for new timer/interval - ensures accurate scheduling
|
||||
const auto now = this->millis_64_(millis()); // Fresh millis() call
|
||||
@ -123,7 +123,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, item->get_source(),
|
||||
name_cstr ? name_cstr : "(null)", type_str, delay, static_cast<uint32_t>(item->next_execution_ - now));
|
||||
}
|
||||
#endif
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
LockGuard guard{this->lock_};
|
||||
// If name is provided, do atomic cancel-and-add
|
||||
@ -231,7 +231,7 @@ optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
||||
return item->next_execution_ - now_64;
|
||||
}
|
||||
void HOT Scheduler::call(uint32_t now) {
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||
#ifndef ESPHOME_CORES_SINGLE
|
||||
// Process defer queue first to guarantee FIFO execution order for deferred items.
|
||||
// Previously, defer() used the heap which gave undefined order for equal timestamps,
|
||||
// causing race conditions on multi-core systems (ESP32, BK7200).
|
||||
@ -239,8 +239,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
// - Deferred items (delay=0) go directly to defer_queue_ in set_timer_common_
|
||||
// - Items execute in exact order they were deferred (FIFO guarantee)
|
||||
// - No deferred items exist in to_add_, so processing order doesn't affect correctness
|
||||
// ESP8266 and RP2040 don't use this queue - they fall back to the heap-based approach
|
||||
// (ESP8266: single-core, RP2040: empty mutex implementation).
|
||||
// Single-core platforms don't use this queue and fall back to the heap-based approach.
|
||||
//
|
||||
// Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still
|
||||
// processed here. They are removed from the queue normally via pop_front() but skipped
|
||||
@ -262,7 +261,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
this->execute_item_(item.get(), now);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif /* not ESPHOME_CORES_SINGLE */
|
||||
|
||||
// Convert the fresh timestamp from main loop to 64-bit for scheduler operations
|
||||
const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from Application::loop()
|
||||
@ -274,13 +273,15 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
if (now_64 - last_print > 2000) {
|
||||
last_print = now_64;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> old_items;
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY)
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64,
|
||||
this->millis_major_, this->last_millis_.load(std::memory_order_relaxed));
|
||||
#else
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64,
|
||||
#ifdef ESPHOME_CORES_MULTI_ATOMICS
|
||||
const auto last_dbg = this->last_millis_.load(std::memory_order_relaxed);
|
||||
const auto major_dbg = this->millis_major_.load(std::memory_order_relaxed);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
|
||||
major_dbg, last_dbg);
|
||||
#else /* not ESPHOME_CORES_MULTI_ATOMICS */
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
|
||||
this->millis_major_, this->last_millis_);
|
||||
#endif
|
||||
#endif /* else ESPHOME_CORES_MULTI_ATOMICS */
|
||||
while (!this->empty_()) {
|
||||
std::unique_ptr<SchedulerItem> item;
|
||||
{
|
||||
@ -305,7 +306,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
||||
}
|
||||
}
|
||||
#endif // ESPHOME_DEBUG_SCHEDULER
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
// If we have too many items to remove
|
||||
if (this->to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
|
||||
@ -352,7 +353,7 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
||||
item->get_type_str(), item->get_source(), item_name ? item_name : "(null)", item->interval,
|
||||
item->next_execution_, now_64);
|
||||
#endif
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
// Warning: During callback(), a lot of stuff can happen, including:
|
||||
// - timeouts/intervals get added, potentially invalidating vector pointers
|
||||
@ -460,7 +461,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
size_t total_cancelled = 0;
|
||||
|
||||
// Check all containers for matching items
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||
#ifndef ESPHOME_CORES_SINGLE
|
||||
// Only check defer queue for timeouts (intervals never go there)
|
||||
if (type == SchedulerItem::TIMEOUT) {
|
||||
for (auto &item : this->defer_queue_) {
|
||||
@ -470,7 +471,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif /* not ESPHOME_CORES_SINGLE */
|
||||
|
||||
// Cancel items in the main heap
|
||||
for (auto &item : this->items_) {
|
||||
@ -495,24 +496,53 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
|
||||
uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
// THREAD SAFETY NOTE:
|
||||
// This function can be called from multiple threads simultaneously on ESP32/LibreTiny.
|
||||
// On single-threaded platforms (ESP8266, RP2040), atomics are not needed.
|
||||
// This function has three implementations, based on the precompiler flags
|
||||
// - ESPHOME_CORES_SINGLE - Runs on single-core platforms (ESP8266, RP2040, etc.)
|
||||
// - ESPHOME_CORES_MULTI_NO_ATOMICS - Runs on multi-core platforms without atomics (LibreTiny)
|
||||
// - ESPHOME_CORES_MULTI_ATOMICS - Runs on multi-core platforms with atomics (ESP32, HOST, etc.)
|
||||
//
|
||||
// Make sure all changes are synchronized if you edit this function.
|
||||
//
|
||||
// IMPORTANT: Always pass fresh millis() values to this function. The implementation
|
||||
// handles out-of-order timestamps between threads, but minimizing time differences
|
||||
// helps maintain accuracy.
|
||||
//
|
||||
// The implementation handles the 32-bit rollover (every 49.7 days) by:
|
||||
// 1. Using a lock when detecting rollover to ensure atomic update
|
||||
// 2. Restricting normal updates to forward movement within the same epoch
|
||||
// This prevents race conditions at the rollover boundary without requiring
|
||||
// 64-bit atomics or locking on every call.
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
// LibreTiny: Multi-threaded but lacks atomic operation support
|
||||
// TODO: If LibreTiny ever adds atomic support, remove this entire block and
|
||||
// let it fall through to the atomic-based implementation below
|
||||
// We need to use a lock when near the rollover boundary to prevent races
|
||||
#ifdef ESPHOME_CORES_SINGLE
|
||||
// This is the single core implementation.
|
||||
//
|
||||
// Single-core platforms have no concurrency, so this is a simple implementation
|
||||
// that just tracks 32-bit rollover (every 49.7 days) without any locking or atomics.
|
||||
|
||||
uint16_t major = this->millis_major_;
|
||||
uint32_t last = this->last_millis_;
|
||||
|
||||
// Check for rollover
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
this->millis_major_++;
|
||||
major++;
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
}
|
||||
|
||||
// Only update if time moved forward
|
||||
if (now > last) {
|
||||
this->last_millis_ = now;
|
||||
}
|
||||
|
||||
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
|
||||
return now + (static_cast<uint64_t>(major) << 32);
|
||||
|
||||
#elif defined(ESPHOME_CORES_MULTI_NO_ATOMICS)
|
||||
// This is the multi core no atomics implementation.
|
||||
//
|
||||
// Without atomics, this implementation uses locks more aggressively:
|
||||
// 1. Always locks when near the rollover boundary (within 10 seconds)
|
||||
// 2. Always locks when detecting a large backwards jump
|
||||
// 3. Updates without lock in normal forward progression (accepting minor races)
|
||||
// This is less efficient but necessary without atomic operations.
|
||||
uint16_t major = this->millis_major_;
|
||||
uint32_t last = this->last_millis_;
|
||||
|
||||
// Define a safe window around the rollover point (10 seconds)
|
||||
@ -531,9 +561,10 @@ uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
// True rollover detected (happens every ~49.7 days)
|
||||
this->millis_major_++;
|
||||
major++;
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
|
||||
#endif
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
}
|
||||
// Update last_millis_ while holding lock
|
||||
this->last_millis_ = now;
|
||||
@ -549,58 +580,76 @@ uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
// If now <= last and we're not near rollover, don't update
|
||||
// This minimizes backwards time movement
|
||||
|
||||
#elif !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||
// Multi-threaded platforms with atomic support (ESP32)
|
||||
uint32_t last = this->last_millis_.load(std::memory_order_relaxed);
|
||||
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
|
||||
return now + (static_cast<uint64_t>(major) << 32);
|
||||
|
||||
// If we might be near a rollover (large backwards jump), take the lock for the entire operation
|
||||
// This ensures rollover detection and last_millis_ update are atomic together
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
// Potential rollover - need lock for atomic rollover detection + update
|
||||
LockGuard guard{this->lock_};
|
||||
// Re-read with lock held
|
||||
last = this->last_millis_.load(std::memory_order_relaxed);
|
||||
#elif defined(ESPHOME_CORES_MULTI_ATOMICS)
|
||||
// This is the multi core with atomics implementation.
|
||||
//
|
||||
// Uses atomic operations with acquire/release semantics to ensure coherent
|
||||
// reads of millis_major_ and last_millis_ across cores. Features:
|
||||
// 1. Epoch-coherency retry loop to handle concurrent updates
|
||||
// 2. Lock only taken for actual rollover detection and update
|
||||
// 3. Lock-free CAS updates for normal forward time progression
|
||||
// 4. Memory ordering ensures cores see consistent time values
|
||||
|
||||
for (;;) {
|
||||
uint16_t major = this->millis_major_.load(std::memory_order_acquire);
|
||||
|
||||
/*
|
||||
* Acquire so that if we later decide **not** to take the lock we still
|
||||
* observe a `millis_major_` value coherent with the loaded `last_millis_`.
|
||||
* The acquire load ensures any later read of `millis_major_` sees its
|
||||
* corresponding increment.
|
||||
*/
|
||||
uint32_t last = this->last_millis_.load(std::memory_order_acquire);
|
||||
|
||||
// If we might be near a rollover (large backwards jump), take the lock for the entire operation
|
||||
// This ensures rollover detection and last_millis_ update are atomic together
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
// True rollover detected (happens every ~49.7 days)
|
||||
this->millis_major_++;
|
||||
// Potential rollover - need lock for atomic rollover detection + update
|
||||
LockGuard guard{this->lock_};
|
||||
// Re-read with lock held; mutex already provides ordering
|
||||
last = this->last_millis_.load(std::memory_order_relaxed);
|
||||
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
// True rollover detected (happens every ~49.7 days)
|
||||
this->millis_major_.fetch_add(1, std::memory_order_relaxed);
|
||||
major++;
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
|
||||
#endif
|
||||
}
|
||||
// Update last_millis_ while holding lock to prevent races
|
||||
this->last_millis_.store(now, std::memory_order_relaxed);
|
||||
} else {
|
||||
// Normal case: Try lock-free update, but only allow forward movement within same epoch
|
||||
// This prevents accidentally moving backwards across a rollover boundary
|
||||
while (now > last && (now - last) < HALF_MAX_UINT32) {
|
||||
if (this->last_millis_.compare_exchange_weak(last, now, std::memory_order_relaxed)) {
|
||||
break;
|
||||
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
}
|
||||
/*
|
||||
* Update last_millis_ while holding the lock to prevent races
|
||||
* Publish the new low-word *after* bumping `millis_major_` (done above)
|
||||
* so readers never see a mismatched pair.
|
||||
*/
|
||||
this->last_millis_.store(now, std::memory_order_release);
|
||||
} else {
|
||||
// Normal case: Try lock-free update, but only allow forward movement within same epoch
|
||||
// This prevents accidentally moving backwards across a rollover boundary
|
||||
while (now > last && (now - last) < HALF_MAX_UINT32) {
|
||||
if (this->last_millis_.compare_exchange_weak(last, now,
|
||||
std::memory_order_release, // success
|
||||
std::memory_order_relaxed)) { // failure
|
||||
break;
|
||||
}
|
||||
// CAS failure means no data was published; relaxed is fine
|
||||
// last is automatically updated by compare_exchange_weak if it fails
|
||||
}
|
||||
// last is automatically updated by compare_exchange_weak if it fails
|
||||
}
|
||||
uint16_t major_end = this->millis_major_.load(std::memory_order_relaxed);
|
||||
if (major_end == major)
|
||||
return now + (static_cast<uint64_t>(major) << 32);
|
||||
}
|
||||
// Unreachable - the loop always returns when major_end == major
|
||||
__builtin_unreachable();
|
||||
|
||||
#else
|
||||
// Single-threaded platforms (ESP8266, RP2040): No atomics needed
|
||||
uint32_t last = this->last_millis_;
|
||||
|
||||
// Check for rollover
|
||||
if (now < last && (last - now) > HALF_MAX_UINT32) {
|
||||
this->millis_major_++;
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
|
||||
#error \
|
||||
"No platform threading model defined. One of ESPHOME_CORES_SINGLE, ESPHOME_CORES_MULTI_NO_ATOMICS, or ESPHOME_CORES_MULTI_ATOMICS must be defined."
|
||||
#endif
|
||||
}
|
||||
|
||||
// Only update if time moved forward
|
||||
if (now > last) {
|
||||
this->last_millis_ = now;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
|
||||
return now + (static_cast<uint64_t>(this->millis_major_) << 32);
|
||||
}
|
||||
|
||||
bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
|
||||
|
@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY)
|
||||
#ifdef ESPHOME_CORES_MULTI_ATOMICS
|
||||
#include <atomic>
|
||||
#endif
|
||||
|
||||
@ -204,23 +205,40 @@ class Scheduler {
|
||||
Mutex lock_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> items_;
|
||||
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||
// ESP8266 and RP2040 don't need the defer queue because:
|
||||
// ESP8266: Single-core with no preemptive multitasking
|
||||
// RP2040: Currently has empty mutex implementation in ESPHome
|
||||
// Both platforms save 40 bytes of RAM by excluding this
|
||||
#ifndef ESPHOME_CORES_SINGLE
|
||||
// Single-core platforms don't need the defer queue and save 40 bytes of RAM
|
||||
std::deque<std::unique_ptr<SchedulerItem>> defer_queue_; // FIFO queue for defer() calls
|
||||
#endif
|
||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY)
|
||||
// Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
|
||||
#endif /* ESPHOME_CORES_SINGLE */
|
||||
uint32_t to_remove_{0};
|
||||
|
||||
#ifdef ESPHOME_CORES_MULTI_ATOMICS
|
||||
/*
|
||||
* Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
|
||||
*
|
||||
* MEMORY-ORDERING NOTE
|
||||
* --------------------
|
||||
* `last_millis_` and `millis_major_` form a single 64-bit timestamp split in half.
|
||||
* Writers publish `last_millis_` with memory_order_release and readers use
|
||||
* memory_order_acquire. This ensures that once a reader sees the new low word,
|
||||
* it also observes the corresponding increment of `millis_major_`.
|
||||
*/
|
||||
std::atomic<uint32_t> last_millis_{0};
|
||||
#else
|
||||
#else /* not ESPHOME_CORES_MULTI_ATOMICS */
|
||||
// Platforms without atomic support or single-threaded platforms
|
||||
uint32_t last_millis_{0};
|
||||
#endif
|
||||
// millis_major_ is protected by lock when incrementing
|
||||
#endif /* else ESPHOME_CORES_MULTI_ATOMICS */
|
||||
|
||||
/*
|
||||
* Upper 16 bits of the 64-bit millis counter. Incremented only while holding
|
||||
* `lock_`; read concurrently. Atomic (relaxed) avoids a formal data race.
|
||||
* Ordering relative to `last_millis_` is provided by its release store and the
|
||||
* corresponding acquire loads.
|
||||
*/
|
||||
#ifdef ESPHOME_CORES_MULTI_ATOMICS
|
||||
std::atomic<uint16_t> millis_major_{0};
|
||||
#else /* not ESPHOME_CORES_MULTI_ATOMICS */
|
||||
uint16_t millis_major_{0};
|
||||
uint32_t to_remove_{0};
|
||||
#endif /* else ESPHOME_CORES_MULTI_ATOMICS */
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
@ -971,11 +971,11 @@ class RepeatedTypeInfo(TypeInfo):
|
||||
|
||||
def build_type_usage_map(
|
||||
file_desc: descriptor.FileDescriptorProto,
|
||||
) -> tuple[dict[str, str | None], dict[str, str | None], dict[str, int]]:
|
||||
) -> tuple[dict[str, str | None], dict[str, str | None], dict[str, int], set[str]]:
|
||||
"""Build mappings for both enums and messages to their ifdefs based on usage.
|
||||
|
||||
Returns:
|
||||
tuple: (enum_ifdef_map, message_ifdef_map, message_source_map)
|
||||
tuple: (enum_ifdef_map, message_ifdef_map, message_source_map, used_messages)
|
||||
"""
|
||||
enum_ifdef_map: dict[str, str | None] = {}
|
||||
message_ifdef_map: dict[str, str | None] = {}
|
||||
@ -988,6 +988,7 @@ def build_type_usage_map(
|
||||
message_usage: dict[
|
||||
str, set[str]
|
||||
] = {} # message_name -> set of message names that use it
|
||||
used_messages: set[str] = set() # Track which messages are actually used
|
||||
|
||||
# Build message name to ifdef mapping for quick lookup
|
||||
message_to_ifdef: dict[str, str | None] = {
|
||||
@ -996,17 +997,26 @@ def build_type_usage_map(
|
||||
|
||||
# Analyze field usage
|
||||
for message in file_desc.message_type:
|
||||
# Skip deprecated messages entirely
|
||||
if message.options.deprecated:
|
||||
continue
|
||||
|
||||
for field in message.field:
|
||||
# Skip deprecated fields when tracking enum usage
|
||||
if field.options.deprecated:
|
||||
continue
|
||||
|
||||
type_name = field.type_name.split(".")[-1] if field.type_name else None
|
||||
if not type_name:
|
||||
continue
|
||||
|
||||
# Track enum usage
|
||||
# Track enum usage (only from non-deprecated fields)
|
||||
if field.type == 14: # TYPE_ENUM
|
||||
enum_usage.setdefault(type_name, set()).add(message.name)
|
||||
# Track message usage
|
||||
elif field.type == 11: # TYPE_MESSAGE
|
||||
message_usage.setdefault(type_name, set()).add(message.name)
|
||||
used_messages.add(type_name)
|
||||
|
||||
# Helper to get unique ifdef from a set of messages
|
||||
def get_unique_ifdef(message_names: set[str]) -> str | None:
|
||||
@ -1069,12 +1079,18 @@ def build_type_usage_map(
|
||||
# Build message source map
|
||||
# First pass: Get explicit sources for messages with source option or id
|
||||
for msg in file_desc.message_type:
|
||||
# Skip deprecated messages
|
||||
if msg.options.deprecated:
|
||||
continue
|
||||
|
||||
if msg.options.HasExtension(pb.source):
|
||||
# Explicit source option takes precedence
|
||||
message_source_map[msg.name] = get_opt(msg, pb.source, SOURCE_BOTH)
|
||||
elif msg.options.HasExtension(pb.id):
|
||||
# Service messages (with id) default to SOURCE_BOTH
|
||||
message_source_map[msg.name] = SOURCE_BOTH
|
||||
# Service messages are always used
|
||||
used_messages.add(msg.name)
|
||||
|
||||
# Second pass: Determine sources for embedded messages based on their usage
|
||||
for msg in file_desc.message_type:
|
||||
@ -1103,7 +1119,12 @@ def build_type_usage_map(
|
||||
# Not used by any message and no explicit source - default to encode-only
|
||||
message_source_map[msg.name] = SOURCE_SERVER
|
||||
|
||||
return enum_ifdef_map, message_ifdef_map, message_source_map
|
||||
return (
|
||||
enum_ifdef_map,
|
||||
message_ifdef_map,
|
||||
message_source_map,
|
||||
used_messages,
|
||||
)
|
||||
|
||||
|
||||
def build_enum_type(desc, enum_ifdef_map) -> tuple[str, str, str]:
|
||||
@ -1145,6 +1166,10 @@ def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int:
|
||||
total_size = 0
|
||||
|
||||
for field in desc.field:
|
||||
# Skip deprecated fields
|
||||
if field.options.deprecated:
|
||||
continue
|
||||
|
||||
ti = create_field_type_info(field)
|
||||
|
||||
# Add estimated size for this field
|
||||
@ -1213,6 +1238,10 @@ def build_message_type(
|
||||
public_content.append("#endif")
|
||||
|
||||
for field in desc.field:
|
||||
# Skip deprecated fields completely
|
||||
if field.options.deprecated:
|
||||
continue
|
||||
|
||||
ti = create_field_type_info(field)
|
||||
|
||||
# Skip field declarations for fields that are in the base class
|
||||
@ -1459,8 +1488,10 @@ def find_common_fields(
|
||||
if not messages:
|
||||
return []
|
||||
|
||||
# Start with fields from the first message
|
||||
first_msg_fields = {field.name: field for field in messages[0].field}
|
||||
# Start with fields from the first message (excluding deprecated fields)
|
||||
first_msg_fields = {
|
||||
field.name: field for field in messages[0].field if not field.options.deprecated
|
||||
}
|
||||
common_fields = []
|
||||
|
||||
# Check each field to see if it exists in all messages with same type
|
||||
@ -1471,6 +1502,9 @@ def find_common_fields(
|
||||
for msg in messages[1:]:
|
||||
found = False
|
||||
for other_field in msg.field:
|
||||
# Skip deprecated fields
|
||||
if other_field.options.deprecated:
|
||||
continue
|
||||
if (
|
||||
other_field.name == field_name
|
||||
and other_field.type == field.type
|
||||
@ -1599,6 +1633,10 @@ def build_service_message_type(
|
||||
message_source_map: dict[str, int],
|
||||
) -> tuple[str, str] | None:
|
||||
"""Builds the service message type."""
|
||||
# Skip deprecated messages
|
||||
if mt.options.deprecated:
|
||||
return None
|
||||
|
||||
snake = camel_to_snake(mt.name)
|
||||
id_: int | None = get_opt(mt, pb.id)
|
||||
if id_ is None:
|
||||
@ -1700,12 +1738,18 @@ namespace api {
|
||||
content += "namespace enums {\n\n"
|
||||
|
||||
# Build dynamic ifdef mappings for both enums and messages
|
||||
enum_ifdef_map, message_ifdef_map, message_source_map = build_type_usage_map(file)
|
||||
enum_ifdef_map, message_ifdef_map, message_source_map, used_messages = (
|
||||
build_type_usage_map(file)
|
||||
)
|
||||
|
||||
# Simple grouping of enums by ifdef
|
||||
current_ifdef = None
|
||||
|
||||
for enum in file.enum_type:
|
||||
# Skip deprecated enums
|
||||
if enum.options.deprecated:
|
||||
continue
|
||||
|
||||
s, c, dc = build_enum_type(enum, enum_ifdef_map)
|
||||
enum_ifdef = enum_ifdef_map.get(enum.name)
|
||||
|
||||
@ -1756,6 +1800,14 @@ namespace api {
|
||||
current_ifdef = None
|
||||
|
||||
for m in mt:
|
||||
# Skip deprecated messages
|
||||
if m.options.deprecated:
|
||||
continue
|
||||
|
||||
# Skip messages that aren't used (unless they have an ID/service message)
|
||||
if m.name not in used_messages and not m.options.HasExtension(pb.id):
|
||||
continue
|
||||
|
||||
s, c, dc = build_message_type(m, base_class_fields, message_source_map)
|
||||
msg_ifdef = message_ifdef_map.get(m.name)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user