mirror of
https://github.com/esphome/esphome.git
synced 2025-07-31 15:37:49 +00:00
Merge remote-tracking branch 'upstream/dev' into memory
This commit is contained in:
commit
e76c40a1e7
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@ -41,7 +41,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
python -m venv venv
|
||||
./venv/Scripts/activate
|
||||
source ./venv/Scripts/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
110
.github/workflows/ci.yml
vendored
110
.github/workflows/ci.yml
vendored
@ -66,6 +66,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -87,6 +89,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -108,6 +112,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -129,6 +135,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -214,7 +222,7 @@ jobs:
|
||||
- name: Run pytest
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
./venv/Scripts/activate
|
||||
. ./venv/Scripts/activate.ps1
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Run pytest
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||
@ -232,11 +240,54 @@ jobs:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
|
||||
determine-jobs:
|
||||
name: Determine which jobs to run
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
outputs:
|
||||
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||
clang-format: ${{ steps.determine.outputs.clang-format }}
|
||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Determine which tests to run
|
||||
id: determine
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
output=$(python script/determine-jobs.py)
|
||||
echo "Test determination output:"
|
||||
echo "$output" | jq
|
||||
|
||||
# Extract individual fields
|
||||
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||
echo "clang-format=$(echo "$output" | jq -r '.clang_format')" >> $GITHUB_OUTPUT
|
||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||
|
||||
integration-tests:
|
||||
name: Run integration tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -271,6 +322,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.clang-format == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
@ -304,6 +357,8 @@ jobs:
|
||||
- pylint
|
||||
- pytest
|
||||
- pyupgrade
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.clang-tidy == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
strategy:
|
||||
@ -344,6 +399,9 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@ -408,50 +466,18 @@ jobs:
|
||||
# yamllint disable-line rule:line-length
|
||||
if: always()
|
||||
|
||||
list-components:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
outputs:
|
||||
components: ${{ steps.list-components.outputs.components }}
|
||||
count: ${{ steps.list-components.outputs.count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Find changed components
|
||||
id: list-components
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
components=$(script/list-components.py --changed)
|
||||
output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))')
|
||||
count=$(echo "$output_components" | jq length)
|
||||
|
||||
echo "components=$output_components" >> $GITHUB_OUTPUT
|
||||
echo "count=$count" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "$count Components:"
|
||||
echo "$output_components" | jq
|
||||
|
||||
test-build-components:
|
||||
name: Component test ${{ matrix.file }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- list-components
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) > 0 && fromJSON(needs.list-components.outputs.count) < 100
|
||||
- determine-jobs
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 && fromJSON(needs.determine-jobs.outputs.component-test-count) < 100
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.list-components.outputs.components) }}
|
||||
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@ -479,8 +505,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- list-components
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
|
||||
- determine-jobs
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
||||
outputs:
|
||||
matrix: ${{ steps.split.outputs.components }}
|
||||
steps:
|
||||
@ -489,7 +515,7 @@ jobs:
|
||||
- name: Split components into 20 groups
|
||||
id: split
|
||||
run: |
|
||||
components=$(echo '${{ needs.list-components.outputs.components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
|
||||
components=$(echo '${{ needs.determine-jobs.outputs.changed-components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
|
||||
echo "components=$components" >> $GITHUB_OUTPUT
|
||||
|
||||
test-build-components-split:
|
||||
@ -497,9 +523,9 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- list-components
|
||||
- determine-jobs
|
||||
- test-build-components-splitter
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
@ -550,7 +576,7 @@ jobs:
|
||||
- integration-tests
|
||||
- pyupgrade
|
||||
- clang-tidy
|
||||
- list-components
|
||||
- determine-jobs
|
||||
- test-build-components
|
||||
- test-build-components-splitter
|
||||
- test-build-components-split
|
||||
|
@ -374,6 +374,7 @@ message CoverCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
|
||||
@ -387,6 +388,7 @@ message CoverCommandRequest {
|
||||
bool has_tilt = 6;
|
||||
float tilt = 7;
|
||||
bool stop = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
|
||||
// ==================== FAN ====================
|
||||
@ -441,6 +443,7 @@ message FanCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
@ -455,6 +458,7 @@ message FanCommandRequest {
|
||||
int32 speed_level = 11;
|
||||
bool has_preset_mode = 12;
|
||||
string preset_mode = 13;
|
||||
uint32 device_id = 14;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
@ -523,6 +527,7 @@ message LightCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
@ -551,6 +556,7 @@ message LightCommandRequest {
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
uint32 device_id = 28;
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
@ -640,9 +646,11 @@ message SwitchCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
||||
// ==================== TEXT SENSOR ====================
|
||||
@ -850,12 +858,14 @@ message ListEntitiesCameraResponse {
|
||||
|
||||
message CameraImageResponse {
|
||||
option (id) = 44;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
uint32 device_id = 4;
|
||||
}
|
||||
message CameraImageRequest {
|
||||
option (id) = 45;
|
||||
@ -980,6 +990,7 @@ message ClimateCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_mode = 2;
|
||||
@ -1005,6 +1016,7 @@ message ClimateCommandRequest {
|
||||
string custom_preset = 21;
|
||||
bool has_target_humidity = 22;
|
||||
float target_humidity = 23;
|
||||
uint32 device_id = 24;
|
||||
}
|
||||
|
||||
// ==================== NUMBER ====================
|
||||
@ -1054,9 +1066,11 @@ message NumberCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
||||
// ==================== SELECT ====================
|
||||
@ -1096,9 +1110,11 @@ message SelectCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
||||
// ==================== SIREN ====================
|
||||
@ -1137,6 +1153,7 @@ message SirenCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SIREN";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
@ -1147,6 +1164,7 @@ message SirenCommandRequest {
|
||||
uint32 duration = 7;
|
||||
bool has_volume = 8;
|
||||
float volume = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
|
||||
// ==================== LOCK ====================
|
||||
@ -1201,12 +1219,14 @@ message LockCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
LockCommand command = 2;
|
||||
|
||||
// Not yet implemented:
|
||||
bool has_code = 3;
|
||||
string code = 4;
|
||||
uint32 device_id = 5;
|
||||
}
|
||||
|
||||
// ==================== BUTTON ====================
|
||||
@ -1232,8 +1252,10 @@ message ButtonCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BUTTON";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
uint32 device_id = 2;
|
||||
}
|
||||
|
||||
// ==================== MEDIA PLAYER ====================
|
||||
@ -1301,6 +1323,7 @@ message MediaPlayerCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
|
||||
@ -1315,6 +1338,7 @@ message MediaPlayerCommandRequest {
|
||||
|
||||
bool has_announcement = 8;
|
||||
bool announcement = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
|
||||
// ==================== BLUETOOTH ====================
|
||||
@ -1843,9 +1867,11 @@ message AlarmControlPanelCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
AlarmControlPanelStateCommand command = 2;
|
||||
string code = 3;
|
||||
uint32 device_id = 4;
|
||||
}
|
||||
|
||||
// ===================== TEXT =====================
|
||||
@ -1892,9 +1918,11 @@ message TextCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
||||
|
||||
@ -1936,11 +1964,13 @@ message DateCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
uint32 year = 2;
|
||||
uint32 month = 3;
|
||||
uint32 day = 4;
|
||||
uint32 device_id = 5;
|
||||
}
|
||||
|
||||
// ==================== DATETIME TIME ====================
|
||||
@ -1981,11 +2011,13 @@ message TimeCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
uint32 hour = 2;
|
||||
uint32 minute = 3;
|
||||
uint32 second = 4;
|
||||
uint32 device_id = 5;
|
||||
}
|
||||
|
||||
// ==================== EVENT ====================
|
||||
@ -2065,11 +2097,13 @@ message ValveCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VALVE";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_position = 2;
|
||||
float position = 3;
|
||||
bool stop = 4;
|
||||
uint32 device_id = 5;
|
||||
}
|
||||
|
||||
// ==================== DATETIME DATETIME ====================
|
||||
@ -2108,9 +2142,11 @@ message DateTimeCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 epoch_seconds = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
||||
// ==================== UPDATE ====================
|
||||
@ -2160,7 +2196,9 @@ message UpdateCommandRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
UpdateCommand command = 2;
|
||||
uint32 device_id = 3;
|
||||
}
|
||||
|
@ -1920,7 +1920,7 @@ uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
||||
case ListEntitiesClimateResponse::MESSAGE_TYPE:
|
||||
return ListEntitiesClimateResponse::ESTIMATED_SIZE;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#ifdef USE_CAMERA
|
||||
case ListEntitiesCameraResponse::MESSAGE_TYPE:
|
||||
return ListEntitiesCameraResponse::ESTIMATED_SIZE;
|
||||
#endif
|
||||
|
@ -623,6 +623,10 @@ bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -654,6 +658,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->has_tilt);
|
||||
buffer.encode_float(7, this->tilt);
|
||||
buffer.encode_bool(8, this->stop);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void CoverCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -664,6 +669,7 @@ void CoverCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_tilt, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
@ -889,6 +895,10 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_preset_mode = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -927,6 +937,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_int32(11, this->speed_level);
|
||||
buffer.encode_bool(12, this->has_preset_mode);
|
||||
buffer.encode_string(13, this->preset_mode);
|
||||
buffer.encode_uint32(14, this->device_id);
|
||||
}
|
||||
void FanCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -942,6 +953,7 @@ void FanCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_int32_field(total_size, 1, this->speed_level, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_preset_mode, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->preset_mode, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
@ -1247,6 +1259,10 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_effect = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 28: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1335,6 +1351,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(17, this->flash_length);
|
||||
buffer.encode_bool(18, this->has_effect);
|
||||
buffer.encode_string(19, this->effect);
|
||||
buffer.encode_uint32(28, this->device_id);
|
||||
}
|
||||
void LightCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -1364,6 +1381,7 @@ void LightCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->flash_length, false);
|
||||
ProtoSize::add_bool_field(total_size, 2, this->has_effect, false);
|
||||
ProtoSize::add_string_field(total_size, 2, this->effect, false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
@ -1637,6 +1655,10 @@ bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1654,10 +1676,12 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void SwitchCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
@ -2293,6 +2317,10 @@ bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->done = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2321,11 +2349,13 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bytes(2, reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size());
|
||||
buffer.encode_bool(3, this->done);
|
||||
buffer.encode_uint32(4, this->device_id);
|
||||
}
|
||||
void CameraImageResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->data, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->done, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
@ -2749,6 +2779,10 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
this->has_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 24: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2817,6 +2851,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(21, this->custom_preset);
|
||||
buffer.encode_bool(22, this->has_target_humidity);
|
||||
buffer.encode_float(23, this->target_humidity);
|
||||
buffer.encode_uint32(24, this->device_id);
|
||||
}
|
||||
void ClimateCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -2842,6 +2877,7 @@ void ClimateCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 2, this->custom_preset, false);
|
||||
ProtoSize::add_bool_field(total_size, 2, this->has_target_humidity, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 2, this->target_humidity != 0.0f, false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
@ -2991,6 +3027,16 @@ void NumberStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool NumberCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -3008,10 +3054,12 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void NumberCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
@ -3143,6 +3191,16 @@ void SelectStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
@ -3166,10 +3224,12 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void SelectCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SIREN
|
||||
@ -3327,6 +3387,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_volume = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3365,6 +3429,7 @@ void SirenCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(7, this->duration);
|
||||
buffer.encode_bool(8, this->has_volume);
|
||||
buffer.encode_float(9, this->volume);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void SirenCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -3376,6 +3441,7 @@ void SirenCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->duration, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_volume, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
@ -3517,6 +3583,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3546,12 +3616,14 @@ void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::LockCommand>(2, this->command);
|
||||
buffer.encode_bool(3, this->has_code);
|
||||
buffer.encode_string(4, this->code);
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
}
|
||||
void LockCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->command), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_code, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->code, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
@ -3631,6 +3703,16 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool ButtonCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -3641,9 +3723,13 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
|
||||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, this->device_id);
|
||||
}
|
||||
void ButtonCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
@ -3849,6 +3935,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->announcement = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3887,6 +3977,7 @@ void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(7, this->media_url);
|
||||
buffer.encode_bool(8, this->has_announcement);
|
||||
buffer.encode_bool(9, this->announcement);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void MediaPlayerCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
@ -3898,6 +3989,7 @@ void MediaPlayerCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->media_url, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_announcement, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->announcement, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
@ -5311,6 +5403,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI
|
||||
this->command = value.as_enum<enums::AlarmControlPanelStateCommand>();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5339,11 +5435,13 @@ void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::AlarmControlPanelStateCommand>(2, this->command);
|
||||
buffer.encode_string(3, this->code);
|
||||
buffer.encode_uint32(4, this->device_id);
|
||||
}
|
||||
void AlarmControlPanelCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->command), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->code, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
@ -5487,6 +5585,16 @@ void TextStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->missing_state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
@ -5510,10 +5618,12 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void TextCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void TextCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->state, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
@ -5653,6 +5763,10 @@ bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->day = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5672,12 +5786,14 @@ void DateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(2, this->year);
|
||||
buffer.encode_uint32(3, this->month);
|
||||
buffer.encode_uint32(4, this->day);
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
}
|
||||
void DateCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->year, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->month, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->day, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
@ -5817,6 +5933,10 @@ bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->second = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -5836,12 +5956,14 @@ void TimeCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(2, this->hour);
|
||||
buffer.encode_uint32(3, this->minute);
|
||||
buffer.encode_uint32(4, this->second);
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
}
|
||||
void TimeCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->hour, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->minute, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->second, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
@ -6119,6 +6241,10 @@ bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -6142,12 +6268,14 @@ void ValveCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(2, this->has_position);
|
||||
buffer.encode_float(3, this->position);
|
||||
buffer.encode_bool(4, this->stop);
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
}
|
||||
void ValveCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->has_position, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
@ -6261,6 +6389,16 @@ void DateTimeStateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
bool DateTimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -6278,10 +6416,12 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void DateTimeCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_fixed32(2, this->epoch_seconds);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void DateTimeCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
@ -6455,6 +6595,10 @@ bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->command = value.as_enum<enums::UpdateCommand>();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -6472,10 +6616,12 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::UpdateCommand>(2, this->command);
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
}
|
||||
void UpdateCommandRequest::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->command), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -307,6 +307,15 @@ class StateResponseProtoMessage : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
class CommandProtoMessage : public ProtoMessage {
|
||||
public:
|
||||
~CommandProtoMessage() override = default;
|
||||
uint32_t key{0};
|
||||
uint32_t device_id{0};
|
||||
|
||||
protected:
|
||||
};
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 1;
|
||||
@ -640,14 +649,13 @@ class CoverStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverCommandRequest : public ProtoMessage {
|
||||
class CoverCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 30;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 25;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 29;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "cover_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_legacy_command{false};
|
||||
enums::LegacyCoverCommand legacy_command{};
|
||||
bool has_position{false};
|
||||
@ -714,14 +722,13 @@ class FanStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
class FanCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 31;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 38;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 42;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "fan_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_speed{false};
|
||||
@ -803,14 +810,13 @@ class LightStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightCommandRequest : public ProtoMessage {
|
||||
class LightCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 32;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 107;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 112;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "light_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_brightness{false};
|
||||
@ -933,14 +939,13 @@ class SwitchStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchCommandRequest : public ProtoMessage {
|
||||
class SwitchCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 33;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 11;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "switch_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@ -1292,14 +1297,13 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
class CameraImageResponse : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 44;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 16;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 20;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "camera_image_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string data{};
|
||||
bool done{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@ -1401,14 +1405,13 @@ class ClimateStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateCommandRequest : public ProtoMessage {
|
||||
class ClimateCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 48;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 83;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 88;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "climate_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_mode{false};
|
||||
enums::ClimateMode mode{};
|
||||
bool has_target_temperature{false};
|
||||
@ -1487,14 +1490,13 @@ class NumberStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class NumberCommandRequest : public ProtoMessage {
|
||||
class NumberCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 51;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 10;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "number_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
float state{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@ -1504,6 +1506,7 @@ class NumberCommandRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
@ -1546,14 +1549,13 @@ class SelectStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SelectCommandRequest : public ProtoMessage {
|
||||
class SelectCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 54;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 18;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "select_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@ -1564,6 +1566,7 @@ class SelectCommandRequest : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_SIREN
|
||||
@ -1606,14 +1609,13 @@ class SirenStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SirenCommandRequest : public ProtoMessage {
|
||||
class SirenCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 57;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 33;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 37;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "siren_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_tone{false};
|
||||
@ -1675,14 +1677,13 @@ class LockStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockCommandRequest : public ProtoMessage {
|
||||
class LockCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 60;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 18;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 22;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "lock_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::LockCommand command{};
|
||||
bool has_code{false};
|
||||
std::string code{};
|
||||
@ -1718,14 +1719,13 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ButtonCommandRequest : public ProtoMessage {
|
||||
class ButtonCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 62;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 5;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 9;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "button_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@ -1734,6 +1734,7 @@ class ButtonCommandRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
@ -1794,14 +1795,13 @@ class MediaPlayerStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class MediaPlayerCommandRequest : public ProtoMessage {
|
||||
class MediaPlayerCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 65;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 31;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 35;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "media_player_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_command{false};
|
||||
enums::MediaPlayerCommand command{};
|
||||
bool has_volume{false};
|
||||
@ -2669,14 +2669,13 @@ class AlarmControlPanelStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class AlarmControlPanelCommandRequest : public ProtoMessage {
|
||||
class AlarmControlPanelCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 96;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 16;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 20;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "alarm_control_panel_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::AlarmControlPanelStateCommand command{};
|
||||
std::string code{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@ -2734,14 +2733,13 @@ class TextStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextCommandRequest : public ProtoMessage {
|
||||
class TextCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 99;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 18;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "text_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@ -2752,6 +2750,7 @@ class TextCommandRequest : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
@ -2794,14 +2793,13 @@ class DateStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateCommandRequest : public ProtoMessage {
|
||||
class DateCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 102;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 17;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 21;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "date_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
uint32_t year{0};
|
||||
uint32_t month{0};
|
||||
uint32_t day{0};
|
||||
@ -2856,14 +2854,13 @@ class TimeStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TimeCommandRequest : public ProtoMessage {
|
||||
class TimeCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 105;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 17;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 21;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "time_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
uint32_t hour{0};
|
||||
uint32_t minute{0};
|
||||
uint32_t second{0};
|
||||
@ -2961,14 +2958,13 @@ class ValveStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ValveCommandRequest : public ProtoMessage {
|
||||
class ValveCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 111;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 18;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "valve_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool has_position{false};
|
||||
float position{0.0f};
|
||||
bool stop{false};
|
||||
@ -3021,14 +3017,13 @@ class DateTimeStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateTimeCommandRequest : public ProtoMessage {
|
||||
class DateTimeCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 114;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 10;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "date_time_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
uint32_t epoch_seconds{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@ -3038,6 +3033,7 @@ class DateTimeCommandRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
@ -3087,14 +3083,13 @@ class UpdateStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class UpdateCommandRequest : public ProtoMessage {
|
||||
class UpdateCommandRequest : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 118;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 11;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "update_command_request"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::UpdateCommand command{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
|
@ -986,6 +986,11 @@ void CoverCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" stop: ");
|
||||
out.append(YESNO(this->stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1146,6 +1151,11 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" preset_mode: ");
|
||||
out.append("'").append(this->preset_mode).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1419,6 +1429,11 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" effect: ");
|
||||
out.append("'").append(this->effect).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1586,6 +1601,11 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" state: ");
|
||||
out.append(YESNO(this->state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1944,6 +1964,11 @@ void CameraImageResponse::dump_to(std::string &out) const {
|
||||
out.append(" done: ");
|
||||
out.append(YESNO(this->done));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void CameraImageRequest::dump_to(std::string &out) const {
|
||||
@ -2263,6 +2288,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%g", this->target_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2367,6 +2397,11 @@ void NumberCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%g", this->state);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2448,6 +2483,11 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2563,6 +2603,11 @@ void SirenCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%g", this->volume);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2658,6 +2703,11 @@ void LockCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2711,6 +2761,11 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -2857,6 +2912,11 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" announcement: ");
|
||||
out.append(YESNO(this->announcement));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3682,6 +3742,11 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3775,6 +3840,11 @@ void TextCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3872,6 +3942,11 @@ void DateCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->day);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3969,6 +4044,11 @@ void TimeCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->second);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4138,6 +4218,11 @@ void ValveCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" stop: ");
|
||||
out.append(YESNO(this->stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4215,6 +4300,11 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->epoch_seconds);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4323,6 +4413,11 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" command: ");
|
||||
out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -53,6 +53,7 @@ void DebugComponent::on_shutdown() {
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
if (component != nullptr) {
|
||||
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
|
||||
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||
}
|
||||
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
|
||||
pref.save(&buffer);
|
||||
@ -68,6 +69,7 @@ std::string DebugComponent::get_reset_reason_() {
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
char buffer[REBOOT_MAX_LEN]{};
|
||||
if (pref.load(&buffer)) {
|
||||
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||
reset_reason = "Reboot request from " + std::string(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -707,6 +707,7 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
|
||||
|
@ -114,7 +114,6 @@ void ESP32InternalGPIOPin::setup() {
|
||||
if (flags_ & gpio::FLAG_OUTPUT) {
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
ESP_LOGD(TAG, "rtc: %d", SOC_GPIO_SUPPORT_RTC_INDEPENDENT);
|
||||
}
|
||||
|
||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
|
@ -308,7 +308,7 @@ async def to_code(config):
|
||||
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
|
||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add_define("USE_ESP32_CAMERA")
|
||||
cg.add_define("USE_CAMERA")
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.0.15")
|
||||
|
@ -180,6 +180,7 @@ async def to_code(config):
|
||||
cg.add(esp8266_ns.setup_preferences())
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_ESP8266")
|
||||
|
@ -45,3 +45,4 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
|
@ -268,6 +268,7 @@ async def component_to_code(config):
|
||||
|
||||
# disable library compatibility checks
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
# include <Arduino.h> in every file
|
||||
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
|
||||
# dummy version code
|
||||
|
@ -153,11 +153,15 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
|
||||
case MQTT_EVENT_DATA: {
|
||||
static std::string topic;
|
||||
if (!event.topic.empty()) {
|
||||
// When a single message arrives as multiple chunks, the topic will be empty
|
||||
// on any but the first message, leading to event.topic being an empty string.
|
||||
// To ensure handlers get the correct topic, cache the last seen topic to
|
||||
// simulate always receiving the topic from underlying library
|
||||
topic = event.topic;
|
||||
}
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
|
||||
this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
|
||||
event.current_data_offset, event.total_data_len);
|
||||
this->on_message_.call(topic.c_str(), event.data.data(), event.data.size(), event.current_data_offset,
|
||||
event.total_data_len);
|
||||
} break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
|
||||
|
@ -165,6 +165,7 @@ async def to_code(config):
|
||||
# Allow LDF to properly discover dependency including those in preprocessor
|
||||
# conditionals
|
||||
cg.add_platformio_option("lib_ldf_mode", "chain+")
|
||||
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_RP2040")
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
|
@ -1055,6 +1055,7 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False):
|
||||
return validator
|
||||
|
||||
|
||||
bps = float_with_unit("bits per second", "(bps|bits/s|bit/s)?")
|
||||
frequency = float_with_unit("frequency", "(Hz|HZ|hz)?")
|
||||
resistance = float_with_unit("resistance", "(Ω|Ω|ohm|Ohm|OHM)?")
|
||||
current = float_with_unit("current", "(a|A|amp|Amp|amps|Amps|ampere|Ampere)?")
|
||||
|
@ -187,6 +187,12 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
|
||||
# No name to validate
|
||||
return config
|
||||
|
||||
# Skip validation for internal entities
|
||||
# Internal entities are not exposed to Home Assistant and don't use the hash-based
|
||||
# entity state tracking system, so name collisions don't matter for them
|
||||
if config.get(CONF_INTERNAL, False):
|
||||
return config
|
||||
|
||||
# Get the entity name
|
||||
entity_name = config[CONF_NAME]
|
||||
|
||||
|
@ -66,10 +66,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
|
||||
if (delay == SCHEDULER_DONT_RUN) {
|
||||
// Still need to cancel existing timer if name is not empty
|
||||
if (this->is_name_valid_(name_cstr)) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->cancel_item_locked_(component, name_cstr, type);
|
||||
}
|
||||
LockGuard guard{this->lock_};
|
||||
this->cancel_item_locked_(component, name_cstr, type);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -125,10 +123,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
|
||||
LockGuard guard{this->lock_};
|
||||
// If name is provided, do atomic cancel-and-add
|
||||
if (this->is_name_valid_(name_cstr)) {
|
||||
// Cancel existing items
|
||||
this->cancel_item_locked_(component, name_cstr, type);
|
||||
}
|
||||
// Cancel existing items
|
||||
this->cancel_item_locked_(component, name_cstr, type);
|
||||
// Add new item directly to to_add_
|
||||
// since we have the lock held
|
||||
this->to_add_.push_back(std::move(item));
|
||||
@ -442,10 +438,6 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
|
||||
// Get the name as const char*
|
||||
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
|
||||
|
||||
// Handle null or empty names
|
||||
if (!this->is_name_valid_(name_cstr))
|
||||
return false;
|
||||
|
||||
// obtain lock because this function iterates and can be called from non-loop task context
|
||||
LockGuard guard{this->lock_};
|
||||
return this->cancel_item_locked_(component, name_cstr, type);
|
||||
@ -453,6 +445,11 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
|
||||
|
||||
// Helper to cancel items by name - must be called with lock held
|
||||
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
|
||||
// Early return if name is invalid - no items to cancel
|
||||
if (name_cstr == nullptr || name_cstr[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t total_cancelled = 0;
|
||||
|
||||
// Check all containers for matching items
|
||||
|
@ -150,9 +150,6 @@ class Scheduler {
|
||||
return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
|
||||
}
|
||||
|
||||
// Helper to check if a name is valid (not null and not empty)
|
||||
inline bool is_name_valid_(const char *name) { return name != nullptr && name[0] != '\0'; }
|
||||
|
||||
// Common implementation for cancel operations
|
||||
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
|
||||
|
||||
|
@ -61,6 +61,7 @@ src_filter =
|
||||
+<../tests/dummy_main.cpp>
|
||||
+<../.temp/all-include.cpp>
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = strict
|
||||
|
||||
; This are common settings for all Arduino-framework based environments.
|
||||
[common:arduino]
|
||||
|
245
script/determine-jobs.py
Executable file
245
script/determine-jobs.py
Executable file
@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Determine which CI jobs should run based on changed files.
|
||||
|
||||
This script is a centralized way to determine which CI jobs need to run based on
|
||||
what files have changed. It outputs JSON with the following structure:
|
||||
|
||||
{
|
||||
"integration_tests": true/false,
|
||||
"clang_tidy": true/false,
|
||||
"clang_format": true/false,
|
||||
"python_linters": true/false,
|
||||
"changed_components": ["component1", "component2", ...],
|
||||
"component_test_count": 5
|
||||
}
|
||||
|
||||
The CI workflow uses this information to:
|
||||
- Skip or run integration tests
|
||||
- Skip or run clang-tidy (and whether to do a full scan)
|
||||
- Skip or run clang-format
|
||||
- Skip or run Python linters (ruff, flake8, pylint, pyupgrade)
|
||||
- Determine which components to test individually
|
||||
- Decide how to split component tests (if there are many)
|
||||
|
||||
Usage:
|
||||
python script/determine-jobs.py [-b BRANCH]
|
||||
|
||||
Options:
|
||||
-b, --branch BRANCH Branch to compare against (default: dev)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from helpers import (
|
||||
CPP_FILE_EXTENSIONS,
|
||||
ESPHOME_COMPONENTS_PATH,
|
||||
PYTHON_FILE_EXTENSIONS,
|
||||
changed_files,
|
||||
get_all_dependencies,
|
||||
get_components_from_integration_fixtures,
|
||||
parse_list_components_output,
|
||||
root_path,
|
||||
)
|
||||
|
||||
|
||||
def should_run_integration_tests(branch: str | None = None) -> bool:
|
||||
"""Determine if integration tests should run based on changed files.
|
||||
|
||||
This function is used by the CI workflow to intelligently skip integration tests when they're
|
||||
not needed, saving significant CI time and resources.
|
||||
|
||||
Integration tests will run when ANY of the following conditions are met:
|
||||
|
||||
1. Core C++ files changed (esphome/core/*)
|
||||
- Any .cpp, .h, .tcc files in the core directory
|
||||
- These files contain fundamental functionality used throughout ESPHome
|
||||
- Examples: esphome/core/component.cpp, esphome/core/application.h
|
||||
|
||||
2. Core Python files changed (esphome/core/*.py)
|
||||
- Only .py files in the esphome/core/ directory
|
||||
- These are core Python files that affect the entire system
|
||||
- Examples: esphome/core/config.py, esphome/core/__init__.py
|
||||
- NOT included: esphome/*.py, esphome/dashboard/*.py, esphome/components/*/*.py
|
||||
|
||||
3. Integration test files changed
|
||||
- Any file in tests/integration/ directory
|
||||
- This includes test files themselves and fixture YAML files
|
||||
- Examples: tests/integration/test_api.py, tests/integration/fixtures/api.yaml
|
||||
|
||||
4. Components used by integration tests (or their dependencies) changed
|
||||
- The function parses all YAML files in tests/integration/fixtures/
|
||||
- Extracts which components are used in integration tests
|
||||
- Recursively finds all dependencies of those components
|
||||
- If any of these components have changes, tests must run
|
||||
- Example: If api.yaml uses 'sensor' and 'api' components, and 'api' depends on 'socket',
|
||||
then changes to sensor/, api/, or socket/ components trigger tests
|
||||
|
||||
Args:
|
||||
branch: Branch to compare against. If None, uses default.
|
||||
|
||||
Returns:
|
||||
True if integration tests should run, False otherwise.
|
||||
"""
|
||||
files = changed_files(branch)
|
||||
|
||||
# Check if any core files changed (esphome/core/*)
|
||||
for file in files:
|
||||
if file.startswith("esphome/core/"):
|
||||
return True
|
||||
|
||||
# Check if any integration test files changed
|
||||
if any("tests/integration" in file for file in files):
|
||||
return True
|
||||
|
||||
# Get all components used in integration tests and their dependencies
|
||||
fixture_components = get_components_from_integration_fixtures()
|
||||
all_required_components = get_all_dependencies(fixture_components)
|
||||
|
||||
# Check if any required components changed
|
||||
for file in files:
|
||||
if file.startswith(ESPHOME_COMPONENTS_PATH):
|
||||
parts = file.split("/")
|
||||
if len(parts) >= 3:
|
||||
component = parts[2]
|
||||
if component in all_required_components:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def should_run_clang_tidy(branch: str | None = None) -> bool:
|
||||
"""Determine if clang-tidy should run based on changed files.
|
||||
|
||||
This function is used by the CI workflow to intelligently skip clang-tidy checks when they're
|
||||
not needed, saving significant CI time and resources.
|
||||
|
||||
Clang-tidy will run when ANY of the following conditions are met:
|
||||
|
||||
1. Clang-tidy configuration changed
|
||||
- The hash of .clang-tidy configuration file has changed
|
||||
- The hash includes the .clang-tidy file, clang-tidy version from requirements_dev.txt,
|
||||
and relevant platformio.ini sections
|
||||
- When configuration changes, a full scan is needed to ensure all code complies
|
||||
with the new rules
|
||||
- Detected by script/clang_tidy_hash.py --check returning exit code 0
|
||||
|
||||
2. Any C++ source files changed
|
||||
- Any file with C++ extensions: .cpp, .h, .hpp, .cc, .cxx, .c, .tcc
|
||||
- Includes files anywhere in the repository, not just in esphome/
|
||||
- This ensures all C++ code is checked, including tests, examples, etc.
|
||||
- Examples: esphome/core/component.cpp, tests/custom/my_component.h
|
||||
|
||||
If the hash check fails for any reason, clang-tidy runs as a safety measure to ensure
|
||||
code quality is maintained.
|
||||
|
||||
Args:
|
||||
branch: Branch to compare against. If None, uses default.
|
||||
|
||||
Returns:
|
||||
True if clang-tidy should run, False otherwise.
|
||||
"""
|
||||
# First check if clang-tidy configuration changed (full scan needed)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[os.path.join(root_path, "script", "clang_tidy_hash.py"), "--check"],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
# Exit 0 means hash changed (full scan needed)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
except Exception:
|
||||
# If hash check fails, run clang-tidy to be safe
|
||||
return True
|
||||
|
||||
return _any_changed_file_endswith(branch, CPP_FILE_EXTENSIONS)
|
||||
|
||||
|
||||
def should_run_clang_format(branch: str | None = None) -> bool:
|
||||
"""Determine if clang-format should run based on changed files.
|
||||
|
||||
This function is used by the CI workflow to skip clang-format checks when no C++ files
|
||||
have changed, saving CI time and resources.
|
||||
|
||||
Clang-format will run when any C++ source files have changed.
|
||||
|
||||
Args:
|
||||
branch: Branch to compare against. If None, uses default.
|
||||
|
||||
Returns:
|
||||
True if clang-format should run, False otherwise.
|
||||
"""
|
||||
return _any_changed_file_endswith(branch, CPP_FILE_EXTENSIONS)
|
||||
|
||||
|
||||
def should_run_python_linters(branch: str | None = None) -> bool:
|
||||
"""Determine if Python linters (ruff, flake8, pylint, pyupgrade) should run based on changed files.
|
||||
|
||||
This function is used by the CI workflow to skip Python linting checks when no Python files
|
||||
have changed, saving CI time and resources.
|
||||
|
||||
Python linters will run when any Python source files have changed.
|
||||
|
||||
Args:
|
||||
branch: Branch to compare against. If None, uses default.
|
||||
|
||||
Returns:
|
||||
True if Python linters should run, False otherwise.
|
||||
"""
|
||||
return _any_changed_file_endswith(branch, PYTHON_FILE_EXTENSIONS)
|
||||
|
||||
|
||||
def _any_changed_file_endswith(branch: str | None, extensions: tuple[str, ...]) -> bool:
|
||||
"""Check if a changed file ends with any of the specified extensions."""
|
||||
return any(file.endswith(extensions) for file in changed_files(branch))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main function that determines which CI jobs to run."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Determine which CI jobs should run based on changed files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b", "--branch", help="Branch to compare changed files against"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Determine what should run
|
||||
run_integration = should_run_integration_tests(args.branch)
|
||||
run_clang_tidy = should_run_clang_tidy(args.branch)
|
||||
run_clang_format = should_run_clang_format(args.branch)
|
||||
run_python_linters = should_run_python_linters(args.branch)
|
||||
|
||||
# Get changed components using list-components.py for exact compatibility
|
||||
script_path = Path(__file__).parent / "list-components.py"
|
||||
cmd = [sys.executable, str(script_path), "--changed"]
|
||||
if args.branch:
|
||||
cmd.extend(["-b", args.branch])
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
changed_components = parse_list_components_output(result.stdout)
|
||||
|
||||
# Build output
|
||||
output: dict[str, Any] = {
|
||||
"integration_tests": run_integration,
|
||||
"clang_tidy": run_clang_tidy,
|
||||
"clang_format": run_clang_format,
|
||||
"python_linters": run_python_linters,
|
||||
"changed_components": changed_components,
|
||||
"component_test_count": len(changed_components),
|
||||
}
|
||||
|
||||
# Output as JSON
|
||||
print(json.dumps(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cache
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
@ -7,6 +8,7 @@ from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import colorama
|
||||
|
||||
@ -15,6 +17,34 @@ basepath = os.path.join(root_path, "esphome")
|
||||
temp_folder = os.path.join(root_path, ".temp")
|
||||
temp_header_file = os.path.join(temp_folder, "all-include.cpp")
|
||||
|
||||
# C++ file extensions used for clang-tidy and clang-format checks
|
||||
CPP_FILE_EXTENSIONS = (".cpp", ".h", ".hpp", ".cc", ".cxx", ".c", ".tcc")
|
||||
|
||||
# Python file extensions
|
||||
PYTHON_FILE_EXTENSIONS = (".py", ".pyi")
|
||||
|
||||
# YAML file extensions
|
||||
YAML_FILE_EXTENSIONS = (".yaml", ".yml")
|
||||
|
||||
# Component path prefix
|
||||
ESPHOME_COMPONENTS_PATH = "esphome/components/"
|
||||
|
||||
|
||||
def parse_list_components_output(output: str) -> list[str]:
|
||||
"""Parse the output from list-components.py script.
|
||||
|
||||
The script outputs one component name per line.
|
||||
|
||||
Args:
|
||||
output: The stdout from list-components.py
|
||||
|
||||
Returns:
|
||||
List of component names, or empty list if no output
|
||||
"""
|
||||
if not output or not output.strip():
|
||||
return []
|
||||
return [c.strip() for c in output.strip().split("\n") if c.strip()]
|
||||
|
||||
|
||||
def styled(color: str | tuple[str, ...], msg: str, reset: bool = True) -> str:
|
||||
prefix = "".join(color) if isinstance(color, tuple) else color
|
||||
@ -96,6 +126,7 @@ def _get_pr_number_from_github_env() -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
@cache
|
||||
def _get_changed_files_github_actions() -> list[str] | None:
|
||||
"""Get changed files in GitHub Actions environment.
|
||||
|
||||
@ -135,7 +166,7 @@ def changed_files(branch: str | None = None) -> list[str]:
|
||||
return github_files
|
||||
|
||||
# Original implementation for local development
|
||||
if branch is None:
|
||||
if not branch: # Treat None and empty string the same
|
||||
branch = "dev"
|
||||
check_remotes = ["upstream", "origin"]
|
||||
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
|
||||
@ -183,7 +214,7 @@ def get_changed_components() -> list[str] | None:
|
||||
changed = changed_files()
|
||||
core_cpp_changed = any(
|
||||
f.startswith("esphome/core/")
|
||||
and f.endswith((".cpp", ".h", ".hpp", ".cc", ".cxx", ".c"))
|
||||
and f.endswith(CPP_FILE_EXTENSIONS[:-1]) # Exclude .tcc for core files
|
||||
for f in changed
|
||||
)
|
||||
if core_cpp_changed:
|
||||
@ -198,8 +229,7 @@ def get_changed_components() -> list[str] | None:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, check=True, close_fds=False
|
||||
)
|
||||
components = [c.strip() for c in result.stdout.strip().split("\n") if c.strip()]
|
||||
return components
|
||||
return parse_list_components_output(result.stdout)
|
||||
except subprocess.CalledProcessError:
|
||||
# If the script fails, fall back to full scan
|
||||
print("Could not determine changed components - will run full clang-tidy scan")
|
||||
@ -249,7 +279,9 @@ def _filter_changed_ci(files: list[str]) -> list[str]:
|
||||
# Action: Check only the specific non-component files that changed
|
||||
changed = changed_files()
|
||||
files = [
|
||||
f for f in files if f in changed and not f.startswith("esphome/components/")
|
||||
f
|
||||
for f in files
|
||||
if f in changed and not f.startswith(ESPHOME_COMPONENTS_PATH)
|
||||
]
|
||||
if not files:
|
||||
print("No files changed")
|
||||
@ -267,7 +299,7 @@ def _filter_changed_ci(files: list[str]) -> list[str]:
|
||||
# because changes in one file can affect other files in the same component.
|
||||
filtered_files = []
|
||||
for f in files:
|
||||
if f.startswith("esphome/components/"):
|
||||
if f.startswith(ESPHOME_COMPONENTS_PATH):
|
||||
# Check if file belongs to any of the changed components
|
||||
parts = f.split("/")
|
||||
if len(parts) >= 3 and parts[2] in component_set:
|
||||
@ -326,7 +358,7 @@ def git_ls_files(patterns: list[str] | None = None) -> dict[str, int]:
|
||||
return {s[3].strip(): int(s[0]) for s in lines}
|
||||
|
||||
|
||||
def load_idedata(environment):
|
||||
def load_idedata(environment: str) -> dict[str, Any]:
|
||||
start_time = time.time()
|
||||
print(f"Loading IDE data for environment '{environment}'...")
|
||||
|
||||
@ -442,3 +474,83 @@ def get_usable_cpu_count() -> int:
|
||||
return (
|
||||
os.process_cpu_count() if hasattr(os, "process_cpu_count") else os.cpu_count()
|
||||
)
|
||||
|
||||
|
||||
def get_all_dependencies(component_names: set[str]) -> set[str]:
|
||||
"""Get all dependencies for a set of components.
|
||||
|
||||
Args:
|
||||
component_names: Set of component names to get dependencies for
|
||||
|
||||
Returns:
|
||||
Set of all components including dependencies and auto-loaded components
|
||||
"""
|
||||
from esphome.const import KEY_CORE
|
||||
from esphome.core import CORE
|
||||
from esphome.loader import get_component
|
||||
|
||||
all_components: set[str] = set(component_names)
|
||||
|
||||
# Reset CORE to ensure clean state
|
||||
CORE.reset()
|
||||
|
||||
# Set up fake config path for component loading
|
||||
root = Path(__file__).parent.parent
|
||||
CORE.config_path = str(root)
|
||||
CORE.data[KEY_CORE] = {}
|
||||
|
||||
# Keep finding dependencies until no new ones are found
|
||||
while True:
|
||||
new_components: set[str] = set()
|
||||
|
||||
for comp_name in all_components:
|
||||
comp = get_component(comp_name)
|
||||
if not comp:
|
||||
continue
|
||||
|
||||
# Add dependencies (extract component name before '.')
|
||||
new_components.update(dep.split(".")[0] for dep in comp.dependencies)
|
||||
|
||||
# Add auto_load components
|
||||
new_components.update(comp.auto_load)
|
||||
|
||||
# Check if we found any new components
|
||||
new_components -= all_components
|
||||
if not new_components:
|
||||
break
|
||||
|
||||
all_components.update(new_components)
|
||||
|
||||
return all_components
|
||||
|
||||
|
||||
def get_components_from_integration_fixtures() -> set[str]:
|
||||
"""Extract all components used in integration test fixtures.
|
||||
|
||||
Returns:
|
||||
Set of component names used in integration test fixtures
|
||||
"""
|
||||
import yaml
|
||||
|
||||
components: set[str] = set()
|
||||
fixtures_dir = Path(__file__).parent.parent / "tests" / "integration" / "fixtures"
|
||||
|
||||
for yaml_file in fixtures_dir.glob("*.yaml"):
|
||||
with open(yaml_file) as f:
|
||||
config: dict[str, any] | None = yaml.safe_load(f)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
# Add all top-level component keys
|
||||
components.update(config.keys())
|
||||
|
||||
# Add platform components (e.g., output.template)
|
||||
for value in config.values():
|
||||
if not isinstance(value, list):
|
||||
continue
|
||||
|
||||
for item in value:
|
||||
if isinstance(item, dict) and "platform" in item:
|
||||
components.add(item["platform"])
|
||||
|
||||
return components
|
||||
|
@ -20,6 +20,12 @@ def filter_component_files(str):
|
||||
return str.startswith("esphome/components/") | str.startswith("tests/components/")
|
||||
|
||||
|
||||
def get_all_component_files() -> list[str]:
|
||||
"""Get all component files from git."""
|
||||
files = git_ls_files()
|
||||
return list(filter(filter_component_files, files))
|
||||
|
||||
|
||||
def extract_component_names_array_from_files_array(files):
|
||||
components = []
|
||||
for file in files:
|
||||
@ -165,17 +171,20 @@ def main():
|
||||
if args.branch and not args.changed:
|
||||
parser.error("--branch requires --changed")
|
||||
|
||||
files = git_ls_files()
|
||||
files = filter(filter_component_files, files)
|
||||
|
||||
if args.changed:
|
||||
if args.branch:
|
||||
changed = changed_files(args.branch)
|
||||
else:
|
||||
changed = changed_files()
|
||||
# When --changed is passed, only get the changed files
|
||||
changed = changed_files(args.branch)
|
||||
|
||||
# If any base test file(s) changed, there's no need to filter out components
|
||||
if not any("tests/test_build_components" in file for file in changed):
|
||||
files = [f for f in files if f in changed]
|
||||
if any("tests/test_build_components" in file for file in changed):
|
||||
# Need to get all component files
|
||||
files = get_all_component_files()
|
||||
else:
|
||||
# Only look at changed component files
|
||||
files = [f for f in changed if filter_component_files(f)]
|
||||
else:
|
||||
# Get all component files
|
||||
files = get_all_component_files()
|
||||
|
||||
for c in get_components(files, args.changed):
|
||||
print(c)
|
||||
|
43
tests/integration/fixtures/scheduler_null_name.yaml
Normal file
43
tests/integration/fixtures/scheduler_null_name.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
esphome:
|
||||
name: scheduler-null-name
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
api:
|
||||
services:
|
||||
- service: test_null_name
|
||||
then:
|
||||
- lambda: |-
|
||||
// First, create a scenario that would trigger the crash
|
||||
// The crash happens when defer() is called with a name that would be cancelled
|
||||
|
||||
// Test 1: Create a defer with a valid name
|
||||
App.scheduler.set_timeout(nullptr, "test_defer", 0, []() {
|
||||
ESP_LOGI("TEST", "First defer should be cancelled");
|
||||
});
|
||||
|
||||
// Test 2: Create another defer with the same name - this triggers cancel_item_locked_
|
||||
// In the unfixed code, this would crash if the name was NULL
|
||||
App.scheduler.set_timeout(nullptr, "test_defer", 0, []() {
|
||||
ESP_LOGI("TEST", "Second defer executed");
|
||||
});
|
||||
|
||||
// Test 3: Now test with nullptr - this is the actual crash scenario
|
||||
// Create a defer item without a name (like voice assistant does)
|
||||
const char* null_name = nullptr;
|
||||
App.scheduler.set_timeout(nullptr, null_name, 0, []() {
|
||||
ESP_LOGI("TEST", "Defer with null name executed");
|
||||
});
|
||||
|
||||
// Test 4: Create another defer with null name - this would trigger the crash
|
||||
App.scheduler.set_timeout(nullptr, null_name, 0, []() {
|
||||
ESP_LOGI("TEST", "Second null defer executed");
|
||||
});
|
||||
|
||||
// Test 5: Verify scheduler still works
|
||||
App.scheduler.set_timeout(nullptr, "valid_timeout", 50, []() {
|
||||
ESP_LOGI("TEST", "Test completed successfully");
|
||||
});
|
59
tests/integration/test_scheduler_null_name.py
Normal file
59
tests/integration/test_scheduler_null_name.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""Test that scheduler handles NULL names safely without crashing."""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scheduler_null_name(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that scheduler handles NULL names safely without crashing."""
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
test_complete_future: asyncio.Future[bool] = loop.create_future()
|
||||
|
||||
# Pattern to match test completion
|
||||
test_complete_pattern = re.compile(r"Test completed successfully")
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
"""Check log output for test completion."""
|
||||
if not test_complete_future.done() and test_complete_pattern.search(line):
|
||||
test_complete_future.set_result(True)
|
||||
|
||||
async with run_compiled(yaml_config, line_callback=check_output):
|
||||
async with api_client_connected() as client:
|
||||
# Verify we can connect
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "scheduler-null-name"
|
||||
|
||||
# List services
|
||||
_, services = await asyncio.wait_for(
|
||||
client.list_entities_services(), timeout=5.0
|
||||
)
|
||||
|
||||
# Find our test service
|
||||
test_null_name_service = next(
|
||||
(s for s in services if s.name == "test_null_name"), None
|
||||
)
|
||||
assert test_null_name_service is not None, (
|
||||
"test_null_name service not found"
|
||||
)
|
||||
|
||||
# Execute the test
|
||||
client.execute_service(test_null_name_service, {})
|
||||
|
||||
# Wait for test completion
|
||||
try:
|
||||
await asyncio.wait_for(test_complete_future, timeout=10.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail(
|
||||
"Test did not complete within timeout - likely crashed due to NULL name"
|
||||
)
|
352
tests/script/test_determine_jobs.py
Normal file
352
tests/script/test_determine_jobs.py
Normal file
@ -0,0 +1,352 @@
|
||||
"""Unit tests for script/determine-jobs.py module."""
|
||||
|
||||
from collections.abc import Generator
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Add the script directory to Python path so we can import the module
|
||||
script_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "script")
|
||||
)
|
||||
sys.path.insert(0, script_dir)
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"determine_jobs", os.path.join(script_dir, "determine-jobs.py")
|
||||
)
|
||||
determine_jobs = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(determine_jobs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_should_run_integration_tests() -> Generator[Mock, None, None]:
|
||||
"""Mock should_run_integration_tests from helpers."""
|
||||
with patch.object(determine_jobs, "should_run_integration_tests") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_should_run_clang_tidy() -> Generator[Mock, None, None]:
|
||||
"""Mock should_run_clang_tidy from helpers."""
|
||||
with patch.object(determine_jobs, "should_run_clang_tidy") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_should_run_clang_format() -> Generator[Mock, None, None]:
|
||||
"""Mock should_run_clang_format from helpers."""
|
||||
with patch.object(determine_jobs, "should_run_clang_format") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_should_run_python_linters() -> Generator[Mock, None, None]:
|
||||
"""Mock should_run_python_linters from helpers."""
|
||||
with patch.object(determine_jobs, "should_run_python_linters") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subprocess_run() -> Generator[Mock, None, None]:
|
||||
"""Mock subprocess.run for list-components.py calls."""
|
||||
with patch.object(determine_jobs.subprocess, "run") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
def test_main_all_tests_should_run(
|
||||
mock_should_run_integration_tests: Mock,
|
||||
mock_should_run_clang_tidy: Mock,
|
||||
mock_should_run_clang_format: Mock,
|
||||
mock_should_run_python_linters: Mock,
|
||||
mock_subprocess_run: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Test when all tests should run."""
|
||||
mock_should_run_integration_tests.return_value = True
|
||||
mock_should_run_clang_tidy.return_value = True
|
||||
mock_should_run_clang_format.return_value = True
|
||||
mock_should_run_python_linters.return_value = True
|
||||
|
||||
# Mock list-components.py output
|
||||
mock_result = Mock()
|
||||
mock_result.stdout = "wifi\napi\nsensor\n"
|
||||
mock_subprocess_run.return_value = mock_result
|
||||
|
||||
# Run main function with mocked argv
|
||||
with patch("sys.argv", ["determine-jobs.py"]):
|
||||
determine_jobs.main()
|
||||
|
||||
# Check output
|
||||
captured = capsys.readouterr()
|
||||
output = json.loads(captured.out)
|
||||
|
||||
assert output["integration_tests"] is True
|
||||
assert output["clang_tidy"] is True
|
||||
assert output["clang_format"] is True
|
||||
assert output["python_linters"] is True
|
||||
assert output["changed_components"] == ["wifi", "api", "sensor"]
|
||||
assert output["component_test_count"] == 3
|
||||
|
||||
|
||||
def test_main_no_tests_should_run(
|
||||
mock_should_run_integration_tests: Mock,
|
||||
mock_should_run_clang_tidy: Mock,
|
||||
mock_should_run_clang_format: Mock,
|
||||
mock_should_run_python_linters: Mock,
|
||||
mock_subprocess_run: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Test when no tests should run."""
|
||||
mock_should_run_integration_tests.return_value = False
|
||||
mock_should_run_clang_tidy.return_value = False
|
||||
mock_should_run_clang_format.return_value = False
|
||||
mock_should_run_python_linters.return_value = False
|
||||
|
||||
# Mock empty list-components.py output
|
||||
mock_result = Mock()
|
||||
mock_result.stdout = ""
|
||||
mock_subprocess_run.return_value = mock_result
|
||||
|
||||
# Run main function with mocked argv
|
||||
with patch("sys.argv", ["determine-jobs.py"]):
|
||||
determine_jobs.main()
|
||||
|
||||
# Check output
|
||||
captured = capsys.readouterr()
|
||||
output = json.loads(captured.out)
|
||||
|
||||
assert output["integration_tests"] is False
|
||||
assert output["clang_tidy"] is False
|
||||
assert output["clang_format"] is False
|
||||
assert output["python_linters"] is False
|
||||
assert output["changed_components"] == []
|
||||
assert output["component_test_count"] == 0
|
||||
|
||||
|
||||
def test_main_list_components_fails(
|
||||
mock_should_run_integration_tests: Mock,
|
||||
mock_should_run_clang_tidy: Mock,
|
||||
mock_should_run_clang_format: Mock,
|
||||
mock_should_run_python_linters: Mock,
|
||||
mock_subprocess_run: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Test when list-components.py fails."""
|
||||
mock_should_run_integration_tests.return_value = True
|
||||
mock_should_run_clang_tidy.return_value = True
|
||||
mock_should_run_clang_format.return_value = True
|
||||
mock_should_run_python_linters.return_value = True
|
||||
|
||||
# Mock list-components.py failure
|
||||
mock_subprocess_run.side_effect = subprocess.CalledProcessError(1, "cmd")
|
||||
|
||||
# Run main function with mocked argv - should raise
|
||||
with patch("sys.argv", ["determine-jobs.py"]):
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
determine_jobs.main()
|
||||
|
||||
|
||||
def test_main_with_branch_argument(
|
||||
mock_should_run_integration_tests: Mock,
|
||||
mock_should_run_clang_tidy: Mock,
|
||||
mock_should_run_clang_format: Mock,
|
||||
mock_should_run_python_linters: Mock,
|
||||
mock_subprocess_run: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Test with branch argument."""
|
||||
mock_should_run_integration_tests.return_value = False
|
||||
mock_should_run_clang_tidy.return_value = True
|
||||
mock_should_run_clang_format.return_value = False
|
||||
mock_should_run_python_linters.return_value = True
|
||||
|
||||
# Mock list-components.py output
|
||||
mock_result = Mock()
|
||||
mock_result.stdout = "mqtt\n"
|
||||
mock_subprocess_run.return_value = mock_result
|
||||
|
||||
with patch("sys.argv", ["script.py", "-b", "main"]):
|
||||
determine_jobs.main()
|
||||
|
||||
# Check that functions were called with branch
|
||||
mock_should_run_integration_tests.assert_called_once_with("main")
|
||||
mock_should_run_clang_tidy.assert_called_once_with("main")
|
||||
mock_should_run_clang_format.assert_called_once_with("main")
|
||||
mock_should_run_python_linters.assert_called_once_with("main")
|
||||
|
||||
# Check that list-components.py was called with branch
|
||||
mock_subprocess_run.assert_called_once()
|
||||
call_args = mock_subprocess_run.call_args[0][0]
|
||||
assert "--changed" in call_args
|
||||
assert "-b" in call_args
|
||||
assert "main" in call_args
|
||||
|
||||
# Check output
|
||||
captured = capsys.readouterr()
|
||||
output = json.loads(captured.out)
|
||||
|
||||
assert output["integration_tests"] is False
|
||||
assert output["clang_tidy"] is True
|
||||
assert output["clang_format"] is False
|
||||
assert output["python_linters"] is True
|
||||
assert output["changed_components"] == ["mqtt"]
|
||||
assert output["component_test_count"] == 1
|
||||
|
||||
|
||||
def test_should_run_integration_tests(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Test should_run_integration_tests function."""
|
||||
# Core C++ files trigger tests
|
||||
with patch.object(
|
||||
determine_jobs, "changed_files", return_value=["esphome/core/component.cpp"]
|
||||
):
|
||||
result = determine_jobs.should_run_integration_tests()
|
||||
assert result is True
|
||||
|
||||
# Core Python files trigger tests
|
||||
with patch.object(
|
||||
determine_jobs, "changed_files", return_value=["esphome/core/config.py"]
|
||||
):
|
||||
result = determine_jobs.should_run_integration_tests()
|
||||
assert result is True
|
||||
|
||||
# Python files directly in esphome/ do NOT trigger tests
|
||||
with patch.object(
|
||||
determine_jobs, "changed_files", return_value=["esphome/config.py"]
|
||||
):
|
||||
result = determine_jobs.should_run_integration_tests()
|
||||
assert result is False
|
||||
|
||||
# Python files in subdirectories (not core) do NOT trigger tests
|
||||
with patch.object(
|
||||
determine_jobs,
|
||||
"changed_files",
|
||||
return_value=["esphome/dashboard/web_server.py"],
|
||||
):
|
||||
result = determine_jobs.should_run_integration_tests()
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_should_run_integration_tests_with_branch() -> None:
|
||||
"""Test should_run_integration_tests with branch argument."""
|
||||
with patch.object(determine_jobs, "changed_files") as mock_changed:
|
||||
mock_changed.return_value = []
|
||||
determine_jobs.should_run_integration_tests("release")
|
||||
mock_changed.assert_called_once_with("release")
|
||||
|
||||
|
||||
def test_should_run_integration_tests_component_dependency() -> None:
|
||||
"""Test that integration tests run when components used in fixtures change."""
|
||||
with patch.object(
|
||||
determine_jobs, "changed_files", return_value=["esphome/components/api/api.cpp"]
|
||||
):
|
||||
with patch.object(
|
||||
determine_jobs, "get_components_from_integration_fixtures"
|
||||
) as mock_fixtures:
|
||||
mock_fixtures.return_value = {"api", "sensor"}
|
||||
with patch.object(determine_jobs, "get_all_dependencies") as mock_deps:
|
||||
mock_deps.return_value = {"api", "sensor", "network"}
|
||||
result = determine_jobs.should_run_integration_tests()
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("check_returncode", "changed_files", "expected_result"),
|
||||
[
|
||||
(0, [], True), # Hash changed - need full scan
|
||||
(1, ["esphome/core.cpp"], True), # C++ file changed
|
||||
(1, ["README.md"], False), # No C++ files changed
|
||||
],
|
||||
)
|
||||
def test_should_run_clang_tidy(
|
||||
check_returncode: int,
|
||||
changed_files: list[str],
|
||||
expected_result: bool,
|
||||
) -> None:
|
||||
"""Test should_run_clang_tidy function."""
|
||||
with patch.object(determine_jobs, "changed_files", return_value=changed_files):
|
||||
# Test with hash check returning specific code
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = Mock(returncode=check_returncode)
|
||||
result = determine_jobs.should_run_clang_tidy()
|
||||
assert result == expected_result
|
||||
|
||||
# Test with hash check failing (exception)
|
||||
if check_returncode != 0:
|
||||
with patch("subprocess.run", side_effect=Exception("Failed")):
|
||||
result = determine_jobs.should_run_clang_tidy()
|
||||
assert result is True # Fail safe - run clang-tidy
|
||||
|
||||
|
||||
def test_should_run_clang_tidy_with_branch() -> None:
|
||||
"""Test should_run_clang_tidy with branch argument."""
|
||||
with patch.object(determine_jobs, "changed_files") as mock_changed:
|
||||
mock_changed.return_value = []
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = Mock(returncode=1) # Hash unchanged
|
||||
determine_jobs.should_run_clang_tidy("release")
|
||||
mock_changed.assert_called_once_with("release")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("changed_files", "expected_result"),
|
||||
[
|
||||
(["esphome/core.py"], True),
|
||||
(["script/test.py"], True),
|
||||
(["esphome/test.pyi"], True), # .pyi files should trigger
|
||||
(["README.md"], False),
|
||||
([], False),
|
||||
],
|
||||
)
|
||||
def test_should_run_python_linters(
|
||||
changed_files: list[str], expected_result: bool
|
||||
) -> None:
|
||||
"""Test should_run_python_linters function."""
|
||||
with patch.object(determine_jobs, "changed_files", return_value=changed_files):
|
||||
result = determine_jobs.should_run_python_linters()
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
def test_should_run_python_linters_with_branch() -> None:
|
||||
"""Test should_run_python_linters with branch argument."""
|
||||
with patch.object(determine_jobs, "changed_files") as mock_changed:
|
||||
mock_changed.return_value = []
|
||||
determine_jobs.should_run_python_linters("release")
|
||||
mock_changed.assert_called_once_with("release")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("changed_files", "expected_result"),
|
||||
[
|
||||
(["esphome/core.cpp"], True),
|
||||
(["esphome/core.h"], True),
|
||||
(["test.hpp"], True),
|
||||
(["test.cc"], True),
|
||||
(["test.cxx"], True),
|
||||
(["test.c"], True),
|
||||
(["test.tcc"], True),
|
||||
(["README.md"], False),
|
||||
([], False),
|
||||
],
|
||||
)
|
||||
def test_should_run_clang_format(
|
||||
changed_files: list[str], expected_result: bool
|
||||
) -> None:
|
||||
"""Test should_run_clang_format function."""
|
||||
with patch.object(determine_jobs, "changed_files", return_value=changed_files):
|
||||
result = determine_jobs.should_run_clang_format()
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
def test_should_run_clang_format_with_branch() -> None:
|
||||
"""Test should_run_clang_format with branch argument."""
|
||||
with patch.object(determine_jobs, "changed_files") as mock_changed:
|
||||
mock_changed.return_value = []
|
||||
determine_jobs.should_run_clang_format("release")
|
||||
mock_changed.assert_called_once_with("release")
|
@ -27,6 +27,7 @@ _filter_changed_ci = helpers._filter_changed_ci
|
||||
_filter_changed_local = helpers._filter_changed_local
|
||||
build_all_include = helpers.build_all_include
|
||||
print_file_list = helpers.print_file_list
|
||||
get_all_dependencies = helpers.get_all_dependencies
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -154,6 +155,14 @@ def test_github_actions_push_event(monkeypatch: MonkeyPatch) -> None:
|
||||
assert result == expected_files
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_caches():
|
||||
"""Clear function caches before each test."""
|
||||
# Clear the cache for _get_changed_files_github_actions
|
||||
_get_changed_files_github_actions.cache_clear()
|
||||
yield
|
||||
|
||||
|
||||
def test_get_changed_files_github_actions_pull_request(
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
@ -847,3 +856,159 @@ def test_print_file_list_default_title(capsys: pytest.CaptureFixture[str]) -> No
|
||||
|
||||
assert "Files:" in captured.out
|
||||
assert " test.cpp" in captured.out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("component_configs", "initial_components", "expected_components"),
|
||||
[
|
||||
# No dependencies
|
||||
(
|
||||
{"sensor": ([], [])}, # (dependencies, auto_load)
|
||||
{"sensor"},
|
||||
{"sensor"},
|
||||
),
|
||||
# Simple dependencies
|
||||
(
|
||||
{
|
||||
"sensor": (["esp32"], []),
|
||||
"esp32": ([], []),
|
||||
},
|
||||
{"sensor"},
|
||||
{"sensor", "esp32"},
|
||||
),
|
||||
# Auto-load components
|
||||
(
|
||||
{
|
||||
"light": ([], ["output", "power_supply"]),
|
||||
"output": ([], []),
|
||||
"power_supply": ([], []),
|
||||
},
|
||||
{"light"},
|
||||
{"light", "output", "power_supply"},
|
||||
),
|
||||
# Transitive dependencies
|
||||
(
|
||||
{
|
||||
"comp_a": (["comp_b"], []),
|
||||
"comp_b": (["comp_c"], []),
|
||||
"comp_c": ([], []),
|
||||
},
|
||||
{"comp_a"},
|
||||
{"comp_a", "comp_b", "comp_c"},
|
||||
),
|
||||
# Dependencies with dots (sensor.base)
|
||||
(
|
||||
{
|
||||
"my_comp": (["sensor.base", "binary_sensor.base"], []),
|
||||
"sensor": ([], []),
|
||||
"binary_sensor": ([], []),
|
||||
},
|
||||
{"my_comp"},
|
||||
{"my_comp", "sensor", "binary_sensor"},
|
||||
),
|
||||
# Circular dependencies (should not cause infinite loop)
|
||||
(
|
||||
{
|
||||
"comp_a": (["comp_b"], []),
|
||||
"comp_b": (["comp_a"], []),
|
||||
},
|
||||
{"comp_a"},
|
||||
{"comp_a", "comp_b"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_all_dependencies(
|
||||
component_configs: dict[str, tuple[list[str], list[str]]],
|
||||
initial_components: set[str],
|
||||
expected_components: set[str],
|
||||
) -> None:
|
||||
"""Test dependency resolution for components."""
|
||||
with patch("esphome.loader.get_component") as mock_get_component:
|
||||
|
||||
def get_component_side_effect(name: str):
|
||||
if name in component_configs:
|
||||
deps, auto_load = component_configs[name]
|
||||
comp = Mock()
|
||||
comp.dependencies = deps
|
||||
comp.auto_load = auto_load
|
||||
return comp
|
||||
return None
|
||||
|
||||
mock_get_component.side_effect = get_component_side_effect
|
||||
|
||||
result = helpers.get_all_dependencies(initial_components)
|
||||
|
||||
assert result == expected_components
|
||||
|
||||
|
||||
def test_get_all_dependencies_handles_missing_components() -> None:
|
||||
"""Test handling of components that can't be loaded."""
|
||||
with patch("esphome.loader.get_component") as mock_get_component:
|
||||
# First component exists, its dependency doesn't
|
||||
comp = Mock()
|
||||
comp.dependencies = ["missing_comp"]
|
||||
comp.auto_load = []
|
||||
|
||||
mock_get_component.side_effect = (
|
||||
lambda name: comp if name == "existing" else None
|
||||
)
|
||||
|
||||
result = helpers.get_all_dependencies({"existing", "nonexistent"})
|
||||
|
||||
# Should still include all components, even if some can't be loaded
|
||||
assert result == {"existing", "nonexistent", "missing_comp"}
|
||||
|
||||
|
||||
def test_get_all_dependencies_empty_set() -> None:
|
||||
"""Test with empty initial component set."""
|
||||
result = helpers.get_all_dependencies(set())
|
||||
assert result == set()
|
||||
|
||||
|
||||
def test_get_components_from_integration_fixtures() -> None:
|
||||
"""Test extraction of components from fixture YAML files."""
|
||||
yaml_content = {
|
||||
"sensor": [{"platform": "template", "name": "test"}],
|
||||
"binary_sensor": [{"platform": "gpio", "pin": 5}],
|
||||
"esphome": {"name": "test"},
|
||||
"api": {},
|
||||
}
|
||||
expected_components = {
|
||||
"sensor",
|
||||
"binary_sensor",
|
||||
"esphome",
|
||||
"api",
|
||||
"template",
|
||||
"gpio",
|
||||
}
|
||||
|
||||
mock_yaml_file = Mock()
|
||||
|
||||
with (
|
||||
patch("pathlib.Path.glob") as mock_glob,
|
||||
patch("builtins.open", create=True),
|
||||
patch("yaml.safe_load", return_value=yaml_content),
|
||||
):
|
||||
mock_glob.return_value = [mock_yaml_file]
|
||||
|
||||
components = helpers.get_components_from_integration_fixtures()
|
||||
|
||||
assert components == expected_components
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"output,expected",
|
||||
[
|
||||
("wifi\napi\nsensor\n", ["wifi", "api", "sensor"]),
|
||||
("wifi\n", ["wifi"]),
|
||||
("", []),
|
||||
(" \n \n", []),
|
||||
("\n\n", []),
|
||||
(" wifi \n api \n", ["wifi", "api"]),
|
||||
("wifi\n\napi\n\nsensor", ["wifi", "api", "sensor"]),
|
||||
],
|
||||
)
|
||||
def test_parse_list_components_output(output: str, expected: list[str]) -> None:
|
||||
"""Test parse_list_components_output function."""
|
||||
result = helpers.parse_list_components_output(output)
|
||||
assert result == expected
|
||||
|
@ -8,9 +8,19 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_NAME
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ICON,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
)
|
||||
from esphome.core import CORE, ID, entity_helpers
|
||||
from esphome.core.entity_helpers import get_base_entity_object_id, setup_entity
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
get_base_entity_object_id,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.helpers import sanitize, snake_case
|
||||
|
||||
@ -493,11 +503,6 @@ async def test_setup_entity_disabled_by_default(
|
||||
|
||||
def test_entity_duplicate_validator() -> None:
|
||||
"""Test the entity_duplicate_validator function."""
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator
|
||||
|
||||
# Reset CORE unique_ids for clean test
|
||||
CORE.unique_ids.clear()
|
||||
|
||||
# Create validator for sensor platform
|
||||
validator = entity_duplicate_validator("sensor")
|
||||
|
||||
@ -523,11 +528,6 @@ def test_entity_duplicate_validator() -> None:
|
||||
|
||||
def test_entity_duplicate_validator_with_devices() -> None:
|
||||
"""Test entity_duplicate_validator with devices."""
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator
|
||||
|
||||
# Reset CORE unique_ids for clean test
|
||||
CORE.unique_ids.clear()
|
||||
|
||||
# Create validator for sensor platform
|
||||
validator = entity_duplicate_validator("sensor")
|
||||
|
||||
@ -605,3 +605,36 @@ def test_entity_different_platforms_yaml_validation(
|
||||
)
|
||||
# This should succeed
|
||||
assert result is not None
|
||||
|
||||
|
||||
def test_entity_duplicate_validator_internal_entities() -> None:
|
||||
"""Test that internal entities are excluded from duplicate name validation."""
|
||||
# Create validator for sensor platform
|
||||
validator = entity_duplicate_validator("sensor")
|
||||
|
||||
# First entity should pass
|
||||
config1 = {CONF_NAME: "Temperature"}
|
||||
validated1 = validator(config1)
|
||||
assert validated1 == config1
|
||||
assert ("sensor", "temperature") in CORE.unique_ids
|
||||
|
||||
# Internal entity with same name should pass (not added to unique_ids)
|
||||
config2 = {CONF_NAME: "Temperature", CONF_INTERNAL: True}
|
||||
validated2 = validator(config2)
|
||||
assert validated2 == config2
|
||||
# Internal entity should not be added to unique_ids
|
||||
assert len([k for k in CORE.unique_ids if k == ("sensor", "temperature")]) == 1
|
||||
|
||||
# Another internal entity with same name should also pass
|
||||
config3 = {CONF_NAME: "Temperature", CONF_INTERNAL: True}
|
||||
validated3 = validator(config3)
|
||||
assert validated3 == config3
|
||||
# Still only one entry in unique_ids (from the non-internal entity)
|
||||
assert len([k for k in CORE.unique_ids if k == ("sensor", "temperature")]) == 1
|
||||
|
||||
# Non-internal entity with same name should fail
|
||||
config4 = {CONF_NAME: "Temperature"}
|
||||
with pytest.raises(
|
||||
Invalid, match=r"Duplicate sensor entity with name 'Temperature' found"
|
||||
):
|
||||
validator(config4)
|
||||
|
Loading…
x
Reference in New Issue
Block a user