diff --git a/.clang-tidy.hash b/.clang-tidy.hash new file mode 100644 index 0000000000..30c52f5baa --- /dev/null +++ b/.clang-tidy.hash @@ -0,0 +1 @@ +a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a \ No newline at end of file diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 082539adaa..3a7b301b60 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -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 . diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml new file mode 100644 index 0000000000..4e89da267c --- /dev/null +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -0,0 +1,76 @@ +name: Clang-tidy Hash CI + +on: + pull_request: + paths: + - ".clang-tidy" + - "platformio.ini" + - "requirements_dev.txt" + - ".clang-tidy.hash" + - "script/clang_tidy_hash.py" + - ".github/workflows/ci-clang-tidy-hash.yml" + +permissions: + contents: read + pull-requests: write + +jobs: + verify-hash: + name: Verify clang-tidy hash + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.2.2 + + - name: Set up Python + uses: actions/setup-python@v5.6.0 + with: + python-version: "3.11" + + - name: Verify hash + run: | + python script/clang_tidy_hash.py --verify + + - if: failure() + name: Show hash details + run: | + python script/clang_tidy_hash.py + echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY + echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY + echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY + + - if: failure() + name: Request changes + uses: actions/github-script@v7.0.1 + with: + script: | + await github.rest.pulls.createReview({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + event: 'REQUEST_CHANGES', + body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.' + }) + + - if: success() + name: Dismiss review + uses: actions/github-script@v7.0.1 + with: + script: | + let reviews = await github.rest.pulls.listReviews({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + for (let review of reviews.data) { + if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') { + await github.rest.pulls.dismissReview({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + review_id: review.id, + message: 'Clang-tidy hash now matches configuration.' + }); + } + } + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca6d1b0aac..503a50c5c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -204,6 +212,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.2.2 - name: Restore Python + id: restore-python uses: ./.github/actions/restore-python with: python-version: ${{ matrix.python-version }} @@ -213,23 +222,108 @@ jobs: - name: Run pytest if: matrix.os == 'windows-latest' run: | - ./venv/Scripts/activate - pytest -vv --cov-report=xml --tb=native -n auto tests + . ./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' run: | . venv/bin/activate - pytest -vv --cov-report=xml --tb=native -n auto tests + pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ - name: Upload coverage to Codecov uses: codecov/codecov-action@v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} + - name: Save Python virtual environment cache + if: github.ref == 'refs/heads/dev' + uses: actions/cache/save@v4.2.3 + with: + 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 + - name: Set up Python 3.13 + id: python + uses: actions/setup-python@v5.6.0 + with: + python-version: "3.13" + - name: Restore Python virtual environment + id: cache-venv + uses: actions/cache@v4.2.3 + with: + path: venv + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + python --version + pip install -r requirements.txt -r requirements_test.txt + pip install -e . + - name: Register matcher + run: echo "::add-matcher::.github/workflows/matchers/pytest.json" + - name: Run integration tests + run: | + . venv/bin/activate + pytest -vv --no-cov --tb=native -n auto tests/integration/ clang-format: name: Check clang-format 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 @@ -263,6 +357,10 @@ jobs: - pylint - pytest - pyupgrade + - determine-jobs + if: needs.determine-jobs.outputs.clang-tidy == 'true' + env: + GH_TOKEN: ${{ github.token }} strategy: fail-fast: false max-parallel: 2 @@ -301,6 +399,10 @@ 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 with: @@ -312,14 +414,14 @@ jobs: uses: actions/cache@v4.2.3 with: path: ~/.platformio - key: platformio-${{ matrix.pio_cache_key }} + key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' uses: actions/cache/restore@v4.2.3 with: path: ~/.platformio - key: platformio-${{ matrix.pio_cache_key }} + key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Register problem matchers run: | @@ -333,10 +435,28 @@ jobs: mkdir -p .temp pio run --list-targets -e esp32-idf-tidy + - name: Check if full clang-tidy scan needed + id: check_full_scan + run: | + . venv/bin/activate + if python script/clang_tidy_hash.py --check; then + echo "full_scan=true" >> $GITHUB_OUTPUT + echo "reason=hash_changed" >> $GITHUB_OUTPUT + else + echo "full_scan=false" >> $GITHUB_OUTPUT + echo "reason=normal" >> $GITHUB_OUTPUT + fi + - name: Run clang-tidy run: | . venv/bin/activate - script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} + if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then + echo "Running FULL clang-tidy scan (hash changed)" + script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} + else + echo "Running clang-tidy on changed files only" + script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} + fi env: # Also cache libdeps, store them in a ~/.platformio subfolder PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps @@ -346,59 +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' - 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 - with: - # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works. - fetch-depth: 500 - - name: Get target branch - id: target-branch - run: | - echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT - - name: Fetch ${{ steps.target-branch.outputs.branch }} branch - run: | - git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }} - git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD - - 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 --branch ${{ steps.target-branch.outputs.branch }}) - 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: | @@ -426,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: @@ -436,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: @@ -444,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 @@ -494,9 +573,10 @@ jobs: - flake8 - pylint - pytest + - integration-tests - pyupgrade - clang-tidy - - list-components + - determine-jobs - test-build-components - test-build-components-splitter - test-build-components-split diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 831473c325..9c7955cc88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.2 + rev: v0.12.3 hooks: # Run the linter. - id: ruff @@ -48,3 +48,10 @@ repos: entry: python3 script/run-in-env.py pylint language: system types: [python] + - id: clang-tidy-hash + name: Update clang-tidy hash + entry: python script/clang_tidy_hash.py --update-if-changed + language: python + files: ^(\.clang-tidy|platformio\.ini|requirements_dev\.txt)$ + pass_filenames: false + additional_dependencies: [] diff --git a/CODEOWNERS b/CODEOWNERS index ca3849eb0d..2975080ba9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,7 +28,7 @@ esphome/components/aic3204/* @kbx81 esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau esphome/components/airthings_wave_mini/* @ncareau -esphome/components/airthings_wave_plus/* @jeromelaban +esphome/components/airthings_wave_plus/* @jeromelaban @precurse esphome/components/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/alpha3/* @jan-hofmeier esphome/components/am2315c/* @swoboda1337 @@ -170,6 +170,7 @@ esphome/components/ft5x06/* @clydebarrow esphome/components/ft63x6/* @gpambrozio esphome/components/gcja5/* @gcormier esphome/components/gdk101/* @Szewcson +esphome/components/gl_r01_i2c/* @pkejval esphome/components/globals/* @esphome/core esphome/components/gp2y1010au0f/* @zry98 esphome/components/gp8403/* @jesserockz @@ -254,6 +255,7 @@ esphome/components/ln882x/* @lamauny esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/logger/select/* @clydebarrow +esphome/components/lps22/* @nagisa esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr501/* @latonita esphome/components/ltr_als_ps/* @latonita diff --git a/Doxyfile b/Doxyfile index 03d432b924..1f5ac5aa1b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.7.0-dev +PROJECT_NUMBER = 2025.8.0-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 5f94c61a08..10b7df8638 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -10,8 +10,15 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32S3, ) +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv -from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266 +from esphome.const import ( + CONF_ANALOG, + CONF_INPUT, + CONF_NUMBER, + PLATFORM_ESP8266, + PlatformFramework, +) from esphome.core import CORE CODEOWNERS = ["@esphome/core"] @@ -229,3 +236,20 @@ def validate_adc_pin(value): )(value) raise NotImplementedError + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "adc_sensor_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "adc_sensor_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/airthings_wave_plus/__init__.py b/esphome/components/airthings_wave_plus/__init__.py index 1aff461edd..e26bfd471b 100644 --- a/esphome/components/airthings_wave_plus/__init__.py +++ b/esphome/components/airthings_wave_plus/__init__.py @@ -1 +1 @@ -CODEOWNERS = ["@jeromelaban"] +CODEOWNERS = ["@jeromelaban", "@precurse"] diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 8c8c514fdb..5ed62fff62 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -73,11 +73,29 @@ void AirthingsWavePlus::dump_config() { LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_); } -AirthingsWavePlus::AirthingsWavePlus() { - this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID); - this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); +void AirthingsWavePlus::setup() { + const char *service_uuid; + const char *characteristic_uuid; + const char *access_control_point_characteristic_uuid; + + // Change UUIDs for Wave Radon Gen2 + switch (this->wave_device_type_) { + case WaveDeviceType::WAVE_GEN2: + service_uuid = SERVICE_UUID_WAVE_RADON_GEN2; + characteristic_uuid = CHARACTERISTIC_UUID_WAVE_RADON_GEN2; + access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2; + break; + default: + // Wave Plus + service_uuid = SERVICE_UUID; + characteristic_uuid = CHARACTERISTIC_UUID; + access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID; + } + + this->service_uuid_ = espbt::ESPBTUUID::from_raw(service_uuid); + this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(characteristic_uuid); this->access_control_point_characteristic_uuid_ = - espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID); + espbt::ESPBTUUID::from_raw(access_control_point_characteristic_uuid); } } // namespace airthings_wave_plus diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index bd7a40ef8b..c978a9af92 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -9,13 +9,20 @@ namespace airthings_wave_plus { namespace espbt = esphome::esp32_ble_tracker; +enum WaveDeviceType : uint8_t { WAVE_PLUS = 0, WAVE_GEN2 = 1 }; + static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba"; +static const char *const SERVICE_UUID_WAVE_RADON_GEN2 = "b42e4a8e-ade7-11e4-89d3-123b93f75cba"; +static const char *const CHARACTERISTIC_UUID_WAVE_RADON_GEN2 = "b42e4dcc-ade7-11e4-89d3-123b93f75cba"; +static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2 = + "b42e50d8-ade7-11e4-89d3-123b93f75cba"; + class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { public: - AirthingsWavePlus(); + void setup() override; void dump_config() override; @@ -23,12 +30,14 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; } + void set_device_type(WaveDeviceType wave_device_type) { wave_device_type_ = wave_device_type; } protected: bool is_valid_radon_value_(uint16_t radon); bool is_valid_co2_value_(uint16_t co2); void read_sensors(uint8_t *raw_value, uint16_t value_len) override; + WaveDeviceType wave_device_type_{WaveDeviceType::WAVE_PLUS}; sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index e0e90735f0..a12c70f04c 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_ILLUMINANCE, CONF_RADON, CONF_RADON_LONG_TERM, + CONF_TVOC, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_ILLUMINANCE, ICON_RADIOACTIVE, @@ -15,6 +16,7 @@ from esphome.const import ( UNIT_LUX, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType DEPENDENCIES = airthings_wave_base.DEPENDENCIES @@ -25,35 +27,59 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_( "AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase ) +CONF_DEVICE_TYPE = "device_type" +WaveDeviceType = airthings_wave_plus_ns.enum("WaveDeviceType") +DEVICE_TYPES = { + "WAVE_PLUS": WaveDeviceType.WAVE_PLUS, + "WAVE_GEN2": WaveDeviceType.WAVE_GEN2, +} -CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(AirthingsWavePlus), - cv.Optional(CONF_RADON): sensor.sensor_schema( - unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, - icon=ICON_RADIOACTIVE, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( - unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, - icon=ICON_RADIOACTIVE, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CO2): sensor.sensor_schema( - unit_of_measurement=UNIT_PARTS_PER_MILLION, - accuracy_decimals=0, - device_class=DEVICE_CLASS_CARBON_DIOXIDE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - unit_of_measurement=UNIT_LUX, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ILLUMINANCE, - state_class=STATE_CLASS_MEASUREMENT, - ), - } + +def validate_wave_gen2_config(config: ConfigType) -> ConfigType: + """Validate that Wave Gen2 devices don't have CO2 or TVOC sensors.""" + if config[CONF_DEVICE_TYPE] == "WAVE_GEN2": + if CONF_CO2 in config: + raise cv.Invalid("Wave Gen2 devices do not support CO2 sensor") + # Check for TVOC in the base schema config + if CONF_TVOC in config: + raise cv.Invalid("Wave Gen2 devices do not support TVOC sensor") + return config + + +CONFIG_SCHEMA = cv.All( + airthings_wave_base.BASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirthingsWavePlus), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DEVICE_TYPE, default="WAVE_PLUS"): cv.enum( + DEVICE_TYPES, upper=True + ), + } + ), + validate_wave_gen2_config, ) @@ -73,3 +99,4 @@ async def to_code(config): if config_illuminance := config.get(CONF_ILLUMINANCE): sens = await sensor.new_sensor(config_illuminance) cg.add(var.set_illuminance(sens)) + cg.add(var.set_device_type(config[CONF_DEVICE_TYPE])) diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 4a6ce371e5..b736e6b8b0 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -23,7 +23,7 @@ void APDS9960::setup() { return; } - if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs + if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs this->error_code_ = WRONG_ID; this->mark_failed(); return; diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 2f1be28293..5b302760b1 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -3,6 +3,7 @@ import base64 from esphome import automation from esphome.automation import Condition import esphome.codegen as cg +from esphome.config_helpers import get_logger_level import esphome.config_validation as cv from esphome.const import ( CONF_ACTION, @@ -23,8 +24,9 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VARIABLES, ) -from esphome.core import coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority +DOMAIN = "api" DEPENDENCIES = ["network"] AUTO_LOAD = ["socket"] CODEOWNERS = ["@OttoWinter"] @@ -50,6 +52,7 @@ SERVICE_ARG_NATIVE_TYPES = { } CONF_ENCRYPTION = "encryption" CONF_BATCH_DELAY = "batch_delay" +CONF_CUSTOM_SERVICES = "custom_services" def validate_encryption_key(value): @@ -114,6 +117,7 @@ CONFIG_SCHEMA = cv.All( cv.positive_time_period_milliseconds, cv.Range(max=cv.TimePeriod(milliseconds=65535)), ), + cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean, cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( single=True ), @@ -138,8 +142,11 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) + # Set USE_API_SERVICES if any services are enabled + if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: + cg.add_define("USE_API_SERVICES") + if actions := config.get(CONF_ACTIONS, []): - cg.add_define("USE_API_YAML_SERVICES") for conf in actions: template_args = [] func_args = [] @@ -313,3 +320,25 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg @automation.register_condition("api.connected", APIConnectedCondition, {}) async def api_connected_to_code(config, condition_id, template_arg, args): return cg.new_Pvariable(condition_id, template_arg) + + +def FILTER_SOURCE_FILES() -> list[str]: + """Filter out api_pb2_dump.cpp when proto message dumping is not enabled + and user_services.cpp when no services are defined.""" + files_to_filter = [] + + # api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined + # This is a particularly large file that still needs to be opened and read + # all the way to the end even when ifdef'd out + # + # HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set, + # which happens when the logger level is VERY_VERBOSE + if get_logger_level() != "VERY_VERBOSE": + files_to_filter.append("api_pb2_dump.cpp") + + # user_services.cpp is only needed when services are defined + config = CORE.config.get(DOMAIN, {}) + if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]: + files_to_filter.append("user_services.cpp") + + return files_to_filter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 93ac698f39..8c5d6d2018 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -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 ==================== @@ -799,18 +807,21 @@ enum ServiceArgType { SERVICE_ARG_TYPE_STRING_ARRAY = 7; } message ListEntitiesServicesArgument { + option (ifdef) = "USE_API_SERVICES"; string name = 1; ServiceArgType type = 2; } message ListEntitiesServicesResponse { option (id) = 41; option (source) = SOURCE_SERVER; + option (ifdef) = "USE_API_SERVICES"; string name = 1; fixed32 key = 2; repeated ListEntitiesServicesArgument args = 3; } message ExecuteServiceArgument { + option (ifdef) = "USE_API_SERVICES"; bool bool_ = 1; int32 legacy_int = 2; float float_ = 3; @@ -826,6 +837,7 @@ message ExecuteServiceRequest { option (id) = 42; option (source) = SOURCE_CLIENT; option (no_delay) = true; + option (ifdef) = "USE_API_SERVICES"; fixed32 key = 1; repeated ExecuteServiceArgument args = 2; @@ -850,12 +862,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 +994,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 +1020,7 @@ message ClimateCommandRequest { string custom_preset = 21; bool has_target_humidity = 22; float target_humidity = 23; + uint32 device_id = 24; } // ==================== NUMBER ==================== @@ -1054,9 +1070,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 +1114,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 +1157,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 +1168,7 @@ message SirenCommandRequest { uint32 duration = 7; bool has_volume = 8; float volume = 9; + uint32 device_id = 10; } // ==================== LOCK ==================== @@ -1201,12 +1223,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 +1256,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 +1327,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 +1342,7 @@ message MediaPlayerCommandRequest { bool has_announcement = 8; bool announcement = 9; + uint32 device_id = 10; } // ==================== BLUETOOTH ==================== @@ -1843,9 +1871,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 +1922,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 +1968,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 +2015,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 +2101,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 +2146,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 +2200,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; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9b4ec2ed50..9b1f01e93f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -42,6 +42,19 @@ static const char *const TAG = "api.connection"; static const int CAMERA_STOP_STREAM = 5000; #endif +// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object +#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \ + entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \ + if ((entity_var) == nullptr) \ + return; \ + auto call = (entity_var)->make_call(); + +// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found +#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \ + entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \ + if ((entity_var) == nullptr) \ + return; + APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) @@ -180,14 +193,15 @@ void APIConnection::loop() { // If we can't send the ping request directly (tx_buffer full), // schedule it at the front of the batch so it will be sent with priority ESP_LOGW(TAG, "Buffer full, ping queued"); - this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); + this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE, + PingRequest::ESTIMATED_SIZE); this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings } } #ifdef USE_CAMERA if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { - uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_->available()); + uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); bool done = this->image_reader_->available() == to_send; uint32_t msg_size = 0; ProtoSize::add_fixed_field<4>(msg_size, 1, true); @@ -248,7 +262,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) { // Encodes a message to the buffer and returns the total number of bytes used, // including header and footer overhead. Returns 0 if the message doesn't fit. -uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, +uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { #ifdef HAS_PROTO_MESSAGE_DUMP // If in log-only mode, just log and return @@ -299,7 +313,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, - BinarySensorStateResponse::MESSAGE_TYPE); + BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -325,7 +339,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne #ifdef USE_COVER bool APIConnection::send_cover_state(cover::Cover *cover) { - return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE, + CoverStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -355,11 +370,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::cover_command(const CoverCommandRequest &msg) { - cover::Cover *cover = App.get_cover_by_key(msg.key); - if (cover == nullptr) - return; - - auto call = cover->make_call(); + ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) if (msg.has_legacy_command) { switch (msg.legacy_command) { case enums::LEGACY_COVER_COMMAND_OPEN: @@ -385,7 +396,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #ifdef USE_FAN bool APIConnection::send_fan_state(fan::Fan *fan) { - return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE, + FanStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -420,11 +432,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::fan_command(const FanCommandRequest &msg) { - fan::Fan *fan = App.get_fan_by_key(msg.key); - if (fan == nullptr) - return; - - auto call = fan->make_call(); + ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan) if (msg.has_state) call.set_state(msg.state); if (msg.has_oscillating) @@ -443,7 +451,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { #ifdef USE_LIGHT bool APIConnection::send_light_state(light::LightState *light) { - return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE, + LightStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -496,11 +505,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::light_command(const LightCommandRequest &msg) { - light::LightState *light = App.get_light_by_key(msg.key); - if (light == nullptr) - return; - - auto call = light->make_call(); + ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light) if (msg.has_state) call.set_state(msg.state); if (msg.has_brightness) @@ -534,7 +539,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) { #ifdef USE_SENSOR bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { - return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE, + SensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -563,7 +569,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * #ifdef USE_SWITCH bool APIConnection::send_switch_state(switch_::Switch *a_switch) { - return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE, + SwitchStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -585,9 +592,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { - switch_::Switch *a_switch = App.get_switch_by_key(msg.key); - if (a_switch == nullptr) - return; + ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) if (msg.state) { a_switch->turn_on(); @@ -600,7 +605,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { #ifdef USE_TEXT_SENSOR bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, - TextSensorStateResponse::MESSAGE_TYPE); + TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -624,7 +629,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect #ifdef USE_CLIMATE bool APIConnection::send_climate_state(climate::Climate *climate) { - return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE, + ClimateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -692,11 +698,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { - climate::Climate *climate = App.get_climate_by_key(msg.key); - if (climate == nullptr) - return; - - auto call = climate->make_call(); + ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate) if (msg.has_mode) call.set_mode(static_cast(msg.mode)); if (msg.has_target_temperature) @@ -723,7 +725,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { #ifdef USE_NUMBER bool APIConnection::send_number_state(number::Number *number) { - return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE, + NumberStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -750,11 +753,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::number_command(const NumberCommandRequest &msg) { - number::Number *number = App.get_number_by_key(msg.key); - if (number == nullptr) - return; - - auto call = number->make_call(); + ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) call.set_value(msg.state); call.perform(); } @@ -762,7 +761,8 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { #ifdef USE_DATETIME_DATE bool APIConnection::send_date_state(datetime::DateEntity *date) { - return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE, + DateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -783,11 +783,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::date_command(const DateCommandRequest &msg) { - datetime::DateEntity *date = App.get_date_by_key(msg.key); - if (date == nullptr) - return; - - auto call = date->make_call(); + ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date) call.set_date(msg.year, msg.month, msg.day); call.perform(); } @@ -795,7 +791,8 @@ void APIConnection::date_command(const DateCommandRequest &msg) { #ifdef USE_DATETIME_TIME bool APIConnection::send_time_state(datetime::TimeEntity *time) { - return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE, + TimeStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -816,11 +813,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::time_command(const TimeCommandRequest &msg) { - datetime::TimeEntity *time = App.get_time_by_key(msg.key); - if (time == nullptr) - return; - - auto call = time->make_call(); + ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time) call.set_time(msg.hour, msg.minute, msg.second); call.perform(); } @@ -829,7 +822,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) { #ifdef USE_DATETIME_DATETIME bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, - DateTimeStateResponse::MESSAGE_TYPE); + DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -851,11 +844,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { - datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); - if (datetime == nullptr) - return; - - auto call = datetime->make_call(); + ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime) call.set_datetime(msg.epoch_seconds); call.perform(); } @@ -863,7 +852,8 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { #ifdef USE_TEXT bool APIConnection::send_text_state(text::Text *text) { - return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE, + TextStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -888,11 +878,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::text_command(const TextCommandRequest &msg) { - text::Text *text = App.get_text_by_key(msg.key); - if (text == nullptr) - return; - - auto call = text->make_call(); + ENTITY_COMMAND_MAKE_CALL(text::Text, text, text) call.set_value(msg.state); call.perform(); } @@ -900,7 +886,8 @@ void APIConnection::text_command(const TextCommandRequest &msg) { #ifdef USE_SELECT bool APIConnection::send_select_state(select::Select *select) { - return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE, + SelectStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -923,11 +910,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::select_command(const SelectCommandRequest &msg) { - select::Select *select = App.get_select_by_key(msg.key); - if (select == nullptr) - return; - - auto call = select->make_call(); + ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) call.set_option(msg.state); call.perform(); } @@ -943,17 +926,15 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) { - button::Button *button = App.get_button_by_key(msg.key); - if (button == nullptr) - return; - + ENTITY_COMMAND_GET(button::Button, button, button) button->press(); } #endif #ifdef USE_LOCK bool APIConnection::send_lock_state(lock::Lock *a_lock) { - return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE, + LockStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -976,9 +957,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::lock_command(const LockCommandRequest &msg) { - lock::Lock *a_lock = App.get_lock_by_key(msg.key); - if (a_lock == nullptr) - return; + ENTITY_COMMAND_GET(lock::Lock, a_lock, lock) switch (msg.command) { case enums::LOCK_UNLOCK: @@ -996,7 +975,8 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { #ifdef USE_VALVE bool APIConnection::send_valve_state(valve::Valve *valve) { - return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE, + ValveStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1020,11 +1000,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::valve_command(const ValveCommandRequest &msg) { - valve::Valve *valve = App.get_valve_by_key(msg.key); - if (valve == nullptr) - return; - - auto call = valve->make_call(); + ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) if (msg.has_position) call.set_position(msg.position); if (msg.stop) @@ -1036,7 +1012,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) { #ifdef USE_MEDIA_PLAYER bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, - MediaPlayerStateResponse::MESSAGE_TYPE); + MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1070,11 +1046,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { - media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); - if (media_player == nullptr) - return; - - auto call = media_player->make_call(); + ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player) if (msg.has_command) { call.set_command(static_cast(msg.command)); } @@ -1191,66 +1163,53 @@ void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequ #endif #ifdef USE_VOICE_ASSISTANT +bool APIConnection::check_voice_assistant_api_connection_() const { + return voice_assistant::global_voice_assistant != nullptr && + voice_assistant::global_voice_assistant->get_api_connection() == this; +} + void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { if (voice_assistant::global_voice_assistant != nullptr) { voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); } } void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } + if (!this->check_voice_assistant_api_connection_()) { + return; + } - if (msg.error) { - voice_assistant::global_voice_assistant->failed_to_start(); - return; - } - if (msg.port == 0) { - // Use API Audio - voice_assistant::global_voice_assistant->start_streaming(); - } else { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - this->helper_->getpeername((struct sockaddr *) &storage, &len); - voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); - } + if (msg.error) { + voice_assistant::global_voice_assistant->failed_to_start(); + return; + } + if (msg.port == 0) { + // Use API Audio + voice_assistant::global_voice_assistant->start_streaming(); + } else { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + this->helper_->getpeername((struct sockaddr *) &storage, &len); + voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); } }; void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } - + if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_event(msg); } } void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } - + if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_audio(msg); } }; void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } - + if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_timer_event(msg); } }; void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } - + if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_announce(msg); } } @@ -1258,35 +1217,29 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( const VoiceAssistantConfigurationRequest &msg) { VoiceAssistantConfigurationResponse resp; - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return resp; - } - - auto &config = voice_assistant::global_voice_assistant->get_configuration(); - for (auto &wake_word : config.available_wake_words) { - VoiceAssistantWakeWord resp_wake_word; - resp_wake_word.id = wake_word.id; - resp_wake_word.wake_word = wake_word.wake_word; - for (const auto &lang : wake_word.trained_languages) { - resp_wake_word.trained_languages.push_back(lang); - } - resp.available_wake_words.push_back(std::move(resp_wake_word)); - } - for (auto &wake_word_id : config.active_wake_words) { - resp.active_wake_words.push_back(wake_word_id); - } - resp.max_active_wake_words = config.max_active_wake_words; + if (!this->check_voice_assistant_api_connection_()) { + return resp; } + + auto &config = voice_assistant::global_voice_assistant->get_configuration(); + for (auto &wake_word : config.available_wake_words) { + VoiceAssistantWakeWord resp_wake_word; + resp_wake_word.id = wake_word.id; + resp_wake_word.wake_word = wake_word.wake_word; + for (const auto &lang : wake_word.trained_languages) { + resp_wake_word.trained_languages.push_back(lang); + } + resp.available_wake_words.push_back(std::move(resp_wake_word)); + } + for (auto &wake_word_id : config.active_wake_words) { + resp.active_wake_words.push_back(wake_word_id); + } + resp.max_active_wake_words = config.max_active_wake_words; return resp; } void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { - if (voice_assistant::global_voice_assistant != nullptr) { - if (voice_assistant::global_voice_assistant->get_api_connection() != this) { - return; - } - + if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } } @@ -1296,7 +1249,8 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon #ifdef USE_ALARM_CONTROL_PANEL bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, - AlarmControlPanelStateResponse::MESSAGE_TYPE); + AlarmControlPanelStateResponse::MESSAGE_TYPE, + AlarmControlPanelStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1318,11 +1272,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP is_single); } void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { - alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key); - if (a_alarm_control_panel == nullptr) - return; - - auto call = a_alarm_control_panel->make_call(); + ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel) switch (msg.command) { case enums::ALARM_CONTROL_PANEL_DISARM: call.disarm(); @@ -1353,7 +1303,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #ifdef USE_EVENT void APIConnection::send_event(event::Event *event, const std::string &event_type) { - this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); + this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, + EventResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1377,7 +1328,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c #ifdef USE_UPDATE bool APIConnection::send_update_state(update::UpdateEntity *update) { - return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); + return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE, + UpdateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1408,9 +1360,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::update_command(const UpdateCommandRequest &msg) { - update::UpdateEntity *update = App.get_update_by_key(msg.key); - if (update == nullptr) - return; + ENTITY_COMMAND_GET(update::UpdateEntity, update, update) switch (msg.command) { case enums::UPDATE_COMMAND_UPDATE: @@ -1429,12 +1379,11 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { } #endif -bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { +bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) { if (this->flags_.log_subscription < level) return false; // Pre-calculate message size to avoid reallocations - const size_t line_length = strlen(line); uint32_t msg_size = 0; // Add size for level field (field ID 1, varint type) @@ -1443,14 +1392,14 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char // Add size for string field (field ID 3, string type) // 1 byte for field tag + size of length varint + string length - msg_size += 1 + api::ProtoSize::varint(static_cast(line_length)) + line_length; + msg_size += 1 + api::ProtoSize::varint(static_cast(message_len)) + message_len; // Create a pre-sized buffer auto buffer = this->create_buffer(msg_size); // Encode the message (SubscribeLogsResponse) buffer.encode_uint32(1, static_cast(level)); // LogLevel level = 1 - buffer.encode_string(3, line, line_length); // string message = 3 + buffer.encode_string(3, line, message_len); // string message = 3 // SubscribeLogsResponse - 29 return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE); @@ -1572,6 +1521,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes } } } +#ifdef USE_API_SERVICES void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; for (auto *service : this->parent_->get_user_services()) { @@ -1583,6 +1533,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { ESP_LOGV(TAG, "Could not find service"); } } +#endif #ifdef USE_API_NOISE NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { psk_t psk{}; @@ -1626,7 +1577,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { } return false; } -bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) { +bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse return false; } @@ -1660,7 +1611,8 @@ void APIConnection::on_fatal_error() { this->flags_.remove = true; } -void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { +void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, + uint8_t estimated_size) { // Check if we already have a message of this type for this entity // This provides deduplication per entity/message_type combination // O(n) but optimized for RAM and not performance. @@ -1675,12 +1627,13 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c } // No existing item found, add new one - items.emplace_back(entity, std::move(creator), message_type); + items.emplace_back(entity, std::move(creator), message_type, estimated_size); } -void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) { +void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, + uint8_t estimated_size) { // Insert at front for high priority messages (no deduplication check) - items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type)); + items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size)); } bool APIConnection::schedule_batch_() { @@ -1752,7 +1705,7 @@ void APIConnection::process_batch_() { uint32_t total_estimated_size = 0; for (size_t i = 0; i < this->deferred_batch_.size(); i++) { const auto &item = this->deferred_batch_[i]; - total_estimated_size += get_estimated_message_size(item.message_type); + total_estimated_size += item.estimated_size; } // Calculate total overhead for all messages @@ -1790,9 +1743,9 @@ void APIConnection::process_batch_() { // Update tracking variables items_processed++; - // After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation + // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation if (items_processed == 1) { - remaining_size = MAX_PACKET_SIZE; + remaining_size = MAX_BATCH_PACKET_SIZE; } remaining_size -= payload_size; // Calculate where the next message's header padding will start @@ -1846,7 +1799,7 @@ void APIConnection::process_batch_() { } uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, - bool is_single, uint16_t message_type) const { + bool is_single, uint8_t message_type) const { #ifdef USE_EVENT // Special case: EventResponse uses string pointer if (message_type == EventResponse::MESSAGE_TYPE) { @@ -1877,149 +1830,6 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); } -uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) { - // Use generated ESTIMATED_SIZE constants from each message type - switch (message_type) { -#ifdef USE_BINARY_SENSOR - case BinarySensorStateResponse::MESSAGE_TYPE: - return BinarySensorStateResponse::ESTIMATED_SIZE; - case ListEntitiesBinarySensorResponse::MESSAGE_TYPE: - return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_SENSOR - case SensorStateResponse::MESSAGE_TYPE: - return SensorStateResponse::ESTIMATED_SIZE; - case ListEntitiesSensorResponse::MESSAGE_TYPE: - return ListEntitiesSensorResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_SWITCH - case SwitchStateResponse::MESSAGE_TYPE: - return SwitchStateResponse::ESTIMATED_SIZE; - case ListEntitiesSwitchResponse::MESSAGE_TYPE: - return ListEntitiesSwitchResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_TEXT_SENSOR - case TextSensorStateResponse::MESSAGE_TYPE: - return TextSensorStateResponse::ESTIMATED_SIZE; - case ListEntitiesTextSensorResponse::MESSAGE_TYPE: - return ListEntitiesTextSensorResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_NUMBER - case NumberStateResponse::MESSAGE_TYPE: - return NumberStateResponse::ESTIMATED_SIZE; - case ListEntitiesNumberResponse::MESSAGE_TYPE: - return ListEntitiesNumberResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_TEXT - case TextStateResponse::MESSAGE_TYPE: - return TextStateResponse::ESTIMATED_SIZE; - case ListEntitiesTextResponse::MESSAGE_TYPE: - return ListEntitiesTextResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_SELECT - case SelectStateResponse::MESSAGE_TYPE: - return SelectStateResponse::ESTIMATED_SIZE; - case ListEntitiesSelectResponse::MESSAGE_TYPE: - return ListEntitiesSelectResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_LOCK - case LockStateResponse::MESSAGE_TYPE: - return LockStateResponse::ESTIMATED_SIZE; - case ListEntitiesLockResponse::MESSAGE_TYPE: - return ListEntitiesLockResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_EVENT - case EventResponse::MESSAGE_TYPE: - return EventResponse::ESTIMATED_SIZE; - case ListEntitiesEventResponse::MESSAGE_TYPE: - return ListEntitiesEventResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_COVER - case CoverStateResponse::MESSAGE_TYPE: - return CoverStateResponse::ESTIMATED_SIZE; - case ListEntitiesCoverResponse::MESSAGE_TYPE: - return ListEntitiesCoverResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_FAN - case FanStateResponse::MESSAGE_TYPE: - return FanStateResponse::ESTIMATED_SIZE; - case ListEntitiesFanResponse::MESSAGE_TYPE: - return ListEntitiesFanResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_LIGHT - case LightStateResponse::MESSAGE_TYPE: - return LightStateResponse::ESTIMATED_SIZE; - case ListEntitiesLightResponse::MESSAGE_TYPE: - return ListEntitiesLightResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_CLIMATE - case ClimateStateResponse::MESSAGE_TYPE: - return ClimateStateResponse::ESTIMATED_SIZE; - case ListEntitiesClimateResponse::MESSAGE_TYPE: - return ListEntitiesClimateResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_ESP32_CAMERA - case ListEntitiesCameraResponse::MESSAGE_TYPE: - return ListEntitiesCameraResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_BUTTON - case ListEntitiesButtonResponse::MESSAGE_TYPE: - return ListEntitiesButtonResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_MEDIA_PLAYER - case MediaPlayerStateResponse::MESSAGE_TYPE: - return MediaPlayerStateResponse::ESTIMATED_SIZE; - case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE: - return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - case AlarmControlPanelStateResponse::MESSAGE_TYPE: - return AlarmControlPanelStateResponse::ESTIMATED_SIZE; - case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE: - return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_DATETIME_DATE - case DateStateResponse::MESSAGE_TYPE: - return DateStateResponse::ESTIMATED_SIZE; - case ListEntitiesDateResponse::MESSAGE_TYPE: - return ListEntitiesDateResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_DATETIME_TIME - case TimeStateResponse::MESSAGE_TYPE: - return TimeStateResponse::ESTIMATED_SIZE; - case ListEntitiesTimeResponse::MESSAGE_TYPE: - return ListEntitiesTimeResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_DATETIME_DATETIME - case DateTimeStateResponse::MESSAGE_TYPE: - return DateTimeStateResponse::ESTIMATED_SIZE; - case ListEntitiesDateTimeResponse::MESSAGE_TYPE: - return ListEntitiesDateTimeResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_VALVE - case ValveStateResponse::MESSAGE_TYPE: - return ValveStateResponse::ESTIMATED_SIZE; - case ListEntitiesValveResponse::MESSAGE_TYPE: - return ListEntitiesValveResponse::ESTIMATED_SIZE; -#endif -#ifdef USE_UPDATE - case UpdateStateResponse::MESSAGE_TYPE: - return UpdateStateResponse::ESTIMATED_SIZE; - case ListEntitiesUpdateResponse::MESSAGE_TYPE: - return ListEntitiesUpdateResponse::ESTIMATED_SIZE; -#endif - case ListEntitiesServicesResponse::MESSAGE_TYPE: - return ListEntitiesServicesResponse::ESTIMATED_SIZE; - case ListEntitiesDoneResponse::MESSAGE_TYPE: - return ListEntitiesDoneResponse::ESTIMATED_SIZE; - case DisconnectRequest::MESSAGE_TYPE: - return DisconnectRequest::ESTIMATED_SIZE; - default: - // Fallback for unknown message types - return 24; - } -} - } // namespace api } // namespace esphome #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 166dbc3656..0051a143de 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -33,7 +33,7 @@ class APIConnection : public APIServerConnection { bool send_list_info_done() { return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, - ListEntitiesDoneResponse::MESSAGE_TYPE); + ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); } #ifdef USE_BINARY_SENSOR bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); @@ -107,7 +107,7 @@ class APIConnection : public APIServerConnection { bool send_media_player_state(media_player::MediaPlayer *media_player); void media_player_command(const MediaPlayerCommandRequest &msg) override; #endif - bool try_send_log_message(int level, const char *tag, const char *line); + bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { if (!this->flags_.service_call_subscription) return; @@ -195,7 +195,9 @@ class APIConnection : public APIServerConnection { // TODO return {}; } +#ifdef USE_API_SERVICES void execute_service(const ExecuteServiceRequest &msg) override; +#endif #ifdef USE_API_NOISE NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; #endif @@ -256,7 +258,7 @@ class APIConnection : public APIServerConnection { } bool try_to_clear_buffer(bool log_out_of_space); - bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override; + bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; std::string get_client_combined_info() const { if (this->client_info_ == this->client_peername_) { @@ -298,9 +300,14 @@ class APIConnection : public APIServerConnection { } // Non-template helper to encode any ProtoMessage - static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, + static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, uint32_t remaining_size, bool is_single); +#ifdef USE_VOICE_ASSISTANT + // Helper to check voice assistant validity and connection ownership + inline bool check_voice_assistant_api_connection_() const; +#endif + // Helper method to process multiple entities from an iterator in a batch template void process_iterator_batch_(Iterator &iterator) { size_t initial_size = this->deferred_batch_.size(); @@ -438,9 +445,6 @@ class APIConnection : public APIServerConnection { static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); - // Helper function to get estimated message size for buffer pre-allocation - static uint16_t get_estimated_message_size(uint16_t message_type); - // Batch message method for ping requests static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); @@ -500,10 +504,10 @@ class APIConnection : public APIServerConnection { // Call operator - uses message_type to determine union type uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, - uint16_t message_type) const; + uint8_t message_type) const; // Manual cleanup method - must be called before destruction for string types - void cleanup(uint16_t message_type) { + void cleanup(uint8_t message_type) { #ifdef USE_EVENT if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { delete data_.string_ptr; @@ -524,11 +528,12 @@ class APIConnection : public APIServerConnection { struct BatchItem { EntityBase *entity; // Entity pointer MessageCreator creator; // Function that creates the message when needed - uint16_t message_type; // Message type for overhead calculation + uint8_t message_type; // Message type for overhead calculation (max 255) + uint8_t estimated_size; // Estimated message size (max 255 bytes) // Constructor for creating BatchItem - BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type) - : entity(entity), creator(std::move(creator)), message_type(message_type) {} + BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) + : entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {} }; std::vector items; @@ -554,9 +559,9 @@ class APIConnection : public APIServerConnection { } // Add item to the batch - void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); + void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); // Add item to the front of the batch (for high priority messages like ping) - void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); + void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); // Clear all items with proper cleanup void clear() { @@ -625,7 +630,7 @@ class APIConnection : public APIServerConnection { // to send in one go. This is the maximum size of a single packet // that can be sent over the network. // This is to avoid fragmentation of the packet. - static constexpr size_t MAX_PACKET_SIZE = 1390; // MTU + static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU bool schedule_batch_(); void process_batch_(); @@ -636,9 +641,9 @@ class APIConnection : public APIServerConnection { #ifdef HAS_PROTO_MESSAGE_DUMP // Helper to log a proto message from a MessageCreator object - void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) { + void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) { this->flags_.log_only_mode = true; - creator(entity, this, MAX_PACKET_SIZE, true, message_type); + creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type); this->flags_.log_only_mode = false; } @@ -649,7 +654,8 @@ class APIConnection : public APIServerConnection { #endif // Helper method to send a message either immediately or via batching - bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) { + bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, + uint8_t estimated_size) { // Try to send immediately if: // 1. We should try to send immediately (should_try_send_immediately = true) // 2. Batch delay is 0 (user has opted in to immediate sending) @@ -657,7 +663,7 @@ class APIConnection : public APIServerConnection { if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 && this->helper_->can_write_without_blocking()) { // Now actually encode and send - if (creator(entity, this, MAX_PACKET_SIZE, true) && + if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { #ifdef HAS_PROTO_MESSAGE_DUMP // Log the message in verbose mode @@ -670,23 +676,25 @@ class APIConnection : public APIServerConnection { } // Fall back to scheduled batching - return this->schedule_message_(entity, creator, message_type); + return this->schedule_message_(entity, creator, message_type, estimated_size); } // Helper function to schedule a deferred message with known message type - bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { - this->deferred_batch_.add_item(entity, std::move(creator), message_type); + bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { + this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); return this->schedule_batch_(); } // Overload for function pointers (for info messages and current state reads) - bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { - return schedule_message_(entity, MessageCreator(function_ptr), message_type); + bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, + uint8_t estimated_size) { + return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size); } // Helper function to schedule a high priority message at the front of the batch - bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { - this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type); + bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, + uint8_t estimated_size) { + this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size); return this->schedule_batch_(); } }; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 6ed9c95354..afd64e8981 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -5,7 +5,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "proto.h" -#include "api_pb2_size.h" #include #include @@ -225,6 +224,22 @@ APIError APIFrameHelper::init_common_() { } #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__) + +APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; + } + return APIError::OK; +} // uncomment to log raw packets //#define HELPER_LOG_PACKETS @@ -327,17 +342,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // no header information yet uint8_t to_read = 3 - rx_header_buf_len_; ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); - if (received == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - return APIError::WOULD_BLOCK; - } - state_ = State::FAILED; - HELPER_LOG("Socket read failed with errno %d", errno); - return APIError::SOCKET_READ_FAILED; - } else if (received == 0) { - state_ = State::FAILED; - HELPER_LOG("Connection closed"); - return APIError::CONNECTION_CLOSED; + APIError err = handle_socket_read_result_(received); + if (err != APIError::OK) { + return err; } rx_header_buf_len_ += static_cast(received); if (static_cast(received) != to_read) { @@ -372,17 +379,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read uint16_t to_read = msg_size - rx_buf_len_; ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (received == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - return APIError::WOULD_BLOCK; - } - state_ = State::FAILED; - HELPER_LOG("Socket read failed with errno %d", errno); - return APIError::SOCKET_READ_FAILED; - } else if (received == 0) { - state_ = State::FAILED; - HELPER_LOG("Connection closed"); - return APIError::CONNECTION_CLOSED; + APIError err = handle_socket_read_result_(received); + if (err != APIError::OK) { + return err; } rx_buf_len_ += static_cast(received); if (static_cast(received) != to_read) { @@ -613,7 +612,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { buffer->type = type; return APIError::OK; } -APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { +APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { // Resize to include MAC space (required for Noise encryption) buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); PacketInfo packet{type, 0, @@ -855,17 +854,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1); - if (received == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - return APIError::WOULD_BLOCK; - } - state_ = State::FAILED; - HELPER_LOG("Socket read failed with errno %d", errno); - return APIError::SOCKET_READ_FAILED; - } else if (received == 0) { - state_ = State::FAILED; - HELPER_LOG("Connection closed"); - return APIError::CONNECTION_CLOSED; + APIError err = handle_socket_read_result_(received); + if (err != APIError::OK) { + return err; } // If this was the first read, validate the indicator byte @@ -949,17 +940,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_; ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (received == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - return APIError::WOULD_BLOCK; - } - state_ = State::FAILED; - HELPER_LOG("Socket read failed with errno %d", errno); - return APIError::SOCKET_READ_FAILED; - } else if (received == 0) { - state_ = State::FAILED; - HELPER_LOG("Connection closed"); - return APIError::CONNECTION_CLOSED; + APIError err = handle_socket_read_result_(received); + if (err != APIError::OK) { + return err; } rx_buf_len_ += static_cast(received); if (static_cast(received) != to_read) { @@ -1018,7 +1001,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { buffer->type = rx_header_parsed_type_; return APIError::OK; } -APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { +APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { PacketInfo packet{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; return write_protobuf_packets(buffer, std::span(&packet, 1)); } diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 1bb6bc7ed3..4bcc4acd61 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -30,13 +30,11 @@ struct ReadPacketBuffer { // Packed packet info structure to minimize memory usage struct PacketInfo { - uint16_t message_type; // 2 bytes - uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes) - uint16_t payload_size; // 2 bytes (up to 65535 bytes) - uint16_t padding; // 2 byte (for alignment) + uint16_t offset; // Offset in buffer where message starts + uint16_t payload_size; // Size of the message payload + uint8_t message_type; // Message type (0-255) - PacketInfo(uint16_t type, uint16_t off, uint16_t size) - : message_type(type), offset(off), payload_size(size), padding(0) {} + PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} }; enum class APIError : uint16_t { @@ -98,7 +96,7 @@ class APIFrameHelper { } // Give this helper a name for logging void set_log_info(std::string info) { info_ = std::move(info); } - virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0; + virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; // Write multiple protobuf packets in a single operation // packets contains (message_type, offset, length) for each message in the buffer // The buffer contains all messages with appropriate padding before each @@ -176,6 +174,9 @@ class APIFrameHelper { // Common initialization for both plaintext and noise protocols APIError init_common_(); + + // Helper method to handle socket read results + APIError handle_socket_read_result_(ssize_t received); }; #ifdef USE_API_NOISE @@ -194,7 +195,7 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; - APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; + APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; // Get the frame header padding required by this protocol uint8_t frame_header_padding() override { return frame_header_padding_; } @@ -248,7 +249,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError init() override; APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; - APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; + APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; uint8_t frame_header_padding() override { return frame_header_padding_; } // Get the frame footer size required by this protocol diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f3d808295a..c80d75fe38 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1,7 +1,6 @@ // This file was automatically generated with a tool. // See script/api_protobuf/api_protobuf.py #include "api_pb2.h" -#include "api_pb2_size.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -32,44 +31,6 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) return false; } } -void HelloRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->client_info); - buffer.encode_uint32(2, this->api_version_major); - buffer.encode_uint32(3, this->api_version_minor); -} -void HelloRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->client_info, false); - ProtoSize::add_uint32_field(total_size, 1, this->api_version_major, false); - ProtoSize::add_uint32_field(total_size, 1, this->api_version_minor, false); -} -bool HelloResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->api_version_major = value.as_uint32(); - return true; - } - case 2: { - this->api_version_minor = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool HelloResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 3: { - this->server_info = value.as_string(); - return true; - } - case 4: { - this->name = value.as_string(); - return true; - } - default: - return false; - } -} void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->api_version_major); buffer.encode_uint32(2, this->api_version_minor); @@ -77,10 +38,10 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->name); } void HelloResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->api_version_major, false); - ProtoSize::add_uint32_field(total_size, 1, this->api_version_minor, false); - ProtoSize::add_string_field(total_size, 1, this->server_info, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); + ProtoSize::add_uint32_field(total_size, 1, this->api_version_major); + ProtoSize::add_uint32_field(total_size, 1, this->api_version_minor); + ProtoSize::add_string_field(total_size, 1, this->server_info); + ProtoSize::add_string_field(total_size, 1, this->name); } bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -92,23 +53,9 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value return false; } } -void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } -void ConnectRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->password, false); -} -bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->invalid_password = value.as_bool(); - return true; - } - default: - return false; - } -} void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } void ConnectResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->invalid_password, false); + ProtoSize::add_bool_field(total_size, 1, this->invalid_password); } bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -135,8 +82,8 @@ void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->name); } void AreaInfo::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); + ProtoSize::add_uint32_field(total_size, 1, this->area_id); + ProtoSize::add_string_field(total_size, 1, this->name); } bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -168,109 +115,9 @@ void DeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->area_id); } void DeviceInfo::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); -} -bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->uses_password = value.as_bool(); - return true; - } - case 7: { - this->has_deep_sleep = value.as_bool(); - return true; - } - case 10: { - this->webserver_port = value.as_uint32(); - return true; - } - case 11: { - this->legacy_bluetooth_proxy_version = value.as_uint32(); - return true; - } - case 15: { - this->bluetooth_proxy_feature_flags = value.as_uint32(); - return true; - } - case 14: { - this->legacy_voice_assistant_version = value.as_uint32(); - return true; - } - case 17: { - this->voice_assistant_feature_flags = value.as_uint32(); - return true; - } - case 19: { - this->api_encryption_supported = value.as_bool(); - return true; - } - default: - return false; - } -} -bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->name = value.as_string(); - return true; - } - case 3: { - this->mac_address = value.as_string(); - return true; - } - case 4: { - this->esphome_version = value.as_string(); - return true; - } - case 5: { - this->compilation_time = value.as_string(); - return true; - } - case 6: { - this->model = value.as_string(); - return true; - } - case 8: { - this->project_name = value.as_string(); - return true; - } - case 9: { - this->project_version = value.as_string(); - return true; - } - case 12: { - this->manufacturer = value.as_string(); - return true; - } - case 13: { - this->friendly_name = value.as_string(); - return true; - } - case 16: { - this->suggested_area = value.as_string(); - return true; - } - case 18: { - this->bluetooth_mac_address = value.as_string(); - return true; - } - case 20: { - this->devices.push_back(value.as_message()); - return true; - } - case 21: { - this->areas.push_back(value.as_message()); - return true; - } - case 22: { - this->area = value.as_message(); - return true; - } - default: - return false; - } + ProtoSize::add_uint32_field(total_size, 1, this->device_id); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_uint32_field(total_size, 1, this->area_id); } void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->uses_password); @@ -293,92 +140,38 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(18, this->bluetooth_mac_address); buffer.encode_bool(19, this->api_encryption_supported); for (auto &it : this->devices) { - buffer.encode_message(20, it, true); + buffer.encode_message(20, it, true); } for (auto &it : this->areas) { - buffer.encode_message(21, it, true); + buffer.encode_message(21, it, true); } - buffer.encode_message(22, this->area); + buffer.encode_message(22, this->area); } void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->mac_address, false); - ProtoSize::add_string_field(total_size, 1, this->esphome_version, false); - ProtoSize::add_string_field(total_size, 1, this->compilation_time, false); - ProtoSize::add_string_field(total_size, 1, this->model, false); - ProtoSize::add_bool_field(total_size, 1, this->has_deep_sleep, false); - ProtoSize::add_string_field(total_size, 1, this->project_name, false); - ProtoSize::add_string_field(total_size, 1, this->project_version, false); - ProtoSize::add_uint32_field(total_size, 1, this->webserver_port, false); - ProtoSize::add_uint32_field(total_size, 1, this->legacy_bluetooth_proxy_version, false); - ProtoSize::add_uint32_field(total_size, 1, this->bluetooth_proxy_feature_flags, false); - ProtoSize::add_string_field(total_size, 1, this->manufacturer, false); - ProtoSize::add_string_field(total_size, 1, this->friendly_name, false); - ProtoSize::add_uint32_field(total_size, 1, this->legacy_voice_assistant_version, false); - ProtoSize::add_uint32_field(total_size, 2, this->voice_assistant_feature_flags, false); - ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); - ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); - ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); + ProtoSize::add_bool_field(total_size, 1, this->uses_password); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->mac_address); + ProtoSize::add_string_field(total_size, 1, this->esphome_version); + ProtoSize::add_string_field(total_size, 1, this->compilation_time); + ProtoSize::add_string_field(total_size, 1, this->model); + ProtoSize::add_bool_field(total_size, 1, this->has_deep_sleep); + ProtoSize::add_string_field(total_size, 1, this->project_name); + ProtoSize::add_string_field(total_size, 1, this->project_version); + ProtoSize::add_uint32_field(total_size, 1, this->webserver_port); + ProtoSize::add_uint32_field(total_size, 1, this->legacy_bluetooth_proxy_version); + ProtoSize::add_uint32_field(total_size, 1, this->bluetooth_proxy_feature_flags); + ProtoSize::add_string_field(total_size, 1, this->manufacturer); + ProtoSize::add_string_field(total_size, 1, this->friendly_name); + ProtoSize::add_uint32_field(total_size, 1, this->legacy_voice_assistant_version); + ProtoSize::add_uint32_field(total_size, 2, this->voice_assistant_feature_flags); + ProtoSize::add_string_field(total_size, 2, this->suggested_area); + ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address); + ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported); ProtoSize::add_repeated_message(total_size, 2, this->devices); ProtoSize::add_repeated_message(total_size, 2, this->areas); - ProtoSize::add_message_object(total_size, 2, this->area, false); + ProtoSize::add_message_object(total_size, 2, this->area); } #ifdef USE_BINARY_SENSOR -bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->is_status_binary_sensor = value.as_bool(); - return true; - } - case 7: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 9: { - this->entity_category = value.as_enum(); - return true; - } - case 10: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->device_class = value.as_string(); - return true; - } - case 8: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesBinarySensorResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -387,47 +180,19 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); - buffer.encode_enum(9, this->entity_category); + buffer.encode_uint32(9, static_cast(this->entity_category)); buffer.encode_uint32(10, this->device_id); } void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_bool_field(total_size, 1, this->is_status_binary_sensor, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_bool(); - return true; - } - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool BinarySensorStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_bool_field(total_size, 1, this->is_status_binary_sensor); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -436,79 +201,13 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void BinarySensorStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->state); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } #endif #ifdef USE_COVER -bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 5: { - this->assumed_state = value.as_bool(); - return true; - } - case 6: { - this->supports_position = value.as_bool(); - return true; - } - case 7: { - this->supports_tilt = value.as_bool(); - return true; - } - case 9: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 11: { - this->entity_category = value.as_enum(); - return true; - } - case 12: { - this->supports_stop = value.as_bool(); - return true; - } - case 13: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - case 10: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesCoverResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -519,75 +218,39 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); - buffer.encode_enum(11, this->entity_category); + buffer.encode_uint32(11, static_cast(this->entity_category)); buffer.encode_bool(12, this->supports_stop); buffer.encode_uint32(13, this->device_id); } void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_tilt, false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->legacy_state = value.as_enum(); - return true; - } - case 5: { - this->current_operation = value.as_enum(); - return true; - } - case 6: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool CoverStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 3: { - this->position = value.as_float(); - return true; - } - case 4: { - this->tilt = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_bool_field(total_size, 1, this->assumed_state); + ProtoSize::add_bool_field(total_size, 1, this->supports_position); + ProtoSize::add_bool_field(total_size, 1, this->supports_tilt); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_bool_field(total_size, 1, this->supports_stop); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->legacy_state); + buffer.encode_uint32(2, static_cast(this->legacy_state)); buffer.encode_float(3, this->position); buffer.encode_float(4, this->tilt); - buffer.encode_enum(5, this->current_operation); + buffer.encode_uint32(5, static_cast(this->current_operation)); buffer.encode_uint32(6, this->device_id); } void CoverStateResponse::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(this->legacy_state), false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->legacy_state)); + ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -596,7 +259,7 @@ bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 3: { - this->legacy_command = value.as_enum(); + this->legacy_command = static_cast(value.as_uint32()); return true; } case 4: { @@ -611,6 +274,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; } @@ -633,94 +300,8 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_legacy_command); - buffer.encode_enum(3, this->legacy_command); - buffer.encode_bool(4, this->has_position); - buffer.encode_float(5, this->position); - buffer.encode_bool(6, this->has_tilt); - buffer.encode_float(7, this->tilt); - buffer.encode_bool(8, this->stop); -} -void CoverCommandRequest::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_legacy_command, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->legacy_command), 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->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); -} #endif #ifdef USE_FAN -bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 5: { - this->supports_oscillation = value.as_bool(); - return true; - } - case 6: { - this->supports_speed = value.as_bool(); - return true; - } - case 7: { - this->supports_direction = value.as_bool(); - return true; - } - case 8: { - this->supported_speed_count = value.as_int32(); - return true; - } - case 9: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 11: { - this->entity_category = value.as_enum(); - return true; - } - case 13: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 10: { - this->icon = value.as_string(); - return true; - } - case 12: { - this->supported_preset_modes.push_back(value.as_string()); - return true; - } - default: - return false; - } -} -bool ListEntitiesFanResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -731,99 +312,49 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); - buffer.encode_enum(11, this->entity_category); + buffer.encode_uint32(11, static_cast(this->entity_category)); for (auto &it : this->supported_preset_modes) { buffer.encode_string(12, it, true); } buffer.encode_uint32(13, this->device_id); } void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_oscillation, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_speed, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_direction, false); - ProtoSize::add_int32_field(total_size, 1, this->supported_speed_count, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_bool_field(total_size, 1, this->supports_oscillation); + ProtoSize::add_bool_field(total_size, 1, this->supports_speed); + ProtoSize::add_bool_field(total_size, 1, this->supports_direction); + ProtoSize::add_int32_field(total_size, 1, this->supported_speed_count); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); if (!this->supported_preset_modes.empty()) { for (const auto &it : this->supported_preset_modes) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_bool(); - return true; - } - case 3: { - this->oscillating = value.as_bool(); - return true; - } - case 4: { - this->speed = value.as_enum(); - return true; - } - case 5: { - this->direction = value.as_enum(); - return true; - } - case 6: { - this->speed_level = value.as_int32(); - return true; - } - case 8: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool FanStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 7: { - this->preset_mode = value.as_string(); - return true; - } - default: - return false; - } -} -bool FanStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); buffer.encode_bool(3, this->oscillating); - buffer.encode_enum(4, this->speed); - buffer.encode_enum(5, this->direction); + buffer.encode_uint32(4, static_cast(this->speed)); + buffer.encode_uint32(5, static_cast(this->direction)); buffer.encode_int32(6, this->speed_level); buffer.encode_string(7, this->preset_mode); buffer.encode_uint32(8, this->device_id); } void FanStateResponse::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_bool_field(total_size, 1, this->oscillating, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->speed), false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->direction), false); - ProtoSize::add_int32_field(total_size, 1, this->speed_level, false); - ProtoSize::add_string_field(total_size, 1, this->preset_mode, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->state); + ProtoSize::add_bool_field(total_size, 1, this->oscillating); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->speed)); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->direction)); + ProtoSize::add_int32_field(total_size, 1, this->speed_level); + ProtoSize::add_string_field(total_size, 1, this->preset_mode); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -840,7 +371,7 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 5: { - this->speed = value.as_enum(); + this->speed = static_cast(value.as_uint32()); return true; } case 6: { @@ -856,7 +387,7 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 9: { - this->direction = value.as_enum(); + this->direction = static_cast(value.as_uint32()); return true; } case 10: { @@ -871,6 +402,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; } @@ -895,122 +430,14 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_state); - buffer.encode_bool(3, this->state); - buffer.encode_bool(4, this->has_speed); - buffer.encode_enum(5, this->speed); - buffer.encode_bool(6, this->has_oscillating); - buffer.encode_bool(7, this->oscillating); - buffer.encode_bool(8, this->has_direction); - buffer.encode_enum(9, this->direction); - buffer.encode_bool(10, this->has_speed_level); - buffer.encode_int32(11, this->speed_level); - buffer.encode_bool(12, this->has_preset_mode); - buffer.encode_string(13, this->preset_mode); -} -void FanCommandRequest::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_state, false); - ProtoSize::add_bool_field(total_size, 1, this->state, false); - ProtoSize::add_bool_field(total_size, 1, this->has_speed, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->speed), false); - ProtoSize::add_bool_field(total_size, 1, this->has_oscillating, false); - ProtoSize::add_bool_field(total_size, 1, this->oscillating, false); - ProtoSize::add_bool_field(total_size, 1, this->has_direction, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->direction), false); - ProtoSize::add_bool_field(total_size, 1, this->has_speed_level, false); - 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); -} #endif #ifdef USE_LIGHT -bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 12: { - this->supported_color_modes.push_back(value.as_enum()); - return true; - } - case 5: { - this->legacy_supports_brightness = value.as_bool(); - return true; - } - case 6: { - this->legacy_supports_rgb = value.as_bool(); - return true; - } - case 7: { - this->legacy_supports_white_value = value.as_bool(); - return true; - } - case 8: { - this->legacy_supports_color_temperature = value.as_bool(); - return true; - } - case 13: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 15: { - this->entity_category = value.as_enum(); - return true; - } - case 16: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 11: { - this->effects.push_back(value.as_string()); - return true; - } - case 14: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesLightResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - case 9: { - this->min_mireds = value.as_float(); - return true; - } - case 10: { - this->max_mireds = value.as_float(); - return true; - } - default: - return false; - } -} void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); for (auto &it : this->supported_color_modes) { - buffer.encode_enum(12, it, true); + buffer.encode_uint32(12, static_cast(it), true); } buffer.encode_bool(5, this->legacy_supports_brightness); buffer.encode_bool(6, this->legacy_supports_rgb); @@ -1023,113 +450,39 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); - buffer.encode_enum(15, this->entity_category); + buffer.encode_uint32(15, static_cast(this->entity_category)); buffer.encode_uint32(16, this->device_id); } void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); if (!this->supported_color_modes.empty()) { for (const auto &it : this->supported_color_modes) { - ProtoSize::add_enum_field(total_size, 1, static_cast(it), true); + ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); } } - ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_brightness, false); - ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_rgb, false); - ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_white_value, false); - ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_color_temperature, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->min_mireds != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->max_mireds != 0.0f, false); + ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_brightness); + ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_rgb); + ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_white_value); + ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_color_temperature); + ProtoSize::add_fixed_field<4>(total_size, 1, this->min_mireds != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->max_mireds != 0.0f); if (!this->effects.empty()) { for (const auto &it : this->effects) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); -} -bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_bool(); - return true; - } - case 11: { - this->color_mode = value.as_enum(); - return true; - } - case 14: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool LightStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 9: { - this->effect = value.as_string(); - return true; - } - default: - return false; - } -} -bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 3: { - this->brightness = value.as_float(); - return true; - } - case 10: { - this->color_brightness = value.as_float(); - return true; - } - case 4: { - this->red = value.as_float(); - return true; - } - case 5: { - this->green = value.as_float(); - return true; - } - case 6: { - this->blue = value.as_float(); - return true; - } - case 7: { - this->white = value.as_float(); - return true; - } - case 8: { - this->color_temperature = value.as_float(); - return true; - } - case 12: { - this->cold_white = value.as_float(); - return true; - } - case 13: { - this->warm_white = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 2, this->device_id); } void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bool(2, this->state); buffer.encode_float(3, this->brightness); - buffer.encode_enum(11, this->color_mode); + buffer.encode_uint32(11, static_cast(this->color_mode)); buffer.encode_float(10, this->color_brightness); buffer.encode_float(4, this->red); buffer.encode_float(5, this->green); @@ -1142,20 +495,20 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(14, this->device_id); } void LightStateResponse::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_fixed_field<4>(total_size, 1, this->brightness != 0.0f, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->color_mode), false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->color_brightness != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->red != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->green != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->blue != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->white != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->color_temperature != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->cold_white != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->warm_white != 0.0f, false); - ProtoSize::add_string_field(total_size, 1, this->effect, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->state); + ProtoSize::add_fixed_field<4>(total_size, 1, this->brightness != 0.0f); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->color_mode)); + ProtoSize::add_fixed_field<4>(total_size, 1, this->color_brightness != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->red != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->green != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->blue != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->white != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->color_temperature != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->cold_white != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->warm_white != 0.0f); + ProtoSize::add_string_field(total_size, 1, this->effect); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1176,7 +529,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 23: { - this->color_mode = value.as_enum(); + this->color_mode = static_cast(value.as_uint32()); return true; } case 20: { @@ -1223,6 +576,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; } @@ -1283,136 +640,8 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_state); - buffer.encode_bool(3, this->state); - buffer.encode_bool(4, this->has_brightness); - buffer.encode_float(5, this->brightness); - buffer.encode_bool(22, this->has_color_mode); - buffer.encode_enum(23, this->color_mode); - buffer.encode_bool(20, this->has_color_brightness); - buffer.encode_float(21, this->color_brightness); - buffer.encode_bool(6, this->has_rgb); - buffer.encode_float(7, this->red); - buffer.encode_float(8, this->green); - buffer.encode_float(9, this->blue); - buffer.encode_bool(10, this->has_white); - buffer.encode_float(11, this->white); - buffer.encode_bool(12, this->has_color_temperature); - buffer.encode_float(13, this->color_temperature); - buffer.encode_bool(24, this->has_cold_white); - buffer.encode_float(25, this->cold_white); - buffer.encode_bool(26, this->has_warm_white); - buffer.encode_float(27, this->warm_white); - buffer.encode_bool(14, this->has_transition_length); - buffer.encode_uint32(15, this->transition_length); - buffer.encode_bool(16, this->has_flash_length); - buffer.encode_uint32(17, this->flash_length); - buffer.encode_bool(18, this->has_effect); - buffer.encode_string(19, this->effect); -} -void LightCommandRequest::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_state, false); - ProtoSize::add_bool_field(total_size, 1, this->state, false); - ProtoSize::add_bool_field(total_size, 1, this->has_brightness, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->brightness != 0.0f, false); - ProtoSize::add_bool_field(total_size, 2, this->has_color_mode, false); - ProtoSize::add_enum_field(total_size, 2, static_cast(this->color_mode), false); - ProtoSize::add_bool_field(total_size, 2, this->has_color_brightness, false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->color_brightness != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_rgb, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->red != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->green != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->blue != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_white, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->white != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_color_temperature, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->color_temperature != 0.0f, false); - ProtoSize::add_bool_field(total_size, 2, this->has_cold_white, false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->cold_white != 0.0f, false); - ProtoSize::add_bool_field(total_size, 2, this->has_warm_white, false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->warm_white != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_transition_length, false); - ProtoSize::add_uint32_field(total_size, 1, this->transition_length, false); - ProtoSize::add_bool_field(total_size, 2, this->has_flash_length, false); - 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); -} #endif #ifdef USE_SENSOR -bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 7: { - this->accuracy_decimals = value.as_int32(); - return true; - } - case 8: { - this->force_update = value.as_bool(); - return true; - } - case 10: { - this->state_class = value.as_enum(); - return true; - } - case 11: { - this->legacy_last_reset_type = value.as_enum(); - return true; - } - case 12: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 13: { - this->entity_category = value.as_enum(); - return true; - } - case 14: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 6: { - this->unit_of_measurement = value.as_string(); - return true; - } - case 9: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSensorResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -1422,54 +651,26 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(7, this->accuracy_decimals); buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); - buffer.encode_enum(10, this->state_class); - buffer.encode_enum(11, this->legacy_last_reset_type); + buffer.encode_uint32(10, static_cast(this->state_class)); + buffer.encode_uint32(11, static_cast(this->legacy_last_reset_type)); buffer.encode_bool(12, this->disabled_by_default); - buffer.encode_enum(13, this->entity_category); + buffer.encode_uint32(13, static_cast(this->entity_category)); buffer.encode_uint32(14, this->device_id); } void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); - ProtoSize::add_int32_field(total_size, 1, this->accuracy_decimals, false); - ProtoSize::add_bool_field(total_size, 1, this->force_update, false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state_class), false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->legacy_last_reset_type), false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool SensorStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 2: { - this->state = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement); + ProtoSize::add_int32_field(total_size, 1, this->accuracy_decimals); + ProtoSize::add_bool_field(total_size, 1, this->force_update); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->state_class)); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->legacy_last_reset_type)); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -1478,67 +679,13 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void SensorStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } #endif #ifdef USE_SWITCH -bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->assumed_state = value.as_bool(); - return true; - } - case 7: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 8: { - this->entity_category = value.as_enum(); - return true; - } - case 10: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 9: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSwitchResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -1546,44 +693,20 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); - buffer.encode_enum(8, this->entity_category); + buffer.encode_uint32(8, static_cast(this->entity_category)); buffer.encode_string(9, this->device_class); buffer.encode_uint32(10, this->device_id); } void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_bool(); - return true; - } - case 3: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool SwitchStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->assumed_state); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -1591,9 +714,9 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); } void SwitchStateResponse::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); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1601,6 +724,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; } @@ -1615,119 +742,27 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); -} -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); -} #endif #ifdef USE_TEXT_SENSOR -bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 9: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTextSensorResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class); buffer.encode_uint32(9, this->device_id); } void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool TextSensorStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->state = value.as_string(); - return true; - } - default: - return false; - } -} -bool TextSensorStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -1736,16 +771,16 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void TextSensorStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->state); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } #endif bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { - this->level = value.as_enum(); + this->level = static_cast(value.as_uint32()); return true; } case 2: { @@ -1756,47 +791,15 @@ bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } } -void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->level); - buffer.encode_bool(2, this->dump_config); -} -void SubscribeLogsRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->level), false); - ProtoSize::add_bool_field(total_size, 1, this->dump_config, false); -} -bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->level = value.as_enum(); - return true; - } - case 4: { - this->send_failed = value.as_bool(); - return true; - } - default: - return false; - } -} -bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 3: { - this->message = value.as_string(); - return true; - } - default: - return false; - } -} void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->level); + buffer.encode_uint32(1, static_cast(this->level)); buffer.encode_bytes(3, reinterpret_cast(this->message.data()), this->message.size()); buffer.encode_bool(4, this->send_failed); } void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->level), false); - ProtoSize::add_string_field(total_size, 1, this->message, false); - ProtoSize::add_bool_field(total_size, 1, this->send_failed, false); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->level)); + ProtoSize::add_string_field(total_size, 1, this->message); + ProtoSize::add_bool_field(total_size, 1, this->send_failed); } #ifdef USE_API_NOISE bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { @@ -1809,25 +812,9 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD return false; } } -void NoiseEncryptionSetKeyRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bytes(1, reinterpret_cast(this->key.data()), this->key.size()); -} -void NoiseEncryptionSetKeyRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->key, false); -} -bool NoiseEncryptionSetKeyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->success = value.as_bool(); - return true; - } - default: - return false; - } -} void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->success, false); + ProtoSize::add_bool_field(total_size, 1, this->success); } #endif bool HomeassistantServiceMap::decode_length(uint32_t field_id, ProtoLengthDelimited value) { @@ -1849,84 +836,28 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->value); } void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->key, false); - ProtoSize::add_string_field(total_size, 1, this->value, false); -} -bool HomeassistantServiceResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 5: { - this->is_event = value.as_bool(); - return true; - } - default: - return false; - } -} -bool HomeassistantServiceResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->service = value.as_string(); - return true; - } - case 2: { - this->data.push_back(value.as_message()); - return true; - } - case 3: { - this->data_template.push_back(value.as_message()); - return true; - } - case 4: { - this->variables.push_back(value.as_message()); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->key); + ProtoSize::add_string_field(total_size, 1, this->value); } void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->service); for (auto &it : this->data) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it, true); } for (auto &it : this->data_template) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it, true); } for (auto &it : this->variables) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it, true); } buffer.encode_bool(5, this->is_event); } void HomeassistantServiceResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->service, false); + ProtoSize::add_string_field(total_size, 1, this->service); ProtoSize::add_repeated_message(total_size, 1, this->data); ProtoSize::add_repeated_message(total_size, 1, this->data_template); ProtoSize::add_repeated_message(total_size, 1, this->variables); - ProtoSize::add_bool_field(total_size, 1, this->is_event, false); -} -bool SubscribeHomeAssistantStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->once = value.as_bool(); - return true; - } - default: - return false; - } -} -bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->entity_id = value.as_string(); - return true; - } - case 2: { - this->attribute = value.as_string(); - return true; - } - default: - return false; - } + ProtoSize::add_bool_field(total_size, 1, this->is_event); } void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->entity_id); @@ -1934,9 +865,9 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const buffer.encode_bool(3, this->once); } void SubscribeHomeAssistantStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->entity_id, false); - ProtoSize::add_string_field(total_size, 1, this->attribute, false); - ProtoSize::add_bool_field(total_size, 1, this->once, false); + ProtoSize::add_string_field(total_size, 1, this->entity_id); + ProtoSize::add_string_field(total_size, 1, this->attribute); + ProtoSize::add_bool_field(total_size, 1, this->once); } bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -1956,16 +887,6 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel return false; } } -void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->entity_id); - buffer.encode_string(2, this->state); - buffer.encode_string(3, this->attribute); -} -void HomeAssistantStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->entity_id, false); - ProtoSize::add_string_field(total_size, 1, this->state, false); - ProtoSize::add_string_field(total_size, 1, this->attribute, false); -} bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { switch (field_id) { case 1: { @@ -1978,12 +899,13 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { } void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } void GetTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0); } +#ifdef USE_API_SERVICES bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->type = value.as_enum(); + this->type = static_cast(value.as_uint32()); return true; } default: @@ -2002,46 +924,22 @@ bool ListEntitiesServicesArgument::decode_length(uint32_t field_id, ProtoLengthD } void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name); - buffer.encode_enum(2, this->type); + buffer.encode_uint32(2, static_cast(this->type)); } void ListEntitiesServicesArgument::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->type), false); -} -bool ListEntitiesServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->name = value.as_string(); - return true; - } - case 3: { - this->args.push_back(value.as_message()); - return true; - } - default: - return false; - } -} -bool ListEntitiesServicesResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->type)); } void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name); buffer.encode_fixed32(2, this->key); for (auto &it : this->args) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it, true); } } void ListEntitiesServicesResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); ProtoSize::add_repeated_message(total_size, 1, this->args); } bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2118,36 +1016,35 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { } } void ExecuteServiceArgument::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->bool_, false); - ProtoSize::add_int32_field(total_size, 1, this->legacy_int, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->float_ != 0.0f, false); - ProtoSize::add_string_field(total_size, 1, this->string_, false); - ProtoSize::add_sint32_field(total_size, 1, this->int_, false); + ProtoSize::add_bool_field(total_size, 1, this->bool_); + ProtoSize::add_int32_field(total_size, 1, this->legacy_int); + ProtoSize::add_fixed_field<4>(total_size, 1, this->float_ != 0.0f); + ProtoSize::add_string_field(total_size, 1, this->string_); + ProtoSize::add_sint32_field(total_size, 1, this->int_); if (!this->bool_array.empty()) { for (const auto it : this->bool_array) { - ProtoSize::add_bool_field(total_size, 1, it, true); + ProtoSize::add_bool_field_repeated(total_size, 1, it); } } if (!this->int_array.empty()) { for (const auto &it : this->int_array) { - ProtoSize::add_sint32_field(total_size, 1, it, true); + ProtoSize::add_sint32_field_repeated(total_size, 1, it); } } if (!this->float_array.empty()) { - for (const auto &it : this->float_array) { - ProtoSize::add_fixed_field<4>(total_size, 1, it != 0.0f, true); - } + total_size += this->float_array.size() * 5; } if (!this->string_array.empty()) { for (const auto &it : this->string_array) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } } bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - this->args.push_back(value.as_message()); + this->args.emplace_back(); + value.decode_to_message(this->args.back()); return true; } default: @@ -2164,120 +1061,37 @@ bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - for (auto &it : this->args) { - buffer.encode_message(2, it, true); - } -} -void ExecuteServiceRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_repeated_message(total_size, 1, this->args); -} +#endif #ifdef USE_CAMERA -bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 5: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 6: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesCameraResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->device_id); } void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->done = value.as_bool(); - return true; - } - default: - return false; - } -} -bool CameraImageResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->data = value.as_string(); - return true; - } - default: - return false; - } -} -bool CameraImageResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_bytes(2, reinterpret_cast(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_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bool_field(total_size, 1, this->done); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2293,134 +1107,8 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { return false; } } -void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bool(1, this->single); - buffer.encode_bool(2, this->stream); -} -void CameraImageRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->single, false); - ProtoSize::add_bool_field(total_size, 1, this->stream, false); -} #endif #ifdef USE_CLIMATE -bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 5: { - this->supports_current_temperature = value.as_bool(); - return true; - } - case 6: { - this->supports_two_point_target_temperature = value.as_bool(); - return true; - } - case 7: { - this->supported_modes.push_back(value.as_enum()); - return true; - } - case 11: { - this->legacy_supports_away = value.as_bool(); - return true; - } - case 12: { - this->supports_action = value.as_bool(); - return true; - } - case 13: { - this->supported_fan_modes.push_back(value.as_enum()); - return true; - } - case 14: { - this->supported_swing_modes.push_back(value.as_enum()); - return true; - } - case 16: { - this->supported_presets.push_back(value.as_enum()); - return true; - } - case 18: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 20: { - this->entity_category = value.as_enum(); - return true; - } - case 22: { - this->supports_current_humidity = value.as_bool(); - return true; - } - case 23: { - this->supports_target_humidity = value.as_bool(); - return true; - } - case 26: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 15: { - this->supported_custom_fan_modes.push_back(value.as_string()); - return true; - } - case 17: { - this->supported_custom_presets.push_back(value.as_string()); - return true; - } - case 19: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - case 8: { - this->visual_min_temperature = value.as_float(); - return true; - } - case 9: { - this->visual_max_temperature = value.as_float(); - return true; - } - case 10: { - this->visual_target_temperature_step = value.as_float(); - return true; - } - case 21: { - this->visual_current_temperature_step = value.as_float(); - return true; - } - case 24: { - this->visual_min_humidity = value.as_float(); - return true; - } - case 25: { - this->visual_max_humidity = value.as_float(); - return true; - } - default: - return false; - } -} void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -2428,7 +1116,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->supports_current_temperature); buffer.encode_bool(6, this->supports_two_point_target_temperature); for (auto &it : this->supported_modes) { - buffer.encode_enum(7, it, true); + buffer.encode_uint32(7, static_cast(it), true); } buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); @@ -2436,23 +1124,23 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(12, this->supports_action); for (auto &it : this->supported_fan_modes) { - buffer.encode_enum(13, it, true); + buffer.encode_uint32(13, static_cast(it), true); } for (auto &it : this->supported_swing_modes) { - buffer.encode_enum(14, it, true); + buffer.encode_uint32(14, static_cast(it), true); } for (auto &it : this->supported_custom_fan_modes) { buffer.encode_string(15, it, true); } for (auto &it : this->supported_presets) { - buffer.encode_enum(16, it, true); + buffer.encode_uint32(16, static_cast(it), true); } for (auto &it : this->supported_custom_presets) { buffer.encode_string(17, it, true); } buffer.encode_bool(18, this->disabled_by_default); buffer.encode_string(19, this->icon); - buffer.encode_enum(20, this->entity_category); + buffer.encode_uint32(20, static_cast(this->entity_category)); buffer.encode_float(21, this->visual_current_temperature_step); buffer.encode_bool(22, this->supports_current_humidity); buffer.encode_bool(23, this->supports_target_humidity); @@ -2461,173 +1149,91 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(26, this->device_id); } void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_current_temperature, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_two_point_target_temperature, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_bool_field(total_size, 1, this->supports_current_temperature); + ProtoSize::add_bool_field(total_size, 1, this->supports_two_point_target_temperature); if (!this->supported_modes.empty()) { for (const auto &it : this->supported_modes) { - ProtoSize::add_enum_field(total_size, 1, static_cast(it), true); + ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); } } - ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_min_temperature != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_max_temperature != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_target_temperature_step != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_away, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_action, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_min_temperature != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_max_temperature != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_target_temperature_step != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_away); + ProtoSize::add_bool_field(total_size, 1, this->supports_action); if (!this->supported_fan_modes.empty()) { for (const auto &it : this->supported_fan_modes) { - ProtoSize::add_enum_field(total_size, 1, static_cast(it), true); + ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); } } if (!this->supported_swing_modes.empty()) { for (const auto &it : this->supported_swing_modes) { - ProtoSize::add_enum_field(total_size, 1, static_cast(it), true); + ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); } } if (!this->supported_custom_fan_modes.empty()) { for (const auto &it : this->supported_custom_fan_modes) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } if (!this->supported_presets.empty()) { for (const auto &it : this->supported_presets) { - ProtoSize::add_enum_field(total_size, 2, static_cast(it), true); + ProtoSize::add_enum_field_repeated(total_size, 2, static_cast(it)); } } if (!this->supported_custom_presets.empty()) { for (const auto &it : this->supported_custom_presets) { - ProtoSize::add_string_field(total_size, 2, it, true); + ProtoSize::add_string_field_repeated(total_size, 2, it); } } - ProtoSize::add_bool_field(total_size, 2, this->disabled_by_default, false); - ProtoSize::add_string_field(total_size, 2, this->icon, false); - ProtoSize::add_enum_field(total_size, 2, static_cast(this->entity_category), false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_current_temperature_step != 0.0f, false); - ProtoSize::add_bool_field(total_size, 2, this->supports_current_humidity, false); - ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); - ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); -} -bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->mode = value.as_enum(); - return true; - } - case 7: { - this->unused_legacy_away = value.as_bool(); - return true; - } - case 8: { - this->action = value.as_enum(); - return true; - } - case 9: { - this->fan_mode = value.as_enum(); - return true; - } - case 10: { - this->swing_mode = value.as_enum(); - return true; - } - case 12: { - this->preset = value.as_enum(); - return true; - } - case 16: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ClimateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 11: { - this->custom_fan_mode = value.as_string(); - return true; - } - case 13: { - this->custom_preset = value.as_string(); - return true; - } - default: - return false; - } -} -bool ClimateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 3: { - this->current_temperature = value.as_float(); - return true; - } - case 4: { - this->target_temperature = value.as_float(); - return true; - } - case 5: { - this->target_temperature_low = value.as_float(); - return true; - } - case 6: { - this->target_temperature_high = value.as_float(); - return true; - } - case 14: { - this->current_humidity = value.as_float(); - return true; - } - case 15: { - this->target_humidity = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_bool_field(total_size, 2, this->disabled_by_default); + ProtoSize::add_string_field(total_size, 2, this->icon); + ProtoSize::add_enum_field(total_size, 2, static_cast(this->entity_category)); + ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_current_temperature_step != 0.0f); + ProtoSize::add_bool_field(total_size, 2, this->supports_current_humidity); + ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity); + ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f); + ProtoSize::add_uint32_field(total_size, 2, this->device_id); } void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->mode); + buffer.encode_uint32(2, static_cast(this->mode)); buffer.encode_float(3, this->current_temperature); buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); buffer.encode_bool(7, this->unused_legacy_away); - buffer.encode_enum(8, this->action); - buffer.encode_enum(9, this->fan_mode); - buffer.encode_enum(10, this->swing_mode); + buffer.encode_uint32(8, static_cast(this->action)); + buffer.encode_uint32(9, static_cast(this->fan_mode)); + buffer.encode_uint32(10, static_cast(this->swing_mode)); buffer.encode_string(11, this->custom_fan_mode); - buffer.encode_enum(12, this->preset); + buffer.encode_uint32(12, static_cast(this->preset)); buffer.encode_string(13, this->custom_preset); buffer.encode_float(14, this->current_humidity); buffer.encode_float(15, this->target_humidity); buffer.encode_uint32(16, this->device_id); } void ClimateStateResponse::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(this->mode), false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->current_temperature != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_low != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_high != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->unused_legacy_away, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->action), false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->fan_mode), false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->swing_mode), false); - ProtoSize::add_string_field(total_size, 1, this->custom_fan_mode, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->preset), false); - ProtoSize::add_string_field(total_size, 1, this->custom_preset, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->current_humidity != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_humidity != 0.0f, false); - ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); + ProtoSize::add_fixed_field<4>(total_size, 1, this->current_temperature != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_low != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_high != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->unused_legacy_away); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->action)); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->fan_mode)); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->swing_mode)); + ProtoSize::add_string_field(total_size, 1, this->custom_fan_mode); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->preset)); + ProtoSize::add_string_field(total_size, 1, this->custom_preset); + ProtoSize::add_fixed_field<4>(total_size, 1, this->current_humidity != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->target_humidity != 0.0f); + ProtoSize::add_uint32_field(total_size, 2, this->device_id); } bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2636,7 +1242,7 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 3: { - this->mode = value.as_enum(); + this->mode = static_cast(value.as_uint32()); return true; } case 4: { @@ -2664,7 +1270,7 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 13: { - this->fan_mode = value.as_enum(); + this->fan_mode = static_cast(value.as_uint32()); return true; } case 14: { @@ -2672,7 +1278,7 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 15: { - this->swing_mode = value.as_enum(); + this->swing_mode = static_cast(value.as_uint32()); return true; } case 16: { @@ -2684,7 +1290,7 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 19: { - this->preset = value.as_enum(); + this->preset = static_cast(value.as_uint32()); return true; } case 20: { @@ -2695,6 +1301,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; } @@ -2739,128 +1349,8 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_mode); - buffer.encode_enum(3, this->mode); - buffer.encode_bool(4, this->has_target_temperature); - buffer.encode_float(5, this->target_temperature); - buffer.encode_bool(6, this->has_target_temperature_low); - buffer.encode_float(7, this->target_temperature_low); - buffer.encode_bool(8, this->has_target_temperature_high); - buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->unused_has_legacy_away); - buffer.encode_bool(11, this->unused_legacy_away); - buffer.encode_bool(12, this->has_fan_mode); - buffer.encode_enum(13, this->fan_mode); - buffer.encode_bool(14, this->has_swing_mode); - buffer.encode_enum(15, this->swing_mode); - buffer.encode_bool(16, this->has_custom_fan_mode); - buffer.encode_string(17, this->custom_fan_mode); - buffer.encode_bool(18, this->has_preset); - buffer.encode_enum(19, this->preset); - buffer.encode_bool(20, this->has_custom_preset); - buffer.encode_string(21, this->custom_preset); - buffer.encode_bool(22, this->has_target_humidity); - buffer.encode_float(23, this->target_humidity); -} -void ClimateCommandRequest::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_mode, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false); - ProtoSize::add_bool_field(total_size, 1, this->has_target_temperature, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_target_temperature_low, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_low != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->has_target_temperature_high, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_high != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->unused_has_legacy_away, false); - ProtoSize::add_bool_field(total_size, 1, this->unused_legacy_away, false); - ProtoSize::add_bool_field(total_size, 1, this->has_fan_mode, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->fan_mode), false); - ProtoSize::add_bool_field(total_size, 1, this->has_swing_mode, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->swing_mode), false); - ProtoSize::add_bool_field(total_size, 2, this->has_custom_fan_mode, false); - ProtoSize::add_string_field(total_size, 2, this->custom_fan_mode, false); - ProtoSize::add_bool_field(total_size, 2, this->has_preset, false); - ProtoSize::add_enum_field(total_size, 2, static_cast(this->preset), false); - ProtoSize::add_bool_field(total_size, 2, this->has_custom_preset, false); - 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); -} #endif #ifdef USE_NUMBER -bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 9: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 10: { - this->entity_category = value.as_enum(); - return true; - } - case 12: { - this->mode = value.as_enum(); - return true; - } - case 14: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 11: { - this->unit_of_measurement = value.as_string(); - return true; - } - case 13: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesNumberResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - case 6: { - this->min_value = value.as_float(); - return true; - } - case 7: { - this->max_value = value.as_float(); - return true; - } - case 8: { - this->step = value.as_float(); - return true; - } - default: - return false; - } -} void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -2870,54 +1360,26 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); - buffer.encode_enum(10, this->entity_category); + buffer.encode_uint32(10, static_cast(this->entity_category)); buffer.encode_string(11, this->unit_of_measurement); - buffer.encode_enum(12, this->mode); + buffer.encode_uint32(12, static_cast(this->mode)); buffer.encode_string(13, this->device_class); buffer.encode_uint32(14, this->device_id); } void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->min_value != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->max_value != 0.0f, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->step != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool NumberStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool NumberStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 2: { - this->state = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_fixed_field<4>(total_size, 1, this->min_value != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->max_value != 0.0f); + ProtoSize::add_fixed_field<4>(total_size, 1, this->step != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -2926,10 +1388,20 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void NumberStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); +} +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) { @@ -2945,66 +1417,8 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(2, this->state); -} -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); -} #endif #ifdef USE_SELECT -bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 7: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 8: { - this->entity_category = value.as_enum(); - return true; - } - case 9: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 6: { - this->options.push_back(value.as_string()); - return true; - } - default: - return false; - } -} -bool ListEntitiesSelectResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -3014,56 +1428,22 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, it, true); } buffer.encode_bool(7, this->disabled_by_default); - buffer.encode_enum(8, this->entity_category); + buffer.encode_uint32(8, static_cast(this->entity_category)); buffer.encode_uint32(9, this->device_id); } void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); if (!this->options.empty()) { for (const auto &it : this->options) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool SelectStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool SelectStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->state = value.as_string(); - return true; - } - default: - return false; - } -} -bool SelectStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -3072,10 +1452,20 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void SelectStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->state); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); +} +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) { @@ -3097,74 +1487,8 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state); -} -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); -} #endif #ifdef USE_SIREN -bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 8: { - this->supports_duration = value.as_bool(); - return true; - } - case 9: { - this->supports_volume = value.as_bool(); - return true; - } - case 10: { - this->entity_category = value.as_enum(); - return true; - } - case 11: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesSirenResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 7: { - this->tones.push_back(value.as_string()); - return true; - } - default: - return false; - } -} -bool ListEntitiesSirenResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); @@ -3176,48 +1500,24 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(9, this->supports_volume); - buffer.encode_enum(10, this->entity_category); + buffer.encode_uint32(10, static_cast(this->entity_category)); buffer.encode_uint32(11, this->device_id); } void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); if (!this->tones.empty()) { for (const auto &it : this->tones) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool SirenStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_bool(); - return true; - } - case 3: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool SirenStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_bool_field(total_size, 1, this->supports_duration); + ProtoSize::add_bool_field(total_size, 1, this->supports_volume); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void SirenStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -3225,9 +1525,9 @@ void SirenStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); } void SirenStateResponse::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); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -3255,6 +1555,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; } @@ -3283,99 +1587,15 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void SirenCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_state); - buffer.encode_bool(3, this->state); - buffer.encode_bool(4, this->has_tone); - buffer.encode_string(5, this->tone); - buffer.encode_bool(6, this->has_duration); - buffer.encode_uint32(7, this->duration); - buffer.encode_bool(8, this->has_volume); - buffer.encode_float(9, this->volume); -} -void SirenCommandRequest::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_state, false); - ProtoSize::add_bool_field(total_size, 1, this->state, false); - ProtoSize::add_bool_field(total_size, 1, this->has_tone, false); - ProtoSize::add_string_field(total_size, 1, this->tone, false); - ProtoSize::add_bool_field(total_size, 1, this->has_duration, false); - 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); -} #endif #ifdef USE_LOCK -bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->assumed_state = value.as_bool(); - return true; - } - case 9: { - this->supports_open = value.as_bool(); - return true; - } - case 10: { - this->requires_code = value.as_bool(); - return true; - } - case 12: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 11: { - this->code_format = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->assumed_state); buffer.encode_bool(9, this->supports_open); buffer.encode_bool(10, this->requires_code); @@ -3383,62 +1603,42 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->device_id); } void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); - ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); - ProtoSize::add_string_field(total_size, 1, this->code_format, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_enum(); - return true; - } - case 3: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_bool_field(total_size, 1, this->assumed_state); + ProtoSize::add_bool_field(total_size, 1, this->supports_open); + ProtoSize::add_bool_field(total_size, 1, this->requires_code); + ProtoSize::add_string_field(total_size, 1, this->code_format); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void LockStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->state); + buffer.encode_uint32(2, static_cast(this->state)); buffer.encode_uint32(3, this->device_id); } void LockStateResponse::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(this->state), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->command = value.as_enum(); + this->command = static_cast(value.as_uint32()); return true; } case 3: { this->has_code = value.as_bool(); return true; } + case 5: { + this->device_id = value.as_uint32(); + return true; + } default: return false; } @@ -3463,89 +1663,37 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void LockCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->command); - buffer.encode_bool(3, this->has_code); - buffer.encode_string(4, this->code); -} -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(this->command), false); - ProtoSize::add_bool_field(total_size, 1, this->has_code, false); - ProtoSize::add_string_field(total_size, 1, this->code, false); -} #endif #ifdef USE_BUTTON -bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 9: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class); buffer.encode_uint32(9, this->device_id); } void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); +} +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) { @@ -3557,10 +1705,6 @@ 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::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); -} #endif #ifdef USE_MEDIA_PLAYER bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -3574,7 +1718,7 @@ bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 4: { - this->purpose = value.as_enum(); + this->purpose = static_cast(value.as_uint32()); return true; } case 5: { @@ -3599,69 +1743,15 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->format); buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(3, this->num_channels); - buffer.encode_enum(4, this->purpose); + buffer.encode_uint32(4, static_cast(this->purpose)); buffer.encode_uint32(5, this->sample_bytes); } void MediaPlayerSupportedFormat::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->format, false); - ProtoSize::add_uint32_field(total_size, 1, this->sample_rate, false); - ProtoSize::add_uint32_field(total_size, 1, this->num_channels, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->purpose), false); - ProtoSize::add_uint32_field(total_size, 1, this->sample_bytes, false); -} -bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->supports_pause = value.as_bool(); - return true; - } - case 10: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 9: { - this->supported_formats.push_back(value.as_message()); - return true; - } - default: - return false; - } -} -bool ListEntitiesMediaPlayerResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->format); + ProtoSize::add_uint32_field(total_size, 1, this->sample_rate); + ProtoSize::add_uint32_field(total_size, 1, this->num_channels); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->purpose)); + ProtoSize::add_uint32_field(total_size, 1, this->sample_bytes); } void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); @@ -3669,69 +1759,37 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->supports_pause); for (auto &it : this->supported_formats) { - buffer.encode_message(9, it, true); + buffer.encode_message(9, it, true); } buffer.encode_uint32(10, this->device_id); } void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_bool_field(total_size, 1, this->supports_pause); ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool MediaPlayerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_enum(); - return true; - } - case 4: { - this->muted = value.as_bool(); - return true; - } - case 5: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool MediaPlayerStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 3: { - this->volume = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->state); + buffer.encode_uint32(2, static_cast(this->state)); buffer.encode_float(3, this->volume); buffer.encode_bool(4, this->muted); buffer.encode_uint32(5, this->device_id); } void MediaPlayerStateResponse::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(this->state), false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false); - ProtoSize::add_bool_field(total_size, 1, this->muted, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); + ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f); + ProtoSize::add_bool_field(total_size, 1, this->muted); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -3740,7 +1798,7 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val return true; } case 3: { - this->command = value.as_enum(); + this->command = static_cast(value.as_uint32()); return true; } case 4: { @@ -3759,6 +1817,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; } @@ -3787,28 +1849,6 @@ bool MediaPlayerCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value return false; } } -void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_command); - buffer.encode_enum(3, this->command); - buffer.encode_bool(4, this->has_volume); - buffer.encode_float(5, this->volume); - buffer.encode_bool(6, this->has_media_url); - buffer.encode_string(7, this->media_url); - buffer.encode_bool(8, this->has_announcement); - buffer.encode_bool(9, this->announcement); -} -void MediaPlayerCommandRequest::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_command, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->command), 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_bool_field(total_size, 1, this->has_media_url, false); - 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); -} #endif #ifdef USE_BLUETOOTH_PROXY bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -3821,12 +1861,6 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, return false; } } -void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint32(1, this->flags); -} -void SubscribeBluetoothLEAdvertisementsRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->flags, false); -} bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -3859,53 +1893,13 @@ void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); } void BluetoothServiceData::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->uuid, false); + ProtoSize::add_string_field(total_size, 1, this->uuid); if (!this->legacy_data.empty()) { for (const auto &it : this->legacy_data) { - ProtoSize::add_uint32_field(total_size, 1, it, true); + ProtoSize::add_uint32_field_repeated(total_size, 1, it); } } - ProtoSize::add_string_field(total_size, 1, this->data, false); -} -bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 3: { - this->rssi = value.as_sint32(); - return true; - } - case 7: { - this->address_type = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->name = value.as_string(); - return true; - } - case 4: { - this->service_uuids.push_back(value.as_string()); - return true; - } - case 5: { - this->service_data.push_back(value.as_message()); - return true; - } - case 6: { - this->manufacturer_data.push_back(value.as_message()); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->data); } void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); @@ -3915,25 +1909,25 @@ void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, it, true); } for (auto &it : this->service_data) { - buffer.encode_message(5, it, true); + buffer.encode_message(5, it, true); } for (auto &it : this->manufacturer_data) { - buffer.encode_message(6, it, true); + buffer.encode_message(6, it, true); } buffer.encode_uint32(7, this->address_type); } void BluetoothLEAdvertisementResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_sint32_field(total_size, 1, this->rssi, false); + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_sint32_field(total_size, 1, this->rssi); if (!this->service_uuids.empty()) { for (const auto &it : this->service_uuids) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } ProtoSize::add_repeated_message(total_size, 1, this->service_data); ProtoSize::add_repeated_message(total_size, 1, this->manufacturer_data); - ProtoSize::add_uint32_field(total_size, 1, this->address_type, false); + ProtoSize::add_uint32_field(total_size, 1, this->address_type); } bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -3970,24 +1964,14 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(4, reinterpret_cast(this->data.data()), this->data.size()); } void BluetoothLERawAdvertisement::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_sint32_field(total_size, 1, this->rssi, false); - ProtoSize::add_uint32_field(total_size, 1, this->address_type, false); - ProtoSize::add_string_field(total_size, 1, this->data, false); -} -bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->advertisements.push_back(value.as_message()); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_sint32_field(total_size, 1, this->rssi); + ProtoSize::add_uint32_field(total_size, 1, this->address_type); + ProtoSize::add_string_field(total_size, 1, this->data); } void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->advertisements) { - buffer.encode_message(1, it, true); + buffer.encode_message(1, it, true); } } void BluetoothLERawAdvertisementsResponse::calculate_size(uint32_t &total_size) const { @@ -4000,7 +1984,7 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 2: { - this->request_type = value.as_enum(); + this->request_type = static_cast(value.as_uint32()); return true; } case 3: { @@ -4015,40 +1999,6 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return false; } } -void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_enum(2, this->request_type); - buffer.encode_bool(3, this->has_address_type); - buffer.encode_uint32(4, this->address_type); -} -void BluetoothDeviceRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->request_type), false); - ProtoSize::add_bool_field(total_size, 1, this->has_address_type, false); - ProtoSize::add_uint32_field(total_size, 1, this->address_type, false); -} -bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->connected = value.as_bool(); - return true; - } - case 3: { - this->mtu = value.as_uint32(); - return true; - } - case 4: { - this->error = value.as_int32(); - return true; - } - default: - return false; - } -} void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_bool(2, this->connected); @@ -4056,10 +2006,10 @@ void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(4, this->error); } void BluetoothDeviceConnectionResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_bool_field(total_size, 1, this->connected, false); - ProtoSize::add_uint32_field(total_size, 1, this->mtu, false); - ProtoSize::add_int32_field(total_size, 1, this->error, false); + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_bool_field(total_size, 1, this->connected); + ProtoSize::add_uint32_field(total_size, 1, this->mtu); + ProtoSize::add_int32_field(total_size, 1, this->error); } bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -4071,10 +2021,6 @@ bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarI return false; } } -void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } -void BluetoothGATTGetServicesRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); -} bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -4098,10 +2044,10 @@ void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const { void BluetoothGATTDescriptor::calculate_size(uint32_t &total_size) const { if (!this->uuid.empty()) { for (const auto &it : this->uuid) { - ProtoSize::add_uint64_field(total_size, 1, it, true); + ProtoSize::add_uint64_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); + ProtoSize::add_uint32_field(total_size, 1, this->handle); } bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -4124,7 +2070,8 @@ bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt v bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - this->descriptors.push_back(value.as_message()); + this->descriptors.emplace_back(); + value.decode_to_message(this->descriptors.back()); return true; } default: @@ -4138,17 +2085,17 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_uint32(3, this->properties); for (auto &it : this->descriptors) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it, true); } } void BluetoothGATTCharacteristic::calculate_size(uint32_t &total_size) const { if (!this->uuid.empty()) { for (const auto &it : this->uuid) { - ProtoSize::add_uint64_field(total_size, 1, it, true); + ProtoSize::add_uint64_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_uint32_field(total_size, 1, this->properties, false); + ProtoSize::add_uint32_field(total_size, 1, this->handle); + ProtoSize::add_uint32_field(total_size, 1, this->properties); ProtoSize::add_repeated_message(total_size, 1, this->descriptors); } bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -4168,7 +2115,8 @@ bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - this->characteristics.push_back(value.as_message()); + this->characteristics.emplace_back(); + value.decode_to_message(this->characteristics.back()); return true; } default: @@ -4181,63 +2129,33 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { } buffer.encode_uint32(2, this->handle); for (auto &it : this->characteristics) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it, true); } } void BluetoothGATTService::calculate_size(uint32_t &total_size) const { if (!this->uuid.empty()) { for (const auto &it : this->uuid) { - ProtoSize::add_uint64_field(total_size, 1, it, true); + ProtoSize::add_uint64_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); + ProtoSize::add_uint32_field(total_size, 1, this->handle); ProtoSize::add_repeated_message(total_size, 1, this->characteristics); } -bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - default: - return false; - } -} -bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->services.push_back(value.as_message()); - return true; - } - default: - return false; - } -} void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); for (auto &it : this->services) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it, true); } } void BluetoothGATTGetServicesResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); + ProtoSize::add_uint64_field(total_size, 1, this->address); ProtoSize::add_repeated_message(total_size, 1, this->services); } -bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - default: - return false; - } -} void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } void BluetoothGATTGetServicesDoneResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); + ProtoSize::add_uint64_field(total_size, 1, this->address); } bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -4253,47 +2171,15 @@ bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt valu return false; } } -void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); -} -void BluetoothGATTReadRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); -} -bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->handle = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 3: { - this->data = value.as_string(); - return true; - } - default: - return false; - } -} void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); } void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_string_field(total_size, 1, this->data, false); + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_uint32_field(total_size, 1, this->handle); + ProtoSize::add_string_field(total_size, 1, this->data); } bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -4323,18 +2209,6 @@ bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDeli return false; } } -void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_bool(3, this->response); - buffer.encode_bytes(4, reinterpret_cast(this->data.data()), this->data.size()); -} -void BluetoothGATTWriteRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_bool_field(total_size, 1, this->response, false); - ProtoSize::add_string_field(total_size, 1, this->data, false); -} bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -4349,14 +2223,6 @@ bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoV return false; } } -void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); -} -void BluetoothGATTReadDescriptorRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); -} bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -4381,16 +2247,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, Proto return false; } } -void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); -} -void BluetoothGATTWriteDescriptorRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_string_field(total_size, 1, this->data, false); -} bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -4409,67 +2265,15 @@ bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt va return false; } } -void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_bool(3, this->enable); -} -void BluetoothGATTNotifyRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_bool_field(total_size, 1, this->enable, false); -} -bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->handle = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 3: { - this->data = value.as_string(); - return true; - } - default: - return false; - } -} void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); } void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_string_field(total_size, 1, this->data, false); -} -bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->free = value.as_uint32(); - return true; - } - case 2: { - this->limit = value.as_uint32(); - return true; - } - case 3: { - this->allocated.push_back(value.as_uint64()); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_uint32_field(total_size, 1, this->handle); + ProtoSize::add_string_field(total_size, 1, this->data); } void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->free); @@ -4479,103 +2283,39 @@ void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { } } void BluetoothConnectionsFreeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->free, false); - ProtoSize::add_uint32_field(total_size, 1, this->limit, false); + ProtoSize::add_uint32_field(total_size, 1, this->free); + ProtoSize::add_uint32_field(total_size, 1, this->limit); if (!this->allocated.empty()) { for (const auto &it : this->allocated) { - ProtoSize::add_uint64_field(total_size, 1, it, true); + ProtoSize::add_uint64_field_repeated(total_size, 1, it); } } } -bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->handle = value.as_uint32(); - return true; - } - case 3: { - this->error = value.as_int32(); - return true; - } - default: - return false; - } -} void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); buffer.encode_int32(3, this->error); } void BluetoothGATTErrorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); - ProtoSize::add_int32_field(total_size, 1, this->error, false); -} -bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->handle = value.as_uint32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_uint32_field(total_size, 1, this->handle); + ProtoSize::add_int32_field(total_size, 1, this->error); } void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); } void BluetoothGATTWriteResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); -} -bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->handle = value.as_uint32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_uint32_field(total_size, 1, this->handle); } void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); } void BluetoothGATTNotifyResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_uint32_field(total_size, 1, this->handle, false); -} -bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->paired = value.as_bool(); - return true; - } - case 3: { - this->error = value.as_int32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_uint32_field(total_size, 1, this->handle); } void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); @@ -4583,27 +2323,9 @@ void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(3, this->error); } void BluetoothDevicePairingResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_bool_field(total_size, 1, this->paired, false); - ProtoSize::add_int32_field(total_size, 1, this->error, false); -} -bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->success = value.as_bool(); - return true; - } - case 3: { - this->error = value.as_int32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_bool_field(total_size, 1, this->paired); + ProtoSize::add_int32_field(total_size, 1, this->error); } void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); @@ -4611,27 +2333,9 @@ void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(3, this->error); } void BluetoothDeviceUnpairingResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_bool_field(total_size, 1, this->success, false); - ProtoSize::add_int32_field(total_size, 1, this->error, false); -} -bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->address = value.as_uint64(); - return true; - } - case 2: { - this->success = value.as_bool(); - return true; - } - case 3: { - this->error = value.as_int32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_bool_field(total_size, 1, this->success); + ProtoSize::add_int32_field(total_size, 1, this->error); } void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); @@ -4639,48 +2343,28 @@ void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(3, this->error); } void BluetoothDeviceClearCacheResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address, false); - ProtoSize::add_bool_field(total_size, 1, this->success, false); - ProtoSize::add_int32_field(total_size, 1, this->error, false); -} -bool BluetoothScannerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->state = value.as_enum(); - return true; - } - case 2: { - this->mode = value.as_enum(); - return true; - } - default: - return false; - } + ProtoSize::add_uint64_field(total_size, 1, this->address); + ProtoSize::add_bool_field(total_size, 1, this->success); + ProtoSize::add_int32_field(total_size, 1, this->error); } void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->state); - buffer.encode_enum(2, this->mode); + buffer.encode_uint32(1, static_cast(this->state)); + buffer.encode_uint32(2, static_cast(this->mode)); } void BluetoothScannerStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state), false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); } bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { - this->mode = value.as_enum(); + this->mode = static_cast(value.as_uint32()); return true; } default: return false; } } -void BluetoothScannerSetModeRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->mode); -} -void BluetoothScannerSetModeRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false); -} #endif #ifdef USE_VOICE_ASSISTANT bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -4697,14 +2381,6 @@ bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarIn return false; } } -void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bool(1, this->subscribe); - buffer.encode_uint32(2, this->flags); -} -void SubscribeVoiceAssistantRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->subscribe, false); - ProtoSize::add_uint32_field(total_size, 1, this->flags, false); -} bool VoiceAssistantAudioSettings::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { @@ -4735,55 +2411,23 @@ void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(3, this->volume_multiplier); } void VoiceAssistantAudioSettings::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->noise_suppression_level, false); - ProtoSize::add_uint32_field(total_size, 1, this->auto_gain, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->volume_multiplier != 0.0f, false); -} -bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->start = value.as_bool(); - return true; - } - case 3: { - this->flags = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->conversation_id = value.as_string(); - return true; - } - case 4: { - this->audio_settings = value.as_message(); - return true; - } - case 5: { - this->wake_word_phrase = value.as_string(); - return true; - } - default: - return false; - } + ProtoSize::add_uint32_field(total_size, 1, this->noise_suppression_level); + ProtoSize::add_uint32_field(total_size, 1, this->auto_gain); + ProtoSize::add_fixed_field<4>(total_size, 1, this->volume_multiplier != 0.0f); } void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); buffer.encode_string(2, this->conversation_id); buffer.encode_uint32(3, this->flags); - buffer.encode_message(4, this->audio_settings); + buffer.encode_message(4, this->audio_settings); buffer.encode_string(5, this->wake_word_phrase); } void VoiceAssistantRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->start, false); - ProtoSize::add_string_field(total_size, 1, this->conversation_id, false); - ProtoSize::add_uint32_field(total_size, 1, this->flags, false); - ProtoSize::add_message_object(total_size, 1, this->audio_settings, false); - ProtoSize::add_string_field(total_size, 1, this->wake_word_phrase, false); + ProtoSize::add_bool_field(total_size, 1, this->start); + ProtoSize::add_string_field(total_size, 1, this->conversation_id); + ProtoSize::add_uint32_field(total_size, 1, this->flags); + ProtoSize::add_message_object(total_size, 1, this->audio_settings); + ProtoSize::add_string_field(total_size, 1, this->wake_word_phrase); } bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -4799,14 +2443,6 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) return false; } } -void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint32(1, this->port); - buffer.encode_bool(2, this->error); -} -void VoiceAssistantResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->port, false); - ProtoSize::add_bool_field(total_size, 1, this->error, false); -} bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -4826,13 +2462,13 @@ void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->value); } void VoiceAssistantEventData::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->value, false); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->value); } bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { - this->event_type = value.as_enum(); + this->event_type = static_cast(value.as_uint32()); return true; } default: @@ -4842,23 +2478,14 @@ bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt v bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - this->data.push_back(value.as_message()); + this->data.emplace_back(); + value.decode_to_message(this->data.back()); return true; } default: return false; } } -void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->event_type); - for (auto &it : this->data) { - buffer.encode_message(2, it, true); - } -} -void VoiceAssistantEventResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->event_type), false); - ProtoSize::add_repeated_message(total_size, 1, this->data); -} bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -4884,13 +2511,13 @@ void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(2, this->end); } void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->data, false); - ProtoSize::add_bool_field(total_size, 1, this->end, false); + ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bool_field(total_size, 1, this->end); } bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { - this->event_type = value.as_enum(); + this->event_type = static_cast(value.as_uint32()); return true; } case 4: { @@ -4923,22 +2550,6 @@ bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLen return false; } } -void VoiceAssistantTimerEventResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_enum(1, this->event_type); - buffer.encode_string(2, this->timer_id); - buffer.encode_string(3, this->name); - buffer.encode_uint32(4, this->total_seconds); - buffer.encode_uint32(5, this->seconds_left); - buffer.encode_bool(6, this->is_active); -} -void VoiceAssistantTimerEventResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->event_type), false); - ProtoSize::add_string_field(total_size, 1, this->timer_id, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_uint32_field(total_size, 1, this->total_seconds, false); - ProtoSize::add_uint32_field(total_size, 1, this->seconds_left, false); - ProtoSize::add_bool_field(total_size, 1, this->is_active, false); -} bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 4: { @@ -4967,31 +2578,9 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength return false; } } -void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->media_id); - buffer.encode_string(2, this->text); - buffer.encode_string(3, this->preannounce_media_id); - buffer.encode_bool(4, this->start_conversation); -} -void VoiceAssistantAnnounceRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->media_id, false); - ProtoSize::add_string_field(total_size, 1, this->text, false); - ProtoSize::add_string_field(total_size, 1, this->preannounce_media_id, false); - ProtoSize::add_bool_field(total_size, 1, this->start_conversation, false); -} -bool VoiceAssistantAnnounceFinished::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->success = value.as_bool(); - return true; - } - default: - return false; - } -} void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } void VoiceAssistantAnnounceFinished::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->success, false); + ProtoSize::add_bool_field(total_size, 1, this->success); } bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -5019,41 +2608,17 @@ void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { } } void VoiceAssistantWakeWord::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->id, false); - ProtoSize::add_string_field(total_size, 1, this->wake_word, false); + ProtoSize::add_string_field(total_size, 1, this->id); + ProtoSize::add_string_field(total_size, 1, this->wake_word); if (!this->trained_languages.empty()) { for (const auto &it : this->trained_languages) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } } -bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->max_active_wake_words = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->available_wake_words.push_back(value.as_message()); - return true; - } - case 2: { - this->active_wake_words.push_back(value.as_string()); - return true; - } - default: - return false; - } -} void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->available_wake_words) { - buffer.encode_message(1, it, true); + buffer.encode_message(1, it, true); } for (auto &it : this->active_wake_words) { buffer.encode_string(2, it, true); @@ -5064,10 +2629,10 @@ void VoiceAssistantConfigurationResponse::calculate_size(uint32_t &total_size) c ProtoSize::add_repeated_message(total_size, 1, this->available_wake_words); if (!this->active_wake_words.empty()) { for (const auto &it : this->active_wake_words) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->max_active_wake_words, false); + ProtoSize::add_uint32_field(total_size, 1, this->max_active_wake_words); } bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -5079,140 +2644,50 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt return false; } } -void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { - for (auto &it : this->active_wake_words) { - buffer.encode_string(1, it, true); - } -} -void VoiceAssistantSetConfiguration::calculate_size(uint32_t &total_size) const { - if (!this->active_wake_words.empty()) { - for (const auto &it : this->active_wake_words) { - ProtoSize::add_string_field(total_size, 1, it, true); - } - } -} #endif #ifdef USE_ALARM_CONTROL_PANEL -bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->supported_features = value.as_uint32(); - return true; - } - case 9: { - this->requires_code = value.as_bool(); - return true; - } - case 10: { - this->requires_code_to_arm = value.as_bool(); - return true; - } - case 11: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesAlarmControlPanelResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->supported_features); buffer.encode_bool(9, this->requires_code); buffer.encode_bool(10, this->requires_code_to_arm); buffer.encode_uint32(11, this->device_id); } void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); - ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); - ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->state = value.as_enum(); - return true; - } - case 3: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->supported_features); + ProtoSize::add_bool_field(total_size, 1, this->requires_code); + ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->state); + buffer.encode_uint32(2, static_cast(this->state)); buffer.encode_uint32(3, this->device_id); } void AlarmControlPanelStateResponse::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(this->state), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->command = value.as_enum(); + this->command = static_cast(value.as_uint32()); + return true; + } + case 4: { + this->device_id = value.as_uint32(); return true; } default: @@ -5239,139 +2714,33 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit return false; } } -void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->command); - buffer.encode_string(3, this->code); -} -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(this->command), false); - ProtoSize::add_string_field(total_size, 1, this->code, false); -} #endif #ifdef USE_TEXT -bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->min_length = value.as_uint32(); - return true; - } - case 9: { - this->max_length = value.as_uint32(); - return true; - } - case 11: { - this->mode = value.as_enum(); - return true; - } - case 12: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 10: { - this->pattern = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTextResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->min_length); buffer.encode_uint32(9, this->max_length); buffer.encode_string(10, this->pattern); - buffer.encode_enum(11, this->mode); + buffer.encode_uint32(11, static_cast(this->mode)); buffer.encode_uint32(12, this->device_id); } void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->min_length, false); - ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); - ProtoSize::add_string_field(total_size, 1, this->pattern, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool TextStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool TextStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->state = value.as_string(); - return true; - } - default: - return false; - } -} -bool TextStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->min_length); + ProtoSize::add_uint32_field(total_size, 1, this->max_length); + ProtoSize::add_string_field(total_size, 1, this->pattern); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -5380,10 +2749,20 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void TextStateResponse::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_bool_field(total_size, 1, this->missing_state, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->state); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); +} +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) { @@ -5405,115 +2784,25 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void TextCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state); -} -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); -} #endif #ifdef USE_DATETIME_DATE -bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesDateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->device_id); } void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool DateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->missing_state = value.as_bool(); - return true; - } - case 3: { - this->year = value.as_uint32(); - return true; - } - case 4: { - this->month = value.as_uint32(); - return true; - } - case 5: { - this->day = value.as_uint32(); - return true; - } - case 6: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool DateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void DateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -5524,12 +2813,12 @@ void DateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(6, this->device_id); } void DateStateResponse::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->missing_state, 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); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->year); + ProtoSize::add_uint32_field(total_size, 1, this->month); + ProtoSize::add_uint32_field(total_size, 1, this->day); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5545,6 +2834,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; } @@ -5559,119 +2852,25 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void DateCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, this->year); - buffer.encode_uint32(3, this->month); - buffer.encode_uint32(4, this->day); -} -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); -} #endif #ifdef USE_DATETIME_TIME -bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->device_id); } void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool TimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->missing_state = value.as_bool(); - return true; - } - case 3: { - this->hour = value.as_uint32(); - return true; - } - case 4: { - this->minute = value.as_uint32(); - return true; - } - case 5: { - this->second = value.as_uint32(); - return true; - } - case 6: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool TimeStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void TimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -5682,12 +2881,12 @@ void TimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(6, this->device_id); } void TimeStateResponse::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->missing_state, 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); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_uint32_field(total_size, 1, this->hour); + ProtoSize::add_uint32_field(total_size, 1, this->minute); + ProtoSize::add_uint32_field(total_size, 1, this->second); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5703,6 +2902,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; } @@ -5717,81 +2920,15 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void TimeCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, this->hour); - buffer.encode_uint32(3, this->minute); - buffer.encode_uint32(4, this->second); -} -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); -} #endif #ifdef USE_EVENT -bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 10: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - case 9: { - this->event_types.push_back(value.as_string()); - return true; - } - default: - return false; - } -} -bool ListEntitiesEventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class); for (auto &it : this->event_types) { buffer.encode_string(9, it, true); @@ -5799,49 +2936,19 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(10, this->device_id); } void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); if (!this->event_types.empty()) { for (const auto &it : this->event_types) { - ProtoSize::add_string_field(total_size, 1, it, true); + ProtoSize::add_string_field_repeated(total_size, 1, it); } } - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool EventResponse::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 EventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 2: { - this->event_type = value.as_string(); - return true; - } - default: - return false; - } -} -bool EventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -5849,81 +2956,19 @@ void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); } void EventResponse::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->event_type, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->event_type); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } #endif #ifdef USE_VALVE -bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 9: { - this->assumed_state = value.as_bool(); - return true; - } - case 10: { - this->supports_position = value.as_bool(); - return true; - } - case 11: { - this->supports_stop = value.as_bool(); - return true; - } - case 12: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesValveResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesValveResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->assumed_state); buffer.encode_bool(10, this->supports_position); @@ -5931,57 +2976,29 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->device_id); } void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); - ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool ValveStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 3: { - this->current_operation = value.as_enum(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ValveStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 2: { - this->position = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_bool_field(total_size, 1, this->assumed_state); + ProtoSize::add_bool_field(total_size, 1, this->supports_position); + ProtoSize::add_bool_field(total_size, 1, this->supports_stop); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void ValveStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); buffer.encode_float(2, this->position); - buffer.encode_enum(3, this->current_operation); + buffer.encode_uint32(3, static_cast(this->current_operation)); buffer.encode_uint32(4, this->device_id); } void ValveStateResponse::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->position != 0.0f, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -5993,6 +3010,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; } @@ -6011,111 +3032,25 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void ValveCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->has_position); - buffer.encode_float(3, this->position); - buffer.encode_bool(4, this->stop); -} -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); -} #endif #ifdef USE_DATETIME_DATETIME -bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 8: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesDateTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesDateTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->device_id); } void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool DateTimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->missing_state = value.as_bool(); - return true; - } - case 4: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool DateTimeStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 3: { - this->epoch_seconds = value.as_fixed32(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -6124,10 +3059,20 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); } void DateTimeStateResponse::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->missing_state, 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); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); +} +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) { @@ -6143,147 +3088,27 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void DateTimeCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_fixed32(2, this->epoch_seconds); -} -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); -} #endif #ifdef USE_UPDATE -bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 6: { - this->disabled_by_default = value.as_bool(); - return true; - } - case 7: { - this->entity_category = value.as_enum(); - return true; - } - case 9: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - this->object_id = value.as_string(); - return true; - } - case 3: { - this->name = value.as_string(); - return true; - } - case 5: { - this->icon = value.as_string(); - return true; - } - case 8: { - this->device_class = value.as_string(); - return true; - } - default: - return false; - } -} -bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 2: { - this->key = value.as_fixed32(); - return true; - } - default: - return false; - } -} void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_enum(7, this->entity_category); + buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class); buffer.encode_uint32(9, this->device_id); } void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); - ProtoSize::add_string_field(total_size, 1, this->name, false); - ProtoSize::add_string_field(total_size, 1, this->icon, false); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category), false); - ProtoSize::add_string_field(total_size, 1, this->device_class, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); -} -bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 2: { - this->missing_state = value.as_bool(); - return true; - } - case 3: { - this->in_progress = value.as_bool(); - return true; - } - case 4: { - this->has_progress = value.as_bool(); - return true; - } - case 11: { - this->device_id = value.as_uint32(); - return true; - } - default: - return false; - } -} -bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 6: { - this->current_version = value.as_string(); - return true; - } - case 7: { - this->latest_version = value.as_string(); - return true; - } - case 8: { - this->title = value.as_string(); - return true; - } - case 9: { - this->release_summary = value.as_string(); - return true; - } - case 10: { - this->release_url = value.as_string(); - return true; - } - default: - return false; - } -} -bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { - switch (field_id) { - case 1: { - this->key = value.as_fixed32(); - return true; - } - case 5: { - this->progress = value.as_float(); - return true; - } - default: - return false; - } + ProtoSize::add_string_field(total_size, 1, this->object_id); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_string_field(total_size, 1, this->name); + ProtoSize::add_string_field(total_size, 1, this->icon); + ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + ProtoSize::add_string_field(total_size, 1, this->device_class); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); @@ -6299,22 +3124,26 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->device_id); } void UpdateStateResponse::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->missing_state, false); - ProtoSize::add_bool_field(total_size, 1, this->in_progress, false); - ProtoSize::add_bool_field(total_size, 1, this->has_progress, false); - ProtoSize::add_fixed_field<4>(total_size, 1, this->progress != 0.0f, false); - ProtoSize::add_string_field(total_size, 1, this->current_version, false); - ProtoSize::add_string_field(total_size, 1, this->latest_version, false); - ProtoSize::add_string_field(total_size, 1, this->title, false); - ProtoSize::add_string_field(total_size, 1, this->release_summary, false); - ProtoSize::add_string_field(total_size, 1, this->release_url, false); - ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); + ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0); + ProtoSize::add_bool_field(total_size, 1, this->missing_state); + ProtoSize::add_bool_field(total_size, 1, this->in_progress); + ProtoSize::add_bool_field(total_size, 1, this->has_progress); + ProtoSize::add_fixed_field<4>(total_size, 1, this->progress != 0.0f); + ProtoSize::add_string_field(total_size, 1, this->current_version); + ProtoSize::add_string_field(total_size, 1, this->latest_version); + ProtoSize::add_string_field(total_size, 1, this->title); + ProtoSize::add_string_field(total_size, 1, this->release_summary); + ProtoSize::add_string_field(total_size, 1, this->release_url); + ProtoSize::add_uint32_field(total_size, 1, this->device_id); } bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->command = value.as_enum(); + this->command = static_cast(value.as_uint32()); + return true; + } + case 3: { + this->device_id = value.as_uint32(); return true; } default: @@ -6331,14 +3160,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } } -void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_enum(2, this->command); -} -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(this->command), false); -} #endif } // namespace api diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index a308ff3d40..e84e814f44 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -5,7 +5,6 @@ #include "esphome/core/defines.h" #include "proto.h" -#include "api_pb2_size.h" namespace esphome { namespace api { @@ -82,6 +81,7 @@ enum LogLevel : uint32_t { LOG_LEVEL_VERBOSE = 6, LOG_LEVEL_VERY_VERBOSE = 7, }; +#ifdef USE_API_SERVICES enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, SERVICE_ARG_TYPE_INT = 1, @@ -92,6 +92,7 @@ enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_FLOAT_ARRAY = 6, SERVICE_ARG_TYPE_STRING_ARRAY = 7, }; +#endif #ifdef USE_CLIMATE enum ClimateMode : uint32_t { CLIMATE_MODE_OFF = 0, @@ -306,18 +307,25 @@ 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; - static constexpr uint16_t ESTIMATED_SIZE = 17; + static constexpr uint8_t MESSAGE_TYPE = 1; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "hello_request"; } #endif std::string client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -328,8 +336,8 @@ class HelloRequest : public ProtoMessage { }; class HelloResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 2; - static constexpr uint16_t ESTIMATED_SIZE = 26; + static constexpr uint8_t MESSAGE_TYPE = 2; + static constexpr uint8_t ESTIMATED_SIZE = 26; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "hello_response"; } #endif @@ -344,19 +352,15 @@ class HelloResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class ConnectRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 3; - static constexpr uint16_t ESTIMATED_SIZE = 9; + static constexpr uint8_t MESSAGE_TYPE = 3; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "connect_request"; } #endif std::string password{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -366,8 +370,8 @@ class ConnectRequest : public ProtoMessage { }; class ConnectResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 4; - static constexpr uint16_t ESTIMATED_SIZE = 2; + static constexpr uint8_t MESSAGE_TYPE = 4; + static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "connect_response"; } #endif @@ -379,12 +383,11 @@ class ConnectResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class DisconnectRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 5; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 5; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "disconnect_request"; } #endif @@ -396,8 +399,8 @@ class DisconnectRequest : public ProtoMessage { }; class DisconnectResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 6; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 6; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "disconnect_response"; } #endif @@ -409,8 +412,8 @@ class DisconnectResponse : public ProtoMessage { }; class PingRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 7; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 7; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "ping_request"; } #endif @@ -422,8 +425,8 @@ class PingRequest : public ProtoMessage { }; class PingResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 8; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 8; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "ping_response"; } #endif @@ -435,8 +438,8 @@ class PingResponse : public ProtoMessage { }; class DeviceInfoRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 9; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_request"; } #endif @@ -477,8 +480,8 @@ class DeviceInfo : public ProtoMessage { }; class DeviceInfoResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 219; + static constexpr uint8_t MESSAGE_TYPE = 10; + static constexpr uint8_t ESTIMATED_SIZE = 219; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif @@ -511,13 +514,11 @@ class DeviceInfoResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class ListEntitiesRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 11; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 11; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_request"; } #endif @@ -529,8 +530,8 @@ class ListEntitiesRequest : public ProtoMessage { }; class ListEntitiesDoneResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 19; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_done_response"; } #endif @@ -542,8 +543,8 @@ class ListEntitiesDoneResponse : public ProtoMessage { }; class SubscribeStatesRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 20; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 20; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_states_request"; } #endif @@ -556,8 +557,8 @@ class SubscribeStatesRequest : public ProtoMessage { #ifdef USE_BINARY_SENSOR class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 12; - static constexpr uint16_t ESTIMATED_SIZE = 51; + static constexpr uint8_t MESSAGE_TYPE = 12; + static constexpr uint8_t ESTIMATED_SIZE = 51; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_binary_sensor_response"; } #endif @@ -570,14 +571,11 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { #endif 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; }; class BinarySensorStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 21; - static constexpr uint16_t ESTIMATED_SIZE = 13; + static constexpr uint8_t MESSAGE_TYPE = 21; + static constexpr uint8_t ESTIMATED_SIZE = 13; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "binary_sensor_state_response"; } #endif @@ -590,15 +588,13 @@ class BinarySensorStateResponse : public StateResponseProtoMessage { #endif protected: - bool decode_32bit(uint32_t field_id, Proto32Bit value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif #ifdef USE_COVER class ListEntitiesCoverResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 13; - static constexpr uint16_t ESTIMATED_SIZE = 57; + static constexpr uint8_t MESSAGE_TYPE = 13; + static constexpr uint8_t ESTIMATED_SIZE = 57; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_cover_response"; } #endif @@ -614,14 +610,11 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { #endif 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; }; class CoverStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 22; - static constexpr uint16_t ESTIMATED_SIZE = 23; + static constexpr uint8_t MESSAGE_TYPE = 22; + static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "cover_state_response"; } #endif @@ -636,17 +629,14 @@ class CoverStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 30; + static constexpr uint8_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}; @@ -654,8 +644,6 @@ class CoverCommandRequest : public ProtoMessage { bool has_tilt{false}; float tilt{0.0f}; bool stop{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -668,8 +656,8 @@ class CoverCommandRequest : public ProtoMessage { #ifdef USE_FAN class ListEntitiesFanResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 14; - static constexpr uint16_t ESTIMATED_SIZE = 68; + static constexpr uint8_t MESSAGE_TYPE = 14; + static constexpr uint8_t ESTIMATED_SIZE = 68; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_fan_response"; } #endif @@ -685,14 +673,11 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { #endif 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; }; class FanStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 23; - static constexpr uint16_t ESTIMATED_SIZE = 30; + static constexpr uint8_t MESSAGE_TYPE = 23; + static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "fan_state_response"; } #endif @@ -709,18 +694,14 @@ class FanStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 31; + static constexpr uint8_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}; @@ -733,8 +714,6 @@ class FanCommandRequest : public ProtoMessage { int32_t speed_level{0}; bool has_preset_mode{false}; std::string preset_mode{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -748,8 +727,8 @@ class FanCommandRequest : public ProtoMessage { #ifdef USE_LIGHT class ListEntitiesLightResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 15; - static constexpr uint16_t ESTIMATED_SIZE = 81; + static constexpr uint8_t MESSAGE_TYPE = 15; + static constexpr uint8_t ESTIMATED_SIZE = 81; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_light_response"; } #endif @@ -768,14 +747,11 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { #endif 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; }; class LightStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 24; - static constexpr uint16_t ESTIMATED_SIZE = 67; + static constexpr uint8_t MESSAGE_TYPE = 24; + static constexpr uint8_t ESTIMATED_SIZE = 67; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_state_response"; } #endif @@ -798,18 +774,14 @@ class LightStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 32; + static constexpr uint8_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}; @@ -836,8 +808,6 @@ class LightCommandRequest : public ProtoMessage { uint32_t flash_length{0}; bool has_effect{false}; std::string effect{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -851,8 +821,8 @@ class LightCommandRequest : public ProtoMessage { #ifdef USE_SENSOR class ListEntitiesSensorResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 16; - static constexpr uint16_t ESTIMATED_SIZE = 68; + static constexpr uint8_t MESSAGE_TYPE = 16; + static constexpr uint8_t ESTIMATED_SIZE = 68; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_sensor_response"; } #endif @@ -869,14 +839,11 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { #endif 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; }; class SensorStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 25; - static constexpr uint16_t ESTIMATED_SIZE = 16; + static constexpr uint8_t MESSAGE_TYPE = 25; + static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "sensor_state_response"; } #endif @@ -889,15 +856,13 @@ class SensorStateResponse : public StateResponseProtoMessage { #endif protected: - bool decode_32bit(uint32_t field_id, Proto32Bit value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif #ifdef USE_SWITCH class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 17; - static constexpr uint16_t ESTIMATED_SIZE = 51; + static constexpr uint8_t MESSAGE_TYPE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 51; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_switch_response"; } #endif @@ -910,14 +875,11 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { #endif 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; }; class SwitchStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 26; - static constexpr uint16_t ESTIMATED_SIZE = 11; + static constexpr uint8_t MESSAGE_TYPE = 26; + static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "switch_state_response"; } #endif @@ -929,20 +891,15 @@ class SwitchStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 33; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -955,8 +912,8 @@ class SwitchCommandRequest : public ProtoMessage { #ifdef USE_TEXT_SENSOR class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 18; - static constexpr uint16_t ESTIMATED_SIZE = 49; + static constexpr uint8_t MESSAGE_TYPE = 18; + static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_text_sensor_response"; } #endif @@ -968,14 +925,11 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { #endif 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; }; class TextSensorStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 27; - static constexpr uint16_t ESTIMATED_SIZE = 20; + static constexpr uint8_t MESSAGE_TYPE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_sensor_state_response"; } #endif @@ -988,22 +942,17 @@ class TextSensorStateResponse : public StateResponseProtoMessage { #endif 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 class SubscribeLogsRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 28; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 28; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_logs_request"; } #endif enums::LogLevel level{}; bool dump_config{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1013,8 +962,8 @@ class SubscribeLogsRequest : public ProtoMessage { }; class SubscribeLogsResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 29; - static constexpr uint16_t ESTIMATED_SIZE = 13; + static constexpr uint8_t MESSAGE_TYPE = 29; + static constexpr uint8_t ESTIMATED_SIZE = 13; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_logs_response"; } #endif @@ -1028,20 +977,16 @@ class SubscribeLogsResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #ifdef USE_API_NOISE class NoiseEncryptionSetKeyRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 124; - static constexpr uint16_t ESTIMATED_SIZE = 9; + static constexpr uint8_t MESSAGE_TYPE = 124; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif std::string key{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1051,8 +996,8 @@ class NoiseEncryptionSetKeyRequest : public ProtoMessage { }; class NoiseEncryptionSetKeyResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 125; - static constexpr uint16_t ESTIMATED_SIZE = 2; + static constexpr uint8_t MESSAGE_TYPE = 125; + static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_response"; } #endif @@ -1064,13 +1009,12 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif class SubscribeHomeassistantServicesRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 34; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 34; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_homeassistant_services_request"; } #endif @@ -1095,8 +1039,8 @@ class HomeassistantServiceMap : public ProtoMessage { }; class HomeassistantServiceResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 35; - static constexpr uint16_t ESTIMATED_SIZE = 113; + static constexpr uint8_t MESSAGE_TYPE = 35; + static constexpr uint8_t ESTIMATED_SIZE = 113; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_service_response"; } #endif @@ -1112,13 +1056,11 @@ class HomeassistantServiceResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class SubscribeHomeAssistantStatesRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 38; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 38; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_home_assistant_states_request"; } #endif @@ -1130,8 +1072,8 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage { }; class SubscribeHomeAssistantStateResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 39; - static constexpr uint16_t ESTIMATED_SIZE = 20; + static constexpr uint8_t MESSAGE_TYPE = 39; + static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_home_assistant_state_response"; } #endif @@ -1145,21 +1087,17 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class HomeAssistantStateResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 40; - static constexpr uint16_t ESTIMATED_SIZE = 27; + static constexpr uint8_t MESSAGE_TYPE = 40; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "home_assistant_state_response"; } #endif std::string entity_id{}; std::string state{}; std::string attribute{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1169,8 +1107,8 @@ class HomeAssistantStateResponse : public ProtoMessage { }; class GetTimeRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 36; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 36; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_request"; } #endif @@ -1182,8 +1120,8 @@ class GetTimeRequest : public ProtoMessage { }; class GetTimeResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 37; - static constexpr uint16_t ESTIMATED_SIZE = 5; + static constexpr uint8_t MESSAGE_TYPE = 37; + static constexpr uint8_t ESTIMATED_SIZE = 5; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_response"; } #endif @@ -1197,6 +1135,7 @@ class GetTimeResponse : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; }; +#ifdef USE_API_SERVICES class ListEntitiesServicesArgument : public ProtoMessage { public: std::string name{}; @@ -1213,8 +1152,8 @@ class ListEntitiesServicesArgument : public ProtoMessage { }; class ListEntitiesServicesResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 41; - static constexpr uint16_t ESTIMATED_SIZE = 48; + static constexpr uint8_t MESSAGE_TYPE = 41; + static constexpr uint8_t ESTIMATED_SIZE = 48; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_services_response"; } #endif @@ -1228,8 +1167,6 @@ class ListEntitiesServicesResponse : public ProtoMessage { #endif protected: - bool decode_32bit(uint32_t field_id, Proto32Bit value) override; - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; class ExecuteServiceArgument : public ProtoMessage { public: @@ -1255,15 +1192,13 @@ class ExecuteServiceArgument : public ProtoMessage { }; class ExecuteServiceRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 42; - static constexpr uint16_t ESTIMATED_SIZE = 39; + static constexpr uint8_t MESSAGE_TYPE = 42; + static constexpr uint8_t ESTIMATED_SIZE = 39; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "execute_service_request"; } #endif uint32_t key{0}; std::vector args{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1272,11 +1207,12 @@ class ExecuteServiceRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +#endif #ifdef USE_CAMERA class ListEntitiesCameraResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 43; - static constexpr uint16_t ESTIMATED_SIZE = 40; + static constexpr uint8_t MESSAGE_TYPE = 43; + static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_camera_response"; } #endif @@ -1287,18 +1223,14 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 44; + static constexpr uint8_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; @@ -1308,21 +1240,16 @@ class CameraImageResponse : public ProtoMessage { #endif 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; }; class CameraImageRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 45; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 45; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "camera_image_request"; } #endif bool single{false}; bool stream{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1334,8 +1261,8 @@ class CameraImageRequest : public ProtoMessage { #ifdef USE_CLIMATE class ListEntitiesClimateResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 46; - static constexpr uint16_t ESTIMATED_SIZE = 147; + static constexpr uint8_t MESSAGE_TYPE = 46; + static constexpr uint8_t ESTIMATED_SIZE = 147; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_climate_response"; } #endif @@ -1364,14 +1291,11 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { #endif 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; }; class ClimateStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 47; - static constexpr uint16_t ESTIMATED_SIZE = 70; + static constexpr uint8_t MESSAGE_TYPE = 47; + static constexpr uint8_t ESTIMATED_SIZE = 70; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "climate_state_response"; } #endif @@ -1396,18 +1320,14 @@ class ClimateStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 48; + static constexpr uint8_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}; @@ -1430,8 +1350,6 @@ class ClimateCommandRequest : public ProtoMessage { std::string custom_preset{}; bool has_target_humidity{false}; float target_humidity{0.0f}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1445,8 +1363,8 @@ class ClimateCommandRequest : public ProtoMessage { #ifdef USE_NUMBER class ListEntitiesNumberResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 49; - static constexpr uint16_t ESTIMATED_SIZE = 75; + static constexpr uint8_t MESSAGE_TYPE = 49; + static constexpr uint8_t ESTIMATED_SIZE = 75; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_number_response"; } #endif @@ -1463,14 +1381,11 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { #endif 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; }; class NumberStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 50; - static constexpr uint16_t ESTIMATED_SIZE = 16; + static constexpr uint8_t MESSAGE_TYPE = 50; + static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "number_state_response"; } #endif @@ -1483,33 +1398,29 @@ class NumberStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 51; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif 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 class ListEntitiesSelectResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 52; - static constexpr uint16_t ESTIMATED_SIZE = 58; + static constexpr uint8_t MESSAGE_TYPE = 52; + static constexpr uint8_t ESTIMATED_SIZE = 58; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_select_response"; } #endif @@ -1521,14 +1432,11 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { #endif 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; }; class SelectStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 53; - static constexpr uint16_t ESTIMATED_SIZE = 20; + static constexpr uint8_t MESSAGE_TYPE = 53; + static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_state_response"; } #endif @@ -1541,21 +1449,15 @@ class SelectStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 54; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1563,13 +1465,14 @@ 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 class ListEntitiesSirenResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 55; - static constexpr uint16_t ESTIMATED_SIZE = 62; + static constexpr uint8_t MESSAGE_TYPE = 55; + static constexpr uint8_t ESTIMATED_SIZE = 62; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_siren_response"; } #endif @@ -1583,14 +1486,11 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage { #endif 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; }; class SirenStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 56; - static constexpr uint16_t ESTIMATED_SIZE = 11; + static constexpr uint8_t MESSAGE_TYPE = 56; + static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "siren_state_response"; } #endif @@ -1602,17 +1502,14 @@ class SirenStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 57; + static constexpr uint8_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}; @@ -1621,8 +1518,6 @@ class SirenCommandRequest : public ProtoMessage { uint32_t duration{0}; bool has_volume{false}; float volume{0.0f}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1636,8 +1531,8 @@ class SirenCommandRequest : public ProtoMessage { #ifdef USE_LOCK class ListEntitiesLockResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 58; - static constexpr uint16_t ESTIMATED_SIZE = 55; + static constexpr uint8_t MESSAGE_TYPE = 58; + static constexpr uint8_t ESTIMATED_SIZE = 55; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_lock_response"; } #endif @@ -1652,14 +1547,11 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { #endif 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; }; class LockStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 59; - static constexpr uint16_t ESTIMATED_SIZE = 11; + static constexpr uint8_t MESSAGE_TYPE = 59; + static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "lock_state_response"; } #endif @@ -1671,22 +1563,17 @@ class LockStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 60; + static constexpr uint8_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{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1700,8 +1587,8 @@ class LockCommandRequest : public ProtoMessage { #ifdef USE_BUTTON class ListEntitiesButtonResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 61; - static constexpr uint16_t ESTIMATED_SIZE = 49; + static constexpr uint8_t MESSAGE_TYPE = 61; + static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_button_response"; } #endif @@ -1713,26 +1600,21 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 62; + static constexpr uint8_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 void dump_to(std::string &out) const override; #endif 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 @@ -1755,8 +1637,8 @@ class MediaPlayerSupportedFormat : public ProtoMessage { }; class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 63; - static constexpr uint16_t ESTIMATED_SIZE = 76; + static constexpr uint8_t MESSAGE_TYPE = 63; + static constexpr uint8_t ESTIMATED_SIZE = 76; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_media_player_response"; } #endif @@ -1769,14 +1651,11 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { #endif 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; }; class MediaPlayerStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 64; - static constexpr uint16_t ESTIMATED_SIZE = 18; + static constexpr uint8_t MESSAGE_TYPE = 64; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "media_player_state_response"; } #endif @@ -1790,17 +1669,14 @@ class MediaPlayerStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 65; + static constexpr uint8_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}; @@ -1809,8 +1685,6 @@ class MediaPlayerCommandRequest : public ProtoMessage { std::string media_url{}; bool has_announcement{false}; bool announcement{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1824,14 +1698,12 @@ class MediaPlayerCommandRequest : public ProtoMessage { #ifdef USE_BLUETOOTH_PROXY class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 66; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 66; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_bluetooth_le_advertisements_request"; } #endif uint32_t flags{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1856,8 +1728,8 @@ class BluetoothServiceData : public ProtoMessage { }; class BluetoothLEAdvertisementResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 67; - static constexpr uint16_t ESTIMATED_SIZE = 107; + static constexpr uint8_t MESSAGE_TYPE = 67; + static constexpr uint8_t ESTIMATED_SIZE = 107; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_le_advertisement_response"; } #endif @@ -1875,8 +1747,6 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothLERawAdvertisement : public ProtoMessage { public: @@ -1896,8 +1766,8 @@ class BluetoothLERawAdvertisement : public ProtoMessage { }; class BluetoothLERawAdvertisementsResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 93; - static constexpr uint16_t ESTIMATED_SIZE = 34; + static constexpr uint8_t MESSAGE_TYPE = 93; + static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_le_raw_advertisements_response"; } #endif @@ -1909,12 +1779,11 @@ class BluetoothLERawAdvertisementsResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; class BluetoothDeviceRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 68; - static constexpr uint16_t ESTIMATED_SIZE = 12; + static constexpr uint8_t MESSAGE_TYPE = 68; + static constexpr uint8_t ESTIMATED_SIZE = 12; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_device_request"; } #endif @@ -1922,8 +1791,6 @@ class BluetoothDeviceRequest : public ProtoMessage { enums::BluetoothDeviceRequestType request_type{}; bool has_address_type{false}; uint32_t address_type{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1933,8 +1800,8 @@ class BluetoothDeviceRequest : public ProtoMessage { }; class BluetoothDeviceConnectionResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 69; - static constexpr uint16_t ESTIMATED_SIZE = 14; + static constexpr uint8_t MESSAGE_TYPE = 69; + static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_device_connection_response"; } #endif @@ -1949,18 +1816,15 @@ class BluetoothDeviceConnectionResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTGetServicesRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 70; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 70; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_get_services_request"; } #endif uint64_t address{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2014,8 +1878,8 @@ class BluetoothGATTService : public ProtoMessage { }; class BluetoothGATTGetServicesResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 71; - static constexpr uint16_t ESTIMATED_SIZE = 38; + static constexpr uint8_t MESSAGE_TYPE = 71; + static constexpr uint8_t ESTIMATED_SIZE = 38; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_get_services_response"; } #endif @@ -2028,13 +1892,11 @@ class BluetoothGATTGetServicesResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTGetServicesDoneResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 72; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 72; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_get_services_done_response"; } #endif @@ -2046,19 +1908,16 @@ class BluetoothGATTGetServicesDoneResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTReadRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 73; - static constexpr uint16_t ESTIMATED_SIZE = 8; + static constexpr uint8_t MESSAGE_TYPE = 73; + static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_read_request"; } #endif uint64_t address{0}; uint32_t handle{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2068,8 +1927,8 @@ class BluetoothGATTReadRequest : public ProtoMessage { }; class BluetoothGATTReadResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 74; - static constexpr uint16_t ESTIMATED_SIZE = 17; + static constexpr uint8_t MESSAGE_TYPE = 74; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_read_response"; } #endif @@ -2083,13 +1942,11 @@ class BluetoothGATTReadResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTWriteRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 75; - static constexpr uint16_t ESTIMATED_SIZE = 19; + static constexpr uint8_t MESSAGE_TYPE = 75; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_request"; } #endif @@ -2097,8 +1954,6 @@ class BluetoothGATTWriteRequest : public ProtoMessage { uint32_t handle{0}; bool response{false}; std::string data{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2109,15 +1964,13 @@ class BluetoothGATTWriteRequest : public ProtoMessage { }; class BluetoothGATTReadDescriptorRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 76; - static constexpr uint16_t ESTIMATED_SIZE = 8; + static constexpr uint8_t MESSAGE_TYPE = 76; + static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_read_descriptor_request"; } #endif uint64_t address{0}; uint32_t handle{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2127,16 +1980,14 @@ class BluetoothGATTReadDescriptorRequest : public ProtoMessage { }; class BluetoothGATTWriteDescriptorRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 77; - static constexpr uint16_t ESTIMATED_SIZE = 17; + static constexpr uint8_t MESSAGE_TYPE = 77; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } #endif uint64_t address{0}; uint32_t handle{0}; std::string data{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2147,16 +1998,14 @@ class BluetoothGATTWriteDescriptorRequest : public ProtoMessage { }; class BluetoothGATTNotifyRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 78; - static constexpr uint16_t ESTIMATED_SIZE = 10; + static constexpr uint8_t MESSAGE_TYPE = 78; + static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_notify_request"; } #endif uint64_t address{0}; uint32_t handle{0}; bool enable{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2166,8 +2015,8 @@ class BluetoothGATTNotifyRequest : public ProtoMessage { }; class BluetoothGATTNotifyDataResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 79; - static constexpr uint16_t ESTIMATED_SIZE = 17; + static constexpr uint8_t MESSAGE_TYPE = 79; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; } #endif @@ -2181,13 +2030,11 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 80; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 80; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_bluetooth_connections_free_request"; } #endif @@ -2199,8 +2046,8 @@ class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { }; class BluetoothConnectionsFreeResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 81; - static constexpr uint16_t ESTIMATED_SIZE = 16; + static constexpr uint8_t MESSAGE_TYPE = 81; + static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_connections_free_response"; } #endif @@ -2214,12 +2061,11 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTErrorResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 82; - static constexpr uint16_t ESTIMATED_SIZE = 12; + static constexpr uint8_t MESSAGE_TYPE = 82; + static constexpr uint8_t ESTIMATED_SIZE = 12; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_error_response"; } #endif @@ -2233,12 +2079,11 @@ class BluetoothGATTErrorResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTWriteResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 83; - static constexpr uint16_t ESTIMATED_SIZE = 8; + static constexpr uint8_t MESSAGE_TYPE = 83; + static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_write_response"; } #endif @@ -2251,12 +2096,11 @@ class BluetoothGATTWriteResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothGATTNotifyResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 84; - static constexpr uint16_t ESTIMATED_SIZE = 8; + static constexpr uint8_t MESSAGE_TYPE = 84; + static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_notify_response"; } #endif @@ -2269,12 +2113,11 @@ class BluetoothGATTNotifyResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothDevicePairingResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 85; - static constexpr uint16_t ESTIMATED_SIZE = 10; + static constexpr uint8_t MESSAGE_TYPE = 85; + static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_device_pairing_response"; } #endif @@ -2288,12 +2131,11 @@ class BluetoothDevicePairingResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothDeviceUnpairingResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 86; - static constexpr uint16_t ESTIMATED_SIZE = 10; + static constexpr uint8_t MESSAGE_TYPE = 86; + static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_device_unpairing_response"; } #endif @@ -2307,12 +2149,11 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 87; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 87; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "unsubscribe_bluetooth_le_advertisements_request"; } #endif @@ -2324,8 +2165,8 @@ class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { }; class BluetoothDeviceClearCacheResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 88; - static constexpr uint16_t ESTIMATED_SIZE = 10; + static constexpr uint8_t MESSAGE_TYPE = 88; + static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_device_clear_cache_response"; } #endif @@ -2339,12 +2180,11 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothScannerStateResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 126; - static constexpr uint16_t ESTIMATED_SIZE = 4; + static constexpr uint8_t MESSAGE_TYPE = 126; + static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_scanner_state_response"; } #endif @@ -2357,18 +2197,15 @@ class BluetoothScannerStateResponse : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class BluetoothScannerSetModeRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 127; - static constexpr uint16_t ESTIMATED_SIZE = 2; + static constexpr uint8_t MESSAGE_TYPE = 127; + static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_scanner_set_mode_request"; } #endif enums::BluetoothScannerMode mode{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2380,15 +2217,13 @@ class BluetoothScannerSetModeRequest : public ProtoMessage { #ifdef USE_VOICE_ASSISTANT class SubscribeVoiceAssistantRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 89; - static constexpr uint16_t ESTIMATED_SIZE = 6; + static constexpr uint8_t MESSAGE_TYPE = 89; + static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_voice_assistant_request"; } #endif bool subscribe{false}; uint32_t flags{0}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2413,8 +2248,8 @@ class VoiceAssistantAudioSettings : public ProtoMessage { }; class VoiceAssistantRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 90; - static constexpr uint16_t ESTIMATED_SIZE = 41; + static constexpr uint8_t MESSAGE_TYPE = 90; + static constexpr uint8_t ESTIMATED_SIZE = 41; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_request"; } #endif @@ -2430,20 +2265,16 @@ class VoiceAssistantRequest : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class VoiceAssistantResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 91; - static constexpr uint16_t ESTIMATED_SIZE = 6; + static constexpr uint8_t MESSAGE_TYPE = 91; + static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_response"; } #endif uint32_t port{0}; bool error{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2466,15 +2297,13 @@ class VoiceAssistantEventData : public ProtoMessage { }; class VoiceAssistantEventResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 92; - static constexpr uint16_t ESTIMATED_SIZE = 36; + static constexpr uint8_t MESSAGE_TYPE = 92; + static constexpr uint8_t ESTIMATED_SIZE = 36; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_event_response"; } #endif enums::VoiceAssistantEvent event_type{}; std::vector data{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2485,8 +2314,8 @@ class VoiceAssistantEventResponse : public ProtoMessage { }; class VoiceAssistantAudio : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 106; - static constexpr uint16_t ESTIMATED_SIZE = 11; + static constexpr uint8_t MESSAGE_TYPE = 106; + static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_audio"; } #endif @@ -2504,8 +2333,8 @@ class VoiceAssistantAudio : public ProtoMessage { }; class VoiceAssistantTimerEventResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 115; - static constexpr uint16_t ESTIMATED_SIZE = 30; + static constexpr uint8_t MESSAGE_TYPE = 115; + static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_timer_event_response"; } #endif @@ -2515,8 +2344,6 @@ class VoiceAssistantTimerEventResponse : public ProtoMessage { uint32_t total_seconds{0}; uint32_t seconds_left{0}; bool is_active{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2527,8 +2354,8 @@ class VoiceAssistantTimerEventResponse : public ProtoMessage { }; class VoiceAssistantAnnounceRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 119; - static constexpr uint16_t ESTIMATED_SIZE = 29; + static constexpr uint8_t MESSAGE_TYPE = 119; + static constexpr uint8_t ESTIMATED_SIZE = 29; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_announce_request"; } #endif @@ -2536,8 +2363,6 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage { std::string text{}; std::string preannounce_media_id{}; bool start_conversation{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2548,8 +2373,8 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage { }; class VoiceAssistantAnnounceFinished : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 120; - static constexpr uint16_t ESTIMATED_SIZE = 2; + static constexpr uint8_t MESSAGE_TYPE = 120; + static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_announce_finished"; } #endif @@ -2561,7 +2386,6 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class VoiceAssistantWakeWord : public ProtoMessage { public: @@ -2579,8 +2403,8 @@ class VoiceAssistantWakeWord : public ProtoMessage { }; class VoiceAssistantConfigurationRequest : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 121; - static constexpr uint16_t ESTIMATED_SIZE = 0; + static constexpr uint8_t MESSAGE_TYPE = 121; + static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_configuration_request"; } #endif @@ -2592,8 +2416,8 @@ class VoiceAssistantConfigurationRequest : public ProtoMessage { }; class VoiceAssistantConfigurationResponse : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 122; - static constexpr uint16_t ESTIMATED_SIZE = 56; + static constexpr uint8_t MESSAGE_TYPE = 122; + static constexpr uint8_t ESTIMATED_SIZE = 56; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_configuration_response"; } #endif @@ -2607,19 +2431,15 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { #endif protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class VoiceAssistantSetConfiguration : public ProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 123; - static constexpr uint16_t ESTIMATED_SIZE = 18; + static constexpr uint8_t MESSAGE_TYPE = 123; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_set_configuration"; } #endif std::vector active_wake_words{}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2631,8 +2451,8 @@ class VoiceAssistantSetConfiguration : public ProtoMessage { #ifdef USE_ALARM_CONTROL_PANEL class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 94; - static constexpr uint16_t ESTIMATED_SIZE = 48; + static constexpr uint8_t MESSAGE_TYPE = 94; + static constexpr uint8_t ESTIMATED_SIZE = 48; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_alarm_control_panel_response"; } #endif @@ -2646,14 +2466,11 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { #endif 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; }; class AlarmControlPanelStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 95; - static constexpr uint16_t ESTIMATED_SIZE = 11; + static constexpr uint8_t MESSAGE_TYPE = 95; + static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "alarm_control_panel_state_response"; } #endif @@ -2665,21 +2482,16 @@ class AlarmControlPanelStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 96; + static constexpr uint8_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; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2693,8 +2505,8 @@ class AlarmControlPanelCommandRequest : public ProtoMessage { #ifdef USE_TEXT class ListEntitiesTextResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 97; - static constexpr uint16_t ESTIMATED_SIZE = 59; + static constexpr uint8_t MESSAGE_TYPE = 97; + static constexpr uint8_t ESTIMATED_SIZE = 59; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_text_response"; } #endif @@ -2709,14 +2521,11 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { #endif 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; }; class TextStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 98; - static constexpr uint16_t ESTIMATED_SIZE = 20; + static constexpr uint8_t MESSAGE_TYPE = 98; + static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_state_response"; } #endif @@ -2729,21 +2538,15 @@ class TextStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 99; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2751,13 +2554,14 @@ 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 class ListEntitiesDateResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 100; - static constexpr uint16_t ESTIMATED_SIZE = 40; + static constexpr uint8_t MESSAGE_TYPE = 100; + static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_date_response"; } #endif @@ -2768,14 +2572,11 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { #endif 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; }; class DateStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 101; - static constexpr uint16_t ESTIMATED_SIZE = 23; + static constexpr uint8_t MESSAGE_TYPE = 101; + static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "date_state_response"; } #endif @@ -2790,22 +2591,17 @@ class DateStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 102; + static constexpr uint8_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}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2818,8 +2614,8 @@ class DateCommandRequest : public ProtoMessage { #ifdef USE_DATETIME_TIME class ListEntitiesTimeResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 103; - static constexpr uint16_t ESTIMATED_SIZE = 40; + static constexpr uint8_t MESSAGE_TYPE = 103; + static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_time_response"; } #endif @@ -2830,14 +2626,11 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { #endif 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; }; class TimeStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 104; - static constexpr uint16_t ESTIMATED_SIZE = 23; + static constexpr uint8_t MESSAGE_TYPE = 104; + static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "time_state_response"; } #endif @@ -2852,22 +2645,17 @@ class TimeStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 105; + static constexpr uint8_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}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2880,8 +2668,8 @@ class TimeCommandRequest : public ProtoMessage { #ifdef USE_EVENT class ListEntitiesEventResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 107; - static constexpr uint16_t ESTIMATED_SIZE = 67; + static constexpr uint8_t MESSAGE_TYPE = 107; + static constexpr uint8_t ESTIMATED_SIZE = 67; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_event_response"; } #endif @@ -2894,14 +2682,11 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { #endif 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; }; class EventResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 108; - static constexpr uint16_t ESTIMATED_SIZE = 18; + static constexpr uint8_t MESSAGE_TYPE = 108; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "event_response"; } #endif @@ -2913,16 +2698,13 @@ class EventResponse : public StateResponseProtoMessage { #endif 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_VALVE class ListEntitiesValveResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 109; - static constexpr uint16_t ESTIMATED_SIZE = 55; + static constexpr uint8_t MESSAGE_TYPE = 109; + static constexpr uint8_t ESTIMATED_SIZE = 55; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_valve_response"; } #endif @@ -2937,14 +2719,11 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { #endif 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; }; class ValveStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 110; - static constexpr uint16_t ESTIMATED_SIZE = 16; + static constexpr uint8_t MESSAGE_TYPE = 110; + static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "valve_state_response"; } #endif @@ -2957,22 +2736,17 @@ class ValveStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 111; + static constexpr uint8_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}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2985,8 +2759,8 @@ class ValveCommandRequest : public ProtoMessage { #ifdef USE_DATETIME_DATETIME class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 112; - static constexpr uint16_t ESTIMATED_SIZE = 40; + static constexpr uint8_t MESSAGE_TYPE = 112; + static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_date_time_response"; } #endif @@ -2997,14 +2771,11 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { #endif 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; }; class DateTimeStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 113; - static constexpr uint16_t ESTIMATED_SIZE = 16; + static constexpr uint8_t MESSAGE_TYPE = 113; + static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "date_time_state_response"; } #endif @@ -3017,33 +2788,29 @@ class DateTimeStateResponse : public StateResponseProtoMessage { #endif protected: - 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 uint8_t MESSAGE_TYPE = 114; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif 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 class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 116; - static constexpr uint16_t ESTIMATED_SIZE = 49; + static constexpr uint8_t MESSAGE_TYPE = 116; + static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_update_response"; } #endif @@ -3055,14 +2822,11 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { #endif 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; }; class UpdateStateResponse : public StateResponseProtoMessage { public: - static constexpr uint16_t MESSAGE_TYPE = 117; - static constexpr uint16_t ESTIMATED_SIZE = 65; + static constexpr uint8_t MESSAGE_TYPE = 117; + static constexpr uint8_t ESTIMATED_SIZE = 65; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "update_state_response"; } #endif @@ -3082,21 +2846,15 @@ class UpdateStateResponse : public StateResponseProtoMessage { #endif 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; }; -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 uint8_t MESSAGE_TYPE = 118; + static constexpr uint8_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; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d135294319..647ef3881a 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -162,6 +162,7 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "UNKNOWN"; } } +#ifdef USE_API_SERVICES template<> const char *proto_enum_to_string(enums::ServiceArgType value) { switch (value) { case enums::SERVICE_ARG_TYPE_BOOL: @@ -184,6 +185,7 @@ template<> const char *proto_enum_to_string(enums::Servic return "UNKNOWN"; } } +#endif #ifdef USE_CLIMATE template<> const char *proto_enum_to_string(enums::ClimateMode value) { switch (value) { @@ -600,12 +602,12 @@ void HelloRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" api_version_major: "); - sprintf(buffer, "%" PRIu32, this->api_version_major); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->api_version_major); out.append(buffer); out.append("\n"); out.append(" api_version_minor: "); - sprintf(buffer, "%" PRIu32, this->api_version_minor); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->api_version_minor); out.append(buffer); out.append("\n"); out.append("}"); @@ -614,12 +616,12 @@ void HelloResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" api_version_major: "); - sprintf(buffer, "%" PRIu32, this->api_version_major); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->api_version_major); out.append(buffer); out.append("\n"); out.append(" api_version_minor: "); - sprintf(buffer, "%" PRIu32, this->api_version_minor); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->api_version_minor); out.append(buffer); out.append("\n"); @@ -657,7 +659,7 @@ void AreaInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AreaInfo {\n"); out.append(" area_id: "); - sprintf(buffer, "%" PRIu32, this->area_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->area_id); out.append(buffer); out.append("\n"); @@ -670,7 +672,7 @@ void DeviceInfo::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DeviceInfo {\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); @@ -679,7 +681,7 @@ void DeviceInfo::dump_to(std::string &out) const { out.append("\n"); out.append(" area_id: "); - sprintf(buffer, "%" PRIu32, this->area_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->area_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -724,17 +726,17 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" webserver_port: "); - sprintf(buffer, "%" PRIu32, this->webserver_port); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->webserver_port); out.append(buffer); out.append("\n"); out.append(" legacy_bluetooth_proxy_version: "); - sprintf(buffer, "%" PRIu32, this->legacy_bluetooth_proxy_version); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->legacy_bluetooth_proxy_version); out.append(buffer); out.append("\n"); out.append(" bluetooth_proxy_feature_flags: "); - sprintf(buffer, "%" PRIu32, this->bluetooth_proxy_feature_flags); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->bluetooth_proxy_feature_flags); out.append(buffer); out.append("\n"); @@ -747,12 +749,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" legacy_voice_assistant_version: "); - sprintf(buffer, "%" PRIu32, this->legacy_voice_assistant_version); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->legacy_voice_assistant_version); out.append(buffer); out.append("\n"); out.append(" voice_assistant_feature_flags: "); - sprintf(buffer, "%" PRIu32, this->voice_assistant_feature_flags); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->voice_assistant_feature_flags); out.append(buffer); out.append("\n"); @@ -797,7 +799,7 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -826,7 +828,7 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -835,7 +837,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -848,7 +850,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -863,7 +865,7 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -904,7 +906,7 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -913,7 +915,7 @@ void CoverStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -922,12 +924,12 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" position: "); - sprintf(buffer, "%g", this->position); + snprintf(buffer, sizeof(buffer), "%g", this->position); out.append(buffer); out.append("\n"); out.append(" tilt: "); - sprintf(buffer, "%g", this->tilt); + snprintf(buffer, sizeof(buffer), "%g", this->tilt); out.append(buffer); out.append("\n"); @@ -936,7 +938,7 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -945,7 +947,7 @@ void CoverCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -962,7 +964,7 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" position: "); - sprintf(buffer, "%g", this->position); + snprintf(buffer, sizeof(buffer), "%g", this->position); out.append(buffer); out.append("\n"); @@ -971,13 +973,18 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" tilt: "); - sprintf(buffer, "%g", this->tilt); + snprintf(buffer, sizeof(buffer), "%g", this->tilt); out.append(buffer); out.append("\n"); 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 @@ -990,7 +997,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1011,7 +1018,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" supported_speed_count: "); - sprintf(buffer, "%" PRId32, this->supported_speed_count); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->supported_speed_count); out.append(buffer); out.append("\n"); @@ -1034,7 +1041,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { } out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1043,7 +1050,7 @@ void FanStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1064,7 +1071,7 @@ void FanStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" speed_level: "); - sprintf(buffer, "%" PRId32, this->speed_level); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->speed_level); out.append(buffer); out.append("\n"); @@ -1073,7 +1080,7 @@ void FanStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1082,7 +1089,7 @@ void FanCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1123,7 +1130,7 @@ void FanCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" speed_level: "); - sprintf(buffer, "%" PRId32, this->speed_level); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->speed_level); out.append(buffer); out.append("\n"); @@ -1134,6 +1141,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 @@ -1146,7 +1158,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1177,12 +1189,12 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" min_mireds: "); - sprintf(buffer, "%g", this->min_mireds); + snprintf(buffer, sizeof(buffer), "%g", this->min_mireds); out.append(buffer); out.append("\n"); out.append(" max_mireds: "); - sprintf(buffer, "%g", this->max_mireds); + snprintf(buffer, sizeof(buffer), "%g", this->max_mireds); out.append(buffer); out.append("\n"); @@ -1205,7 +1217,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1214,7 +1226,7 @@ void LightStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1223,7 +1235,7 @@ void LightStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" brightness: "); - sprintf(buffer, "%g", this->brightness); + snprintf(buffer, sizeof(buffer), "%g", this->brightness); out.append(buffer); out.append("\n"); @@ -1232,42 +1244,42 @@ void LightStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" color_brightness: "); - sprintf(buffer, "%g", this->color_brightness); + snprintf(buffer, sizeof(buffer), "%g", this->color_brightness); out.append(buffer); out.append("\n"); out.append(" red: "); - sprintf(buffer, "%g", this->red); + snprintf(buffer, sizeof(buffer), "%g", this->red); out.append(buffer); out.append("\n"); out.append(" green: "); - sprintf(buffer, "%g", this->green); + snprintf(buffer, sizeof(buffer), "%g", this->green); out.append(buffer); out.append("\n"); out.append(" blue: "); - sprintf(buffer, "%g", this->blue); + snprintf(buffer, sizeof(buffer), "%g", this->blue); out.append(buffer); out.append("\n"); out.append(" white: "); - sprintf(buffer, "%g", this->white); + snprintf(buffer, sizeof(buffer), "%g", this->white); out.append(buffer); out.append("\n"); out.append(" color_temperature: "); - sprintf(buffer, "%g", this->color_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->color_temperature); out.append(buffer); out.append("\n"); out.append(" cold_white: "); - sprintf(buffer, "%g", this->cold_white); + snprintf(buffer, sizeof(buffer), "%g", this->cold_white); out.append(buffer); out.append("\n"); out.append(" warm_white: "); - sprintf(buffer, "%g", this->warm_white); + snprintf(buffer, sizeof(buffer), "%g", this->warm_white); out.append(buffer); out.append("\n"); @@ -1276,7 +1288,7 @@ void LightStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1285,7 +1297,7 @@ void LightCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1302,7 +1314,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" brightness: "); - sprintf(buffer, "%g", this->brightness); + snprintf(buffer, sizeof(buffer), "%g", this->brightness); out.append(buffer); out.append("\n"); @@ -1319,7 +1331,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" color_brightness: "); - sprintf(buffer, "%g", this->color_brightness); + snprintf(buffer, sizeof(buffer), "%g", this->color_brightness); out.append(buffer); out.append("\n"); @@ -1328,17 +1340,17 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" red: "); - sprintf(buffer, "%g", this->red); + snprintf(buffer, sizeof(buffer), "%g", this->red); out.append(buffer); out.append("\n"); out.append(" green: "); - sprintf(buffer, "%g", this->green); + snprintf(buffer, sizeof(buffer), "%g", this->green); out.append(buffer); out.append("\n"); out.append(" blue: "); - sprintf(buffer, "%g", this->blue); + snprintf(buffer, sizeof(buffer), "%g", this->blue); out.append(buffer); out.append("\n"); @@ -1347,7 +1359,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" white: "); - sprintf(buffer, "%g", this->white); + snprintf(buffer, sizeof(buffer), "%g", this->white); out.append(buffer); out.append("\n"); @@ -1356,7 +1368,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" color_temperature: "); - sprintf(buffer, "%g", this->color_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->color_temperature); out.append(buffer); out.append("\n"); @@ -1365,7 +1377,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" cold_white: "); - sprintf(buffer, "%g", this->cold_white); + snprintf(buffer, sizeof(buffer), "%g", this->cold_white); out.append(buffer); out.append("\n"); @@ -1374,7 +1386,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" warm_white: "); - sprintf(buffer, "%g", this->warm_white); + snprintf(buffer, sizeof(buffer), "%g", this->warm_white); out.append(buffer); out.append("\n"); @@ -1383,7 +1395,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" transition_length: "); - sprintf(buffer, "%" PRIu32, this->transition_length); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->transition_length); out.append(buffer); out.append("\n"); @@ -1392,7 +1404,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" flash_length: "); - sprintf(buffer, "%" PRIu32, this->flash_length); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->flash_length); out.append(buffer); out.append("\n"); @@ -1403,6 +1415,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 @@ -1415,7 +1432,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1432,7 +1449,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" accuracy_decimals: "); - sprintf(buffer, "%" PRId32, this->accuracy_decimals); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->accuracy_decimals); out.append(buffer); out.append("\n"); @@ -1461,7 +1478,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1470,12 +1487,12 @@ void SensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" state: "); - sprintf(buffer, "%g", this->state); + snprintf(buffer, sizeof(buffer), "%g", this->state); out.append(buffer); out.append("\n"); @@ -1484,7 +1501,7 @@ void SensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1499,7 +1516,7 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1528,7 +1545,7 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1537,7 +1554,7 @@ void SwitchStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1546,7 +1563,7 @@ void SwitchStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1555,13 +1572,18 @@ void SwitchCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); 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 @@ -1574,7 +1596,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1599,7 +1621,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1608,7 +1630,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1621,7 +1643,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1758,11 +1780,12 @@ void GetTimeResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); - sprintf(buffer, "%" PRIu32, this->epoch_seconds); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->epoch_seconds); out.append(buffer); out.append("\n"); out.append("}"); } +#ifdef USE_API_SERVICES void ListEntitiesServicesArgument::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ListEntitiesServicesArgument {\n"); @@ -1783,7 +1806,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1802,12 +1825,12 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append("\n"); out.append(" legacy_int: "); - sprintf(buffer, "%" PRId32, this->legacy_int); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->legacy_int); out.append(buffer); out.append("\n"); out.append(" float_: "); - sprintf(buffer, "%g", this->float_); + snprintf(buffer, sizeof(buffer), "%g", this->float_); out.append(buffer); out.append("\n"); @@ -1816,7 +1839,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append("\n"); out.append(" int_: "); - sprintf(buffer, "%" PRId32, this->int_); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->int_); out.append(buffer); out.append("\n"); @@ -1828,14 +1851,14 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { for (const auto &it : this->int_array) { out.append(" int_array: "); - sprintf(buffer, "%" PRId32, it); + snprintf(buffer, sizeof(buffer), "%" PRId32, it); out.append(buffer); out.append("\n"); } for (const auto &it : this->float_array) { out.append(" float_array: "); - sprintf(buffer, "%g", it); + snprintf(buffer, sizeof(buffer), "%g", it); out.append(buffer); out.append("\n"); } @@ -1851,7 +1874,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1862,6 +1885,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { } out.append("}"); } +#endif #ifdef USE_CAMERA void ListEntitiesCameraResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -1871,7 +1895,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1892,7 +1916,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -1901,7 +1925,7 @@ void CameraImageResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1912,6 +1936,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 { @@ -1936,7 +1965,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1959,17 +1988,17 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { } out.append(" visual_min_temperature: "); - sprintf(buffer, "%g", this->visual_min_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->visual_min_temperature); out.append(buffer); out.append("\n"); out.append(" visual_max_temperature: "); - sprintf(buffer, "%g", this->visual_max_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->visual_max_temperature); out.append(buffer); out.append("\n"); out.append(" visual_target_temperature_step: "); - sprintf(buffer, "%g", this->visual_target_temperature_step); + snprintf(buffer, sizeof(buffer), "%g", this->visual_target_temperature_step); out.append(buffer); out.append("\n"); @@ -2024,7 +2053,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" visual_current_temperature_step: "); - sprintf(buffer, "%g", this->visual_current_temperature_step); + snprintf(buffer, sizeof(buffer), "%g", this->visual_current_temperature_step); out.append(buffer); out.append("\n"); @@ -2037,17 +2066,17 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" visual_min_humidity: "); - sprintf(buffer, "%g", this->visual_min_humidity); + snprintf(buffer, sizeof(buffer), "%g", this->visual_min_humidity); out.append(buffer); out.append("\n"); out.append(" visual_max_humidity: "); - sprintf(buffer, "%g", this->visual_max_humidity); + snprintf(buffer, sizeof(buffer), "%g", this->visual_max_humidity); out.append(buffer); out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2056,7 +2085,7 @@ void ClimateStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2065,22 +2094,22 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" current_temperature: "); - sprintf(buffer, "%g", this->current_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->current_temperature); out.append(buffer); out.append("\n"); out.append(" target_temperature: "); - sprintf(buffer, "%g", this->target_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature); out.append(buffer); out.append("\n"); out.append(" target_temperature_low: "); - sprintf(buffer, "%g", this->target_temperature_low); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature_low); out.append(buffer); out.append("\n"); out.append(" target_temperature_high: "); - sprintf(buffer, "%g", this->target_temperature_high); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature_high); out.append(buffer); out.append("\n"); @@ -2113,17 +2142,17 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" current_humidity: "); - sprintf(buffer, "%g", this->current_humidity); + snprintf(buffer, sizeof(buffer), "%g", this->current_humidity); out.append(buffer); out.append("\n"); out.append(" target_humidity: "); - sprintf(buffer, "%g", this->target_humidity); + snprintf(buffer, sizeof(buffer), "%g", this->target_humidity); out.append(buffer); out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2132,7 +2161,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2149,7 +2178,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" target_temperature: "); - sprintf(buffer, "%g", this->target_temperature); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature); out.append(buffer); out.append("\n"); @@ -2158,7 +2187,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" target_temperature_low: "); - sprintf(buffer, "%g", this->target_temperature_low); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature_low); out.append(buffer); out.append("\n"); @@ -2167,7 +2196,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" target_temperature_high: "); - sprintf(buffer, "%g", this->target_temperature_high); + snprintf(buffer, sizeof(buffer), "%g", this->target_temperature_high); out.append(buffer); out.append("\n"); @@ -2224,7 +2253,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" target_humidity: "); - sprintf(buffer, "%g", this->target_humidity); + 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("}"); @@ -2239,7 +2273,7 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2252,17 +2286,17 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" min_value: "); - sprintf(buffer, "%g", this->min_value); + snprintf(buffer, sizeof(buffer), "%g", this->min_value); out.append(buffer); out.append("\n"); out.append(" max_value: "); - sprintf(buffer, "%g", this->max_value); + snprintf(buffer, sizeof(buffer), "%g", this->max_value); out.append(buffer); out.append("\n"); out.append(" step: "); - sprintf(buffer, "%g", this->step); + snprintf(buffer, sizeof(buffer), "%g", this->step); out.append(buffer); out.append("\n"); @@ -2287,7 +2321,7 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2296,12 +2330,12 @@ void NumberStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" state: "); - sprintf(buffer, "%g", this->state); + snprintf(buffer, sizeof(buffer), "%g", this->state); out.append(buffer); out.append("\n"); @@ -2310,7 +2344,7 @@ void NumberStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2319,12 +2353,17 @@ void NumberCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" state: "); - sprintf(buffer, "%g", this->state); + 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("}"); @@ -2339,7 +2378,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2366,7 +2405,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2375,7 +2414,7 @@ void SelectStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2388,7 +2427,7 @@ void SelectStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2397,13 +2436,18 @@ void SelectCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); 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 @@ -2416,7 +2460,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2451,7 +2495,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2460,7 +2504,7 @@ void SirenStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SirenStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2469,7 +2513,7 @@ void SirenStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2478,7 +2522,7 @@ void SirenCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SirenCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2503,7 +2547,7 @@ void SirenCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" duration: "); - sprintf(buffer, "%" PRIu32, this->duration); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->duration); out.append(buffer); out.append("\n"); @@ -2512,7 +2556,12 @@ void SirenCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" volume: "); - sprintf(buffer, "%g", this->volume); + 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("}"); @@ -2527,7 +2576,7 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2564,7 +2613,7 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2573,7 +2622,7 @@ void LockStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LockStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2582,7 +2631,7 @@ void LockStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2591,7 +2640,7 @@ void LockCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LockCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2606,6 +2655,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 @@ -2618,7 +2672,7 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2643,7 +2697,7 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2652,7 +2706,12 @@ void ButtonCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ButtonCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + 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("}"); @@ -2667,12 +2726,12 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const { out.append("\n"); out.append(" sample_rate: "); - sprintf(buffer, "%" PRIu32, this->sample_rate); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->sample_rate); out.append(buffer); out.append("\n"); out.append(" num_channels: "); - sprintf(buffer, "%" PRIu32, this->num_channels); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->num_channels); out.append(buffer); out.append("\n"); @@ -2681,7 +2740,7 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const { out.append("\n"); out.append(" sample_bytes: "); - sprintf(buffer, "%" PRIu32, this->sample_bytes); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->sample_bytes); out.append(buffer); out.append("\n"); out.append("}"); @@ -2694,7 +2753,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2725,7 +2784,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { } out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2734,7 +2793,7 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("MediaPlayerStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2743,7 +2802,7 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" volume: "); - sprintf(buffer, "%g", this->volume); + snprintf(buffer, sizeof(buffer), "%g", this->volume); out.append(buffer); out.append("\n"); @@ -2752,7 +2811,7 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -2761,7 +2820,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("MediaPlayerCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2778,7 +2837,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" volume: "); - sprintf(buffer, "%g", this->volume); + snprintf(buffer, sizeof(buffer), "%g", this->volume); out.append(buffer); out.append("\n"); @@ -2797,6 +2856,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 @@ -2805,7 +2869,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const __attribute__((unused)) char buffer[64]; out.append("SubscribeBluetoothLEAdvertisementsRequest {\n"); out.append(" flags: "); - sprintf(buffer, "%" PRIu32, this->flags); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->flags); out.append(buffer); out.append("\n"); out.append("}"); @@ -2819,7 +2883,7 @@ void BluetoothServiceData::dump_to(std::string &out) const { for (const auto &it : this->legacy_data) { out.append(" legacy_data: "); - sprintf(buffer, "%" PRIu32, it); + snprintf(buffer, sizeof(buffer), "%" PRIu32, it); out.append(buffer); out.append("\n"); } @@ -2833,7 +2897,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothLEAdvertisementResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -2842,7 +2906,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" rssi: "); - sprintf(buffer, "%" PRId32, this->rssi); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->rssi); out.append(buffer); out.append("\n"); @@ -2865,7 +2929,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { } out.append(" address_type: "); - sprintf(buffer, "%" PRIu32, this->address_type); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); out.append("}"); @@ -2874,17 +2938,17 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothLERawAdvertisement {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" rssi: "); - sprintf(buffer, "%" PRId32, this->rssi); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->rssi); out.append(buffer); out.append("\n"); out.append(" address_type: "); - sprintf(buffer, "%" PRIu32, this->address_type); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); @@ -2907,7 +2971,7 @@ void BluetoothDeviceRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothDeviceRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -2920,7 +2984,7 @@ void BluetoothDeviceRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" address_type: "); - sprintf(buffer, "%" PRIu32, this->address_type); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); out.append("}"); @@ -2929,7 +2993,7 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothDeviceConnectionResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -2938,12 +3002,12 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" mtu: "); - sprintf(buffer, "%" PRIu32, this->mtu); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->mtu); out.append(buffer); out.append("\n"); out.append(" error: "); - sprintf(buffer, "%" PRId32, this->error); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -2952,7 +3016,7 @@ void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTGetServicesRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append("}"); @@ -2962,13 +3026,13 @@ void BluetoothGATTDescriptor::dump_to(std::string &out) const { out.append("BluetoothGATTDescriptor {\n"); for (const auto &it : this->uuid) { out.append(" uuid: "); - sprintf(buffer, "%llu", it); + snprintf(buffer, sizeof(buffer), "%llu", it); out.append(buffer); out.append("\n"); } out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -2978,18 +3042,18 @@ void BluetoothGATTCharacteristic::dump_to(std::string &out) const { out.append("BluetoothGATTCharacteristic {\n"); for (const auto &it : this->uuid) { out.append(" uuid: "); - sprintf(buffer, "%llu", it); + snprintf(buffer, sizeof(buffer), "%llu", it); out.append(buffer); out.append("\n"); } out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append(" properties: "); - sprintf(buffer, "%" PRIu32, this->properties); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->properties); out.append(buffer); out.append("\n"); @@ -3005,13 +3069,13 @@ void BluetoothGATTService::dump_to(std::string &out) const { out.append("BluetoothGATTService {\n"); for (const auto &it : this->uuid) { out.append(" uuid: "); - sprintf(buffer, "%llu", it); + snprintf(buffer, sizeof(buffer), "%llu", it); out.append(buffer); out.append("\n"); } out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3026,7 +3090,7 @@ void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTGetServicesResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -3041,7 +3105,7 @@ void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTGetServicesDoneResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append("}"); @@ -3050,12 +3114,12 @@ void BluetoothGATTReadRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTReadRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -3064,12 +3128,12 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTReadResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3082,12 +3146,12 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTWriteRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3104,12 +3168,12 @@ void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTReadDescriptorRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -3118,12 +3182,12 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTWriteDescriptorRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3136,12 +3200,12 @@ void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTNotifyRequest {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3154,12 +3218,12 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTNotifyDataResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -3175,18 +3239,18 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothConnectionsFreeResponse {\n"); out.append(" free: "); - sprintf(buffer, "%" PRIu32, this->free); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->free); out.append(buffer); out.append("\n"); out.append(" limit: "); - sprintf(buffer, "%" PRIu32, this->limit); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->limit); out.append(buffer); out.append("\n"); for (const auto &it : this->allocated) { out.append(" allocated: "); - sprintf(buffer, "%llu", it); + snprintf(buffer, sizeof(buffer), "%llu", it); out.append(buffer); out.append("\n"); } @@ -3196,17 +3260,17 @@ void BluetoothGATTErrorResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTErrorResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append(" error: "); - sprintf(buffer, "%" PRId32, this->error); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -3215,12 +3279,12 @@ void BluetoothGATTWriteResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTWriteResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -3229,12 +3293,12 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothGATTNotifyResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%" PRIu32, this->handle); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -3243,7 +3307,7 @@ void BluetoothDevicePairingResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothDevicePairingResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -3252,7 +3316,7 @@ void BluetoothDevicePairingResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%" PRId32, this->error); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -3261,7 +3325,7 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothDeviceUnpairingResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -3270,7 +3334,7 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%" PRId32, this->error); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -3282,7 +3346,7 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothDeviceClearCacheResponse {\n"); out.append(" address: "); - sprintf(buffer, "%llu", this->address); + snprintf(buffer, sizeof(buffer), "%llu", this->address); out.append(buffer); out.append("\n"); @@ -3291,7 +3355,7 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%" PRId32, this->error); + snprintf(buffer, sizeof(buffer), "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -3326,7 +3390,7 @@ void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" flags: "); - sprintf(buffer, "%" PRIu32, this->flags); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->flags); out.append(buffer); out.append("\n"); out.append("}"); @@ -3335,17 +3399,17 @@ void VoiceAssistantAudioSettings::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantAudioSettings {\n"); out.append(" noise_suppression_level: "); - sprintf(buffer, "%" PRIu32, this->noise_suppression_level); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->noise_suppression_level); out.append(buffer); out.append("\n"); out.append(" auto_gain: "); - sprintf(buffer, "%" PRIu32, this->auto_gain); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->auto_gain); out.append(buffer); out.append("\n"); out.append(" volume_multiplier: "); - sprintf(buffer, "%g", this->volume_multiplier); + snprintf(buffer, sizeof(buffer), "%g", this->volume_multiplier); out.append(buffer); out.append("\n"); out.append("}"); @@ -3362,7 +3426,7 @@ void VoiceAssistantRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" flags: "); - sprintf(buffer, "%" PRIu32, this->flags); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->flags); out.append(buffer); out.append("\n"); @@ -3379,7 +3443,7 @@ void VoiceAssistantResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantResponse {\n"); out.append(" port: "); - sprintf(buffer, "%" PRIu32, this->port); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->port); out.append(buffer); out.append("\n"); @@ -3442,12 +3506,12 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" total_seconds: "); - sprintf(buffer, "%" PRIu32, this->total_seconds); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->total_seconds); out.append(buffer); out.append("\n"); out.append(" seconds_left: "); - sprintf(buffer, "%" PRIu32, this->seconds_left); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->seconds_left); out.append(buffer); out.append("\n"); @@ -3521,7 +3585,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { } out.append(" max_active_wake_words: "); - sprintf(buffer, "%" PRIu32, this->max_active_wake_words); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->max_active_wake_words); out.append(buffer); out.append("\n"); out.append("}"); @@ -3546,7 +3610,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3567,7 +3631,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" supported_features: "); - sprintf(buffer, "%" PRIu32, this->supported_features); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->supported_features); out.append(buffer); out.append("\n"); @@ -3580,7 +3644,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3589,7 +3653,7 @@ void AlarmControlPanelStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AlarmControlPanelStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3598,7 +3662,7 @@ void AlarmControlPanelStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3607,7 +3671,7 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AlarmControlPanelCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3618,6 +3682,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 @@ -3630,7 +3699,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3651,12 +3720,12 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" min_length: "); - sprintf(buffer, "%" PRIu32, this->min_length); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->min_length); out.append(buffer); out.append("\n"); out.append(" max_length: "); - sprintf(buffer, "%" PRIu32, this->max_length); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->max_length); out.append(buffer); out.append("\n"); @@ -3669,7 +3738,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3678,7 +3747,7 @@ void TextStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TextStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3691,7 +3760,7 @@ void TextStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3700,13 +3769,18 @@ void TextCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TextCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); 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 @@ -3719,7 +3793,7 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3740,7 +3814,7 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3749,7 +3823,7 @@ void DateStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DateStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3758,22 +3832,22 @@ void DateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" year: "); - sprintf(buffer, "%" PRIu32, this->year); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->year); out.append(buffer); out.append("\n"); out.append(" month: "); - sprintf(buffer, "%" PRIu32, this->month); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->month); out.append(buffer); out.append("\n"); out.append(" day: "); - sprintf(buffer, "%" PRIu32, this->day); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->day); out.append(buffer); out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3782,22 +3856,27 @@ void DateCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DateCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" year: "); - sprintf(buffer, "%" PRIu32, this->year); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->year); out.append(buffer); out.append("\n"); out.append(" month: "); - sprintf(buffer, "%" PRIu32, this->month); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->month); out.append(buffer); out.append("\n"); out.append(" day: "); - sprintf(buffer, "%" PRIu32, this->day); + 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("}"); @@ -3812,7 +3891,7 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3833,7 +3912,7 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3842,7 +3921,7 @@ void TimeStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TimeStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3851,22 +3930,22 @@ void TimeStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" hour: "); - sprintf(buffer, "%" PRIu32, this->hour); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->hour); out.append(buffer); out.append("\n"); out.append(" minute: "); - sprintf(buffer, "%" PRIu32, this->minute); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->minute); out.append(buffer); out.append("\n"); out.append(" second: "); - sprintf(buffer, "%" PRIu32, this->second); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->second); out.append(buffer); out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3875,22 +3954,27 @@ void TimeCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TimeCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" hour: "); - sprintf(buffer, "%" PRIu32, this->hour); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->hour); out.append(buffer); out.append("\n"); out.append(" minute: "); - sprintf(buffer, "%" PRIu32, this->minute); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->minute); out.append(buffer); out.append("\n"); out.append(" second: "); - sprintf(buffer, "%" PRIu32, this->second); + 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("}"); @@ -3905,7 +3989,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3936,7 +4020,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { } out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3945,7 +4029,7 @@ void EventResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("EventResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3954,7 +4038,7 @@ void EventResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -3969,7 +4053,7 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4006,7 +4090,7 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4015,12 +4099,12 @@ void ValveStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ValveStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" position: "); - sprintf(buffer, "%g", this->position); + snprintf(buffer, sizeof(buffer), "%g", this->position); out.append(buffer); out.append("\n"); @@ -4029,7 +4113,7 @@ void ValveStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4038,7 +4122,7 @@ void ValveCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ValveCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4047,13 +4131,18 @@ void ValveCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" position: "); - sprintf(buffer, "%g", this->position); + snprintf(buffer, sizeof(buffer), "%g", this->position); out.append(buffer); out.append("\n"); 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 @@ -4066,7 +4155,7 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4087,7 +4176,7 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4096,7 +4185,7 @@ void DateTimeStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DateTimeStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4105,12 +4194,12 @@ void DateTimeStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" epoch_seconds: "); - sprintf(buffer, "%" PRIu32, this->epoch_seconds); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->epoch_seconds); out.append(buffer); out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4119,12 +4208,17 @@ void DateTimeCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DateTimeCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" epoch_seconds: "); - sprintf(buffer, "%" PRIu32, this->epoch_seconds); + 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("}"); @@ -4139,7 +4233,7 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4164,7 +4258,7 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4173,7 +4267,7 @@ void UpdateStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("UpdateStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4190,7 +4284,7 @@ void UpdateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" progress: "); - sprintf(buffer, "%g", this->progress); + snprintf(buffer, sizeof(buffer), "%g", this->progress); out.append(buffer); out.append("\n"); @@ -4215,7 +4309,7 @@ void UpdateStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" device_id: "); - sprintf(buffer, "%" PRIu32, this->device_id); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id); out.append(buffer); out.append("\n"); out.append("}"); @@ -4224,13 +4318,18 @@ void UpdateCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("UpdateCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%" PRIu32, this->key); + snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append(" command: "); out.append(proto_enum_to_string(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 diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 92dd90053b..b96e5736a4 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -195,6 +195,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_home_assistant_state_response(msg); break; } +#ifdef USE_API_SERVICES case 42: { ExecuteServiceRequest msg; msg.decode(msg_data, msg_size); @@ -204,6 +205,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_execute_service_request(msg); break; } +#endif #ifdef USE_CAMERA case 45: { CameraImageRequest msg; @@ -660,11 +662,13 @@ void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { } } } +#ifdef USE_API_SERVICES void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { if (this->check_authenticated_()) { this->execute_service(msg); } } +#endif #ifdef USE_API_NOISE void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { if (this->check_authenticated_()) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 458f8ec81b..9c5dc244fe 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -69,7 +69,9 @@ class APIServerConnectionBase : public ProtoService { virtual void on_get_time_request(const GetTimeRequest &value){}; virtual void on_get_time_response(const GetTimeResponse &value){}; +#ifdef USE_API_SERVICES virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; +#endif #ifdef USE_CAMERA virtual void on_camera_image_request(const CameraImageRequest &value){}; @@ -216,7 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; +#ifdef USE_API_SERVICES virtual void execute_service(const ExecuteServiceRequest &msg) = 0; +#endif #ifdef USE_API_NOISE virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0; #endif @@ -333,7 +337,9 @@ class APIServerConnection : public APIServerConnectionBase { void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override; +#ifdef USE_API_SERVICES void on_execute_service_request(const ExecuteServiceRequest &msg) override; +#endif #ifdef USE_API_NOISE void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; #endif diff --git a/esphome/components/api/api_pb2_size.h b/esphome/components/api/api_pb2_size.h deleted file mode 100644 index f371be13a5..0000000000 --- a/esphome/components/api/api_pb2_size.h +++ /dev/null @@ -1,359 +0,0 @@ -#pragma once - -#include "proto.h" -#include -#include - -namespace esphome { -namespace api { - -class ProtoSize { - public: - /** - * @brief ProtoSize class for Protocol Buffer serialization size calculation - * - * This class provides static methods to calculate the exact byte counts needed - * for encoding various Protocol Buffer field types. All methods are designed to be - * efficient for the common case where many fields have default values. - * - * Implements Protocol Buffer encoding size calculation according to: - * https://protobuf.dev/programming-guides/encoding/ - * - * Key features: - * - Early-return optimization for zero/default values - * - Direct total_size updates to avoid unnecessary additions - * - Specialized handling for different field types according to protobuf spec - * - Templated helpers for repeated fields and messages - */ - - /** - * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint - * - * @param value The uint32_t value to calculate size for - * @return The number of bytes needed to encode the value - */ - static inline uint32_t varint(uint32_t value) { - // Optimized varint size calculation using leading zeros - // Each 7 bits requires one byte in the varint encoding - if (value < 128) - return 1; // 7 bits, common case for small values - - // For larger values, count bytes needed based on the position of the highest bit set - if (value < 16384) { - return 2; // 14 bits - } else if (value < 2097152) { - return 3; // 21 bits - } else if (value < 268435456) { - return 4; // 28 bits - } else { - return 5; // 32 bits (maximum for uint32_t) - } - } - - /** - * @brief Calculates the size in bytes needed to encode a uint64_t value as a varint - * - * @param value The uint64_t value to calculate size for - * @return The number of bytes needed to encode the value - */ - static inline uint32_t varint(uint64_t value) { - // Handle common case of values fitting in uint32_t (vast majority of use cases) - if (value <= UINT32_MAX) { - return varint(static_cast(value)); - } - - // For larger values, determine size based on highest bit position - if (value < (1ULL << 35)) { - return 5; // 35 bits - } else if (value < (1ULL << 42)) { - return 6; // 42 bits - } else if (value < (1ULL << 49)) { - return 7; // 49 bits - } else if (value < (1ULL << 56)) { - return 8; // 56 bits - } else if (value < (1ULL << 63)) { - return 9; // 63 bits - } else { - return 10; // 64 bits (maximum for uint64_t) - } - } - - /** - * @brief Calculates the size in bytes needed to encode an int32_t value as a varint - * - * Special handling is needed for negative values, which are sign-extended to 64 bits - * in Protocol Buffers, resulting in a 10-byte varint. - * - * @param value The int32_t value to calculate size for - * @return The number of bytes needed to encode the value - */ - static inline uint32_t varint(int32_t value) { - // Negative values are sign-extended to 64 bits in protocol buffers, - // which always results in a 10-byte varint for negative int32 - if (value < 0) { - return 10; // Negative int32 is always 10 bytes long - } - // For non-negative values, use the uint32_t implementation - return varint(static_cast(value)); - } - - /** - * @brief Calculates the size in bytes needed to encode an int64_t value as a varint - * - * @param value The int64_t value to calculate size for - * @return The number of bytes needed to encode the value - */ - static inline uint32_t varint(int64_t value) { - // For int64_t, we convert to uint64_t and calculate the size - // This works because the bit pattern determines the encoding size, - // and we've handled negative int32 values as a special case above - return varint(static_cast(value)); - } - - /** - * @brief Calculates the size in bytes needed to encode a field ID and wire type - * - * @param field_id The field identifier - * @param type The wire type value (from the WireType enum in the protobuf spec) - * @return The number of bytes needed to encode the field ID and wire type - */ - static inline uint32_t field(uint32_t field_id, uint32_t type) { - uint32_t tag = (field_id << 3) | (type & 0b111); - return varint(tag); - } - - /** - * @brief Common parameters for all add_*_field methods - * - * All add_*_field methods follow these common patterns: - * - * @param total_size Reference to the total message size to update - * @param field_id_size Pre-calculated size of the field ID in bytes - * @param value The value to calculate size for (type varies) - * @param force Whether to calculate size even if the value is default/zero/empty - * - * Each method follows this implementation pattern: - * 1. Skip calculation if value is default (0, false, empty) and not forced - * 2. Calculate the size based on the field's encoding rules - * 3. Add the field_id_size + calculated value size to total_size - */ - - /** - * @brief Calculates and adds the size of an int32 field to the total message size - */ - static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - if (value < 0) { - // Negative values are encoded as 10-byte varints in protobuf - total_size += field_id_size + 10; - } else { - // For non-negative values, use the standard varint size - total_size += field_id_size + varint(static_cast(value)); - } - } - - /** - * @brief Calculates and adds the size of a uint32 field to the total message size - */ - static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, - bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); - } - - /** - * @brief Calculates and adds the size of a boolean field to the total message size - */ - static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) { - // Skip calculation if value is false and not forced - if (!value && !force) { - return; // No need to update total_size - } - - // Boolean fields always use 1 byte when true - total_size += field_id_size + 1; - } - - /** - * @brief Calculates and adds the size of a fixed field to the total message size - * - * Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double). - * - * @tparam NumBytes The number of bytes for this fixed field (4 or 8) - * @param is_nonzero Whether the value is non-zero - */ - template - static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero, - bool force = false) { - // Skip calculation if value is zero and not forced - if (!is_nonzero && !force) { - return; // No need to update total_size - } - - // Fixed fields always take exactly NumBytes - total_size += field_id_size + NumBytes; - } - - /** - * @brief Calculates and adds the size of an enum field to the total message size - * - * Enum fields are encoded as uint32 varints. - */ - static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // Enums are encoded as uint32 - total_size += field_id_size + varint(value); - } - - /** - * @brief Calculates and adds the size of a sint32 field to the total message size - * - * Sint32 fields use ZigZag encoding, which is more efficient for negative values. - */ - static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // ZigZag encoding for sint32: (n << 1) ^ (n >> 31) - uint32_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 31)); - total_size += field_id_size + varint(zigzag); - } - - /** - * @brief Calculates and adds the size of an int64 field to the total message size - */ - static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); - } - - /** - * @brief Calculates and adds the size of a uint64 field to the total message size - */ - static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value, - bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); - } - - /** - * @brief Calculates and adds the size of a sint64 field to the total message size - * - * Sint64 fields use ZigZag encoding, which is more efficient for negative values. - */ - static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) { - // Skip calculation if value is zero and not forced - if (value == 0 && !force) { - return; // No need to update total_size - } - - // ZigZag encoding for sint64: (n << 1) ^ (n >> 63) - uint64_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 63)); - total_size += field_id_size + varint(zigzag); - } - - /** - * @brief Calculates and adds the size of a string/bytes field to the total message size - */ - static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str, - bool force = false) { - // Skip calculation if string is empty and not forced - if (str.empty() && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - const uint32_t str_size = static_cast(str.size()); - total_size += field_id_size + varint(str_size) + str_size; - } - - /** - * @brief Calculates and adds the size of a nested message field to the total message size - * - * This helper function directly updates the total_size reference if the nested size - * is greater than zero or force is true. - * - * @param nested_size The pre-calculated size of the nested message - */ - static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size, - bool force = false) { - // Skip calculation if nested message is empty and not forced - if (nested_size == 0 && !force) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - // Field ID + length varint + nested message content - total_size += field_id_size + varint(nested_size) + nested_size; - } - - /** - * @brief Calculates and adds the size of a nested message field to the total message size - * - * This version takes a ProtoMessage object, calculates its size internally, - * and updates the total_size reference. This eliminates the need for a temporary variable - * at the call site. - * - * @param message The nested message object - */ - static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message, - bool force = false) { - uint32_t nested_size = 0; - message.calculate_size(nested_size); - - // Use the base implementation with the calculated nested_size - add_message_field(total_size, field_id_size, nested_size, force); - } - - /** - * @brief Calculates and adds the sizes of all messages in a repeated field to the total message size - * - * This helper processes a vector of message objects, calculating the size for each message - * and adding it to the total size. - * - * @tparam MessageType The type of the nested messages in the vector - * @param messages Vector of message objects - */ - template - static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size, - const std::vector &messages) { - // Skip if the vector is empty - if (messages.empty()) { - return; - } - - // For repeated fields, always use force=true - for (const auto &message : messages) { - add_message_object(total_size, field_id_size, message, true); - } - } -}; - -} // namespace api -} // namespace esphome diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 575229cf04..f5be672c9a 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,14 +24,6 @@ static const char *const TAG = "api"; // APIServer APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -#ifndef USE_API_YAML_SERVICES -// Global empty vector to avoid guard variables (saves 8 bytes) -// This is initialized at program startup before any threads -static const std::vector empty_user_services{}; - -const std::vector &get_empty_user_services_instance() { return empty_user_services; } -#endif - APIServer::APIServer() { global_api_server = this; // Pre-allocate shared write buffer @@ -104,18 +96,19 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { - logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { - if (this->shutting_down_) { - // Don't try to send logs during shutdown - // as it could result in a recursion and - // we would be filling a buffer we are trying to clear - return; - } - for (auto &c : this->clients_) { - if (!c->flags_.remove) - c->try_send_log_message(level, tag, message); - } - }); + logger::global_logger->add_on_log_callback( + [this](int level, const char *tag, const char *message, size_t message_len) { + if (this->shutting_down_) { + // Don't try to send logs during shutdown + // as it could result in a recursion and + // we would be filling a buffer we are trying to clear + return; + } + for (auto &c : this->clients_) { + if (!c->flags_.remove) + c->try_send_log_message(level, tag, message, message_len); + } + }); } #endif @@ -260,180 +253,114 @@ bool APIServer::check_password(const std::string &password) const { void APIServer::handle_disconnect(APIConnection *conn) {} +// Macro for entities without extra parameters +#define API_DISPATCH_UPDATE(entity_type, entity_name) \ + void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ + if (obj->is_internal()) \ + return; \ + for (auto &c : this->clients_) \ + c->send_##entity_name##_state(obj); \ + } + +// Macro for entities with extra parameters (but parameters not used in send) +#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \ + void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \ + if (obj->is_internal()) \ + return; \ + for (auto &c : this->clients_) \ + c->send_##entity_name##_state(obj); \ + } + #ifdef USE_BINARY_SENSOR -void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_binary_sensor_state(obj); -} +API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor) #endif #ifdef USE_COVER -void APIServer::on_cover_update(cover::Cover *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_cover_state(obj); -} +API_DISPATCH_UPDATE(cover::Cover, cover) #endif #ifdef USE_FAN -void APIServer::on_fan_update(fan::Fan *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_fan_state(obj); -} +API_DISPATCH_UPDATE(fan::Fan, fan) #endif #ifdef USE_LIGHT -void APIServer::on_light_update(light::LightState *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_light_state(obj); -} +API_DISPATCH_UPDATE(light::LightState, light) #endif #ifdef USE_SENSOR -void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_sensor_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state) #endif #ifdef USE_SWITCH -void APIServer::on_switch_update(switch_::Switch *obj, bool state) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_switch_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state) #endif #ifdef USE_TEXT_SENSOR -void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_text_sensor_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state) #endif #ifdef USE_CLIMATE -void APIServer::on_climate_update(climate::Climate *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_climate_state(obj); -} +API_DISPATCH_UPDATE(climate::Climate, climate) #endif #ifdef USE_NUMBER -void APIServer::on_number_update(number::Number *obj, float state) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_number_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state) #endif #ifdef USE_DATETIME_DATE -void APIServer::on_date_update(datetime::DateEntity *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_date_state(obj); -} +API_DISPATCH_UPDATE(datetime::DateEntity, date) #endif #ifdef USE_DATETIME_TIME -void APIServer::on_time_update(datetime::TimeEntity *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_time_state(obj); -} +API_DISPATCH_UPDATE(datetime::TimeEntity, time) #endif #ifdef USE_DATETIME_DATETIME -void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_datetime_state(obj); -} +API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime) #endif #ifdef USE_TEXT -void APIServer::on_text_update(text::Text *obj, const std::string &state) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_text_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state) #endif #ifdef USE_SELECT -void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_select_state(obj); -} +API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index) #endif #ifdef USE_LOCK -void APIServer::on_lock_update(lock::Lock *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_lock_state(obj); -} +API_DISPATCH_UPDATE(lock::Lock, lock) #endif #ifdef USE_VALVE -void APIServer::on_valve_update(valve::Valve *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_valve_state(obj); -} +API_DISPATCH_UPDATE(valve::Valve, valve) #endif #ifdef USE_MEDIA_PLAYER -void APIServer::on_media_player_update(media_player::MediaPlayer *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_media_player_state(obj); -} +API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif #ifdef USE_EVENT +// Event is a special case - it's the only entity that passes extra parameters to the send method void APIServer::on_event(event::Event *obj, const std::string &event_type) { + if (obj->is_internal()) + return; for (auto &c : this->clients_) c->send_event(obj, event_type); } #endif #ifdef USE_UPDATE +// Update is a special case - the method is called on_update, not on_update_update void APIServer::on_update(update::UpdateEntity *obj) { + if (obj->is_internal()) + return; for (auto &c : this->clients_) c->send_update_state(obj); } #endif #ifdef USE_ALARM_CONTROL_PANEL -void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { - if (obj->is_internal()) - return; - for (auto &c : this->clients_) - c->send_alarm_control_panel_state(obj); -} +API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel) #endif float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } @@ -540,7 +467,8 @@ void APIServer::on_shutdown() { if (!c->send_message(DisconnectRequest())) { // If we can't send the disconnect request directly (tx_buffer full), // schedule it at the front of the batch so it will be sent with priority - c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE); + c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE, + DisconnectRequest::ESTIMATED_SIZE); } } } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f34fd55974..f41064b62b 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -12,7 +12,9 @@ #include "esphome/core/log.h" #include "list_entities.h" #include "subscribe_state.h" +#ifdef USE_API_SERVICES #include "user_services.h" +#endif #include @@ -25,11 +27,6 @@ struct SavedNoisePsk { } PACKED; // NOLINT #endif -#ifndef USE_API_YAML_SERVICES -// Forward declaration of helper function -const std::vector &get_empty_user_services_instance(); -#endif - class APIServer : public Component, public Controller { public: APIServer(); @@ -112,18 +109,9 @@ class APIServer : public Component, public Controller { void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); - void register_user_service(UserServiceDescriptor *descriptor) { -#ifdef USE_API_YAML_SERVICES - // Vector is pre-allocated when services are defined in YAML - this->user_services_.push_back(descriptor); -#else - // Lazy allocate vector on first use for CustomAPIDevice - if (!this->user_services_) { - this->user_services_ = std::make_unique>(); - } - this->user_services_->push_back(descriptor); +#ifdef USE_API_SERVICES + void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #endif - } #ifdef USE_HOMEASSISTANT_TIME void request_time(); #endif @@ -152,17 +140,9 @@ class APIServer : public Component, public Controller { void get_home_assistant_state(std::string entity_id, optional attribute, std::function f); const std::vector &get_state_subs() const; - const std::vector &get_user_services() const { -#ifdef USE_API_YAML_SERVICES - return this->user_services_; -#else - if (this->user_services_) { - return *this->user_services_; - } - // Return reference to global empty instance (no guard needed) - return get_empty_user_services_instance(); +#ifdef USE_API_SERVICES + const std::vector &get_user_services() const { return this->user_services_; } #endif - } #ifdef USE_API_CLIENT_CONNECTED_TRIGGER Trigger *get_client_connected_trigger() const { return this->client_connected_trigger_; } @@ -194,14 +174,8 @@ class APIServer : public Component, public Controller { #endif std::vector shared_write_buffer_; // Shared proto write buffer for all connections std::vector state_subs_; -#ifdef USE_API_YAML_SERVICES - // When services are defined in YAML, we know at compile time that services will be registered +#ifdef USE_API_SERVICES std::vector user_services_; -#else - // Services can still be registered at runtime by CustomAPIDevice components even when not - // defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common - // case where no services (YAML or custom) are used. - std::unique_ptr> user_services_; #endif // Group smaller types together diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 1a8e189f41..35329c4a5e 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -3,10 +3,13 @@ #include #include "api_server.h" #ifdef USE_API +#ifdef USE_API_SERVICES #include "user_services.h" +#endif namespace esphome { namespace api { +#ifdef USE_API_SERVICES template class CustomAPIDeviceService : public UserServiceBase { public: CustomAPIDeviceService(const std::string &name, const std::array &arg_names, T *obj, @@ -19,6 +22,7 @@ template class CustomAPIDeviceService : public UserS T *obj_; void (T::*callback_)(Ts...); }; +#endif // USE_API_SERVICES class CustomAPIDevice { public: @@ -46,12 +50,14 @@ class CustomAPIDevice { * @param name The name of the service to register. * @param arg_names The name of the arguments for the service, must match the arguments of the function. */ +#ifdef USE_API_SERVICES template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } +#endif /** Register a custom native API service that will show up in Home Assistant. * @@ -71,10 +77,12 @@ class CustomAPIDevice { * @param callback The member function to call when the service is triggered. * @param name The name of the arguments for the service, must match the arguments of the function. */ +#ifdef USE_API_SERVICES template void register_service(void (T::*callback)(), const std::string &name) { auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } +#endif /** Subscribe to the state (or attribute state) of an entity from Home Assistant. * diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 60814e359d..1fbe68117b 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -83,10 +83,12 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done( ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} +#ifdef USE_API_SERVICES bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); return this->client_->send_message(resp); } +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 4c83ca0935..b4cbf6c489 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -14,7 +14,7 @@ class APIConnection; #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ - ResponseType::MESSAGE_TYPE); \ + ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \ } class ListEntitiesIterator : public ComponentIterator { @@ -44,7 +44,9 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_TEXT_SENSOR bool on_text_sensor(text_sensor::TextSensor *entity) override; #endif +#ifdef USE_API_SERVICES bool on_service(UserServiceDescriptor *service) override; +#endif #ifdef USE_CAMERA bool on_camera(camera::Camera *entity) override; #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 764bac2f39..a435168821 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include #include #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE @@ -59,7 +60,6 @@ class ProtoVarInt { uint32_t as_uint32() const { return this->value_; } uint64_t as_uint64() const { return this->value_; } bool as_bool() const { return this->value_; } - template T as_enum() const { return static_cast(this->as_uint32()); } int32_t as_int32() const { // Not ZigZag encoded return static_cast(this->as_int64()); @@ -133,15 +133,24 @@ class ProtoVarInt { uint64_t value_; }; +// Forward declaration for decode_to_message and encode_to_writer +class ProtoMessage; + class ProtoLengthDelimited { public: explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} std::string as_string() const { return std::string(reinterpret_cast(this->value_), this->length_); } - template C as_message() const { - auto msg = C(); - msg.decode(this->value_, this->length_); - return msg; - } + + /** + * Decode the length-delimited data into an existing ProtoMessage instance. + * + * This method allows decoding without templates, enabling use in contexts + * where the message type is not known at compile time. The ProtoMessage's + * decode() method will be called with the raw data and length. + * + * @param msg The ProtoMessage instance to decode into + */ + void decode_to_message(ProtoMessage &msg) const; protected: const uint8_t *const value_; @@ -263,9 +272,6 @@ class ProtoWriteBuffer { this->write((value >> 48) & 0xFF); this->write((value >> 56) & 0xFF); } - template void encode_enum(uint32_t field_id, T value, bool force = false) { - this->encode_uint32(field_id, static_cast(value), force); - } void encode_float(uint32_t field_id, float value, bool force = false) { if (value == 0.0f && !force) return; @@ -306,18 +312,7 @@ class ProtoWriteBuffer { } this->encode_uint64(field_id, uvalue, force); } - template void encode_message(uint32_t field_id, const C &value, bool force = false) { - this->encode_field_raw(field_id, 2); // type 2: Length-delimited message - size_t begin = this->buffer_->size(); - - value.encode(*this); - - const uint32_t nested_length = this->buffer_->size() - begin; - // add size varint - std::vector var; - ProtoVarInt(nested_length).encode(var); - this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end()); - } + void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false); std::vector *get_buffer() const { return buffer_; } protected: @@ -345,6 +340,494 @@ class ProtoMessage { virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; } }; +class ProtoSize { + public: + /** + * @brief ProtoSize class for Protocol Buffer serialization size calculation + * + * This class provides static methods to calculate the exact byte counts needed + * for encoding various Protocol Buffer field types. All methods are designed to be + * efficient for the common case where many fields have default values. + * + * Implements Protocol Buffer encoding size calculation according to: + * https://protobuf.dev/programming-guides/encoding/ + * + * Key features: + * - Early-return optimization for zero/default values + * - Direct total_size updates to avoid unnecessary additions + * - Specialized handling for different field types according to protobuf spec + * - Templated helpers for repeated fields and messages + */ + + /** + * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint + * + * @param value The uint32_t value to calculate size for + * @return The number of bytes needed to encode the value + */ + static inline uint32_t varint(uint32_t value) { + // Optimized varint size calculation using leading zeros + // Each 7 bits requires one byte in the varint encoding + if (value < 128) + return 1; // 7 bits, common case for small values + + // For larger values, count bytes needed based on the position of the highest bit set + if (value < 16384) { + return 2; // 14 bits + } else if (value < 2097152) { + return 3; // 21 bits + } else if (value < 268435456) { + return 4; // 28 bits + } else { + return 5; // 32 bits (maximum for uint32_t) + } + } + + /** + * @brief Calculates the size in bytes needed to encode a uint64_t value as a varint + * + * @param value The uint64_t value to calculate size for + * @return The number of bytes needed to encode the value + */ + static inline uint32_t varint(uint64_t value) { + // Handle common case of values fitting in uint32_t (vast majority of use cases) + if (value <= UINT32_MAX) { + return varint(static_cast(value)); + } + + // For larger values, determine size based on highest bit position + if (value < (1ULL << 35)) { + return 5; // 35 bits + } else if (value < (1ULL << 42)) { + return 6; // 42 bits + } else if (value < (1ULL << 49)) { + return 7; // 49 bits + } else if (value < (1ULL << 56)) { + return 8; // 56 bits + } else if (value < (1ULL << 63)) { + return 9; // 63 bits + } else { + return 10; // 64 bits (maximum for uint64_t) + } + } + + /** + * @brief Calculates the size in bytes needed to encode an int32_t value as a varint + * + * Special handling is needed for negative values, which are sign-extended to 64 bits + * in Protocol Buffers, resulting in a 10-byte varint. + * + * @param value The int32_t value to calculate size for + * @return The number of bytes needed to encode the value + */ + static inline uint32_t varint(int32_t value) { + // Negative values are sign-extended to 64 bits in protocol buffers, + // which always results in a 10-byte varint for negative int32 + if (value < 0) { + return 10; // Negative int32 is always 10 bytes long + } + // For non-negative values, use the uint32_t implementation + return varint(static_cast(value)); + } + + /** + * @brief Calculates the size in bytes needed to encode an int64_t value as a varint + * + * @param value The int64_t value to calculate size for + * @return The number of bytes needed to encode the value + */ + static inline uint32_t varint(int64_t value) { + // For int64_t, we convert to uint64_t and calculate the size + // This works because the bit pattern determines the encoding size, + // and we've handled negative int32 values as a special case above + return varint(static_cast(value)); + } + + /** + * @brief Calculates the size in bytes needed to encode a field ID and wire type + * + * @param field_id The field identifier + * @param type The wire type value (from the WireType enum in the protobuf spec) + * @return The number of bytes needed to encode the field ID and wire type + */ + static inline uint32_t field(uint32_t field_id, uint32_t type) { + uint32_t tag = (field_id << 3) | (type & 0b111); + return varint(tag); + } + + /** + * @brief Common parameters for all add_*_field methods + * + * All add_*_field methods follow these common patterns: + * + * @param total_size Reference to the total message size to update + * @param field_id_size Pre-calculated size of the field ID in bytes + * @param value The value to calculate size for (type varies) + * @param force Whether to calculate size even if the value is default/zero/empty + * + * Each method follows this implementation pattern: + * 1. Skip calculation if value is default (0, false, empty) and not forced + * 2. Calculate the size based on the field's encoding rules + * 3. Add the field_id_size + calculated value size to total_size + */ + + /** + * @brief Calculates and adds the size of an int32 field to the total message size + */ + static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + if (value < 0) { + // Negative values are encoded as 10-byte varints in protobuf + total_size += field_id_size + 10; + } else { + // For non-negative values, use the standard varint size + total_size += field_id_size + varint(static_cast(value)); + } + } + + /** + * @brief Calculates and adds the size of an int32 field to the total message size (repeated field version) + */ + static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { + // Always calculate size for repeated fields + if (value < 0) { + // Negative values are encoded as 10-byte varints in protobuf + total_size += field_id_size + 10; + } else { + // For non-negative values, use the standard varint size + total_size += field_id_size + varint(static_cast(value)); + } + } + + /** + * @brief Calculates and adds the size of a uint32 field to the total message size + */ + static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version) + */ + static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { + // Always calculate size for repeated fields + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a boolean field to the total message size + */ + static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) { + // Skip calculation if value is false + if (!value) { + return; // No need to update total_size + } + + // Boolean fields always use 1 byte when true + total_size += field_id_size + 1; + } + + /** + * @brief Calculates and adds the size of a boolean field to the total message size (repeated field version) + */ + static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) { + // Always calculate size for repeated fields + // Boolean fields always use 1 byte + total_size += field_id_size + 1; + } + + /** + * @brief Calculates and adds the size of a fixed field to the total message size + * + * Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double). + * + * @tparam NumBytes The number of bytes for this fixed field (4 or 8) + * @param is_nonzero Whether the value is non-zero + */ + template + static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) { + // Skip calculation if value is zero + if (!is_nonzero) { + return; // No need to update total_size + } + + // Fixed fields always take exactly NumBytes + total_size += field_id_size + NumBytes; + } + + /** + * @brief Calculates and adds the size of an enum field to the total message size + * + * Enum fields are encoded as uint32 varints. + */ + static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // Enums are encoded as uint32 + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of an enum field to the total message size (repeated field version) + * + * Enum fields are encoded as uint32 varints. + */ + static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { + // Always calculate size for repeated fields + // Enums are encoded as uint32 + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a sint32 field to the total message size + * + * Sint32 fields use ZigZag encoding, which is more efficient for negative values. + */ + static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // ZigZag encoding for sint32: (n << 1) ^ (n >> 31) + uint32_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 31)); + total_size += field_id_size + varint(zigzag); + } + + /** + * @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version) + * + * Sint32 fields use ZigZag encoding, which is more efficient for negative values. + */ + static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { + // Always calculate size for repeated fields + // ZigZag encoding for sint32: (n << 1) ^ (n >> 31) + uint32_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 31)); + total_size += field_id_size + varint(zigzag); + } + + /** + * @brief Calculates and adds the size of an int64 field to the total message size + */ + static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of an int64 field to the total message size (repeated field version) + */ + static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) { + // Always calculate size for repeated fields + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a uint64 field to the total message size + */ + static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version) + */ + static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { + // Always calculate size for repeated fields + total_size += field_id_size + varint(value); + } + + /** + * @brief Calculates and adds the size of a sint64 field to the total message size + * + * Sint64 fields use ZigZag encoding, which is more efficient for negative values. + */ + static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) { + // Skip calculation if value is zero + if (value == 0) { + return; // No need to update total_size + } + + // ZigZag encoding for sint64: (n << 1) ^ (n >> 63) + uint64_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 63)); + total_size += field_id_size + varint(zigzag); + } + + /** + * @brief Calculates and adds the size of a sint64 field to the total message size (repeated field version) + * + * Sint64 fields use ZigZag encoding, which is more efficient for negative values. + */ + static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) { + // Always calculate size for repeated fields + // ZigZag encoding for sint64: (n << 1) ^ (n >> 63) + uint64_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 63)); + total_size += field_id_size + varint(zigzag); + } + + /** + * @brief Calculates and adds the size of a string/bytes field to the total message size + */ + static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) { + // Skip calculation if string is empty + if (str.empty()) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + const uint32_t str_size = static_cast(str.size()); + total_size += field_id_size + varint(str_size) + str_size; + } + + /** + * @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version) + */ + static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) { + // Always calculate size for repeated fields + const uint32_t str_size = static_cast(str.size()); + total_size += field_id_size + varint(str_size) + str_size; + } + + /** + * @brief Calculates and adds the size of a nested message field to the total message size + * + * This helper function directly updates the total_size reference if the nested size + * is greater than zero. + * + * @param nested_size The pre-calculated size of the nested message + */ + static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { + // Skip calculation if nested message is empty + if (nested_size == 0) { + return; // No need to update total_size + } + + // Calculate and directly add to total_size + // Field ID + length varint + nested message content + total_size += field_id_size + varint(nested_size) + nested_size; + } + + /** + * @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) + * + * @param nested_size The pre-calculated size of the nested message + */ + static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { + // Always calculate size for repeated fields + // Field ID + length varint + nested message content + total_size += field_id_size + varint(nested_size) + nested_size; + } + + /** + * @brief Calculates and adds the size of a nested message field to the total message size + * + * This version takes a ProtoMessage object, calculates its size internally, + * and updates the total_size reference. This eliminates the need for a temporary variable + * at the call site. + * + * @param message The nested message object + */ + static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) { + uint32_t nested_size = 0; + message.calculate_size(nested_size); + + // Use the base implementation with the calculated nested_size + add_message_field(total_size, field_id_size, nested_size); + } + + /** + * @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) + * + * @param message The nested message object + */ + static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size, + const ProtoMessage &message) { + uint32_t nested_size = 0; + message.calculate_size(nested_size); + + // Use the base implementation with the calculated nested_size + add_message_field_repeated(total_size, field_id_size, nested_size); + } + + /** + * @brief Calculates and adds the sizes of all messages in a repeated field to the total message size + * + * This helper processes a vector of message objects, calculating the size for each message + * and adding it to the total size. + * + * @tparam MessageType The type of the nested messages in the vector + * @param messages Vector of message objects + */ + template + static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size, + const std::vector &messages) { + // Skip if the vector is empty + if (messages.empty()) { + return; + } + + // Use the repeated field version for all messages + for (const auto &message : messages) { + add_message_object_repeated(total_size, field_id_size, message); + } + } +}; + +// Implementation of encode_message - must be after ProtoMessage is defined +inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) { + this->encode_field_raw(field_id, 2); // type 2: Length-delimited message + + // Calculate the message size first + uint32_t msg_length_bytes = 0; + value.calculate_size(msg_length_bytes); + + // Calculate how many bytes the length varint needs + uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes); + + // Reserve exact space for the length varint + size_t begin = this->buffer_->size(); + this->buffer_->resize(this->buffer_->size() + varint_length_bytes); + + // Write the length varint directly + ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes); + + // Now encode the message content - it will append to the buffer + value.encode(*this); + + // Verify that the encoded size matches what we calculated + assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes); +} + +// Implementation of decode_to_message - must be after ProtoMessage is defined +inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const { + msg.decode(this->value_, this->length_); +} + template const char *proto_enum_to_string(T value); class ProtoService { @@ -363,11 +846,11 @@ class ProtoService { * @return A ProtoWriteBuffer object with the reserved size. */ virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; - virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0; + virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0; virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; // Optimized method that pre-allocates buffer based on message size - bool send_message_(const ProtoMessage &msg, uint16_t message_type) { + bool send_message_(const ProtoMessage &msg, uint8_t message_type) { uint32_t msg_size = 0; msg.calculate_size(msg_size); diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 673bcf5693..93cea8133f 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -7,6 +7,7 @@ #include "esphome/core/automation.h" #include "api_pb2.h" +#ifdef USE_API_SERVICES namespace esphome { namespace api { @@ -73,3 +74,4 @@ template class UserServiceTrigger : public UserServiceBase &get_batch_buffer() { - static std::vector batch_buffer; - return batch_buffer; -} +// Batch size for BLE advertisements to maximize WiFi efficiency +// Each advertisement is up to 80 bytes when packaged (including protocol overhead) +// Most advertisements are 20-30 bytes, allowing even more to fit per packet +// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload +// This achieves ~97% WiFi MTU utilization while staying under the limit +static constexpr size_t FLUSH_BATCH_SIZE = 16; + +namespace { +// Batch buffer in anonymous namespace to avoid guard variable (saves 8 bytes) +// This is initialized at program startup before any threads +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +std::vector batch_buffer; +} // namespace + +static std::vector &get_batch_buffer() { return batch_buffer; } bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 66a5fe5d81..b084622f4c 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -2,6 +2,7 @@ CODEOWNERS = ["@esphome/core"] +CONF_BYTE_ORDER = "byte_order" CONF_DRAW_ROUNDING = "draw_rounding" CONF_ON_STATE_CHANGE = "on_state_change" CONF_REQUEST_HEADERS = "request_headers" diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 1955b5d22c..500dfac1fe 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_BLOCK, @@ -7,6 +8,7 @@ from esphome.const import ( CONF_FREE, CONF_ID, CONF_LOOP_TIME, + PlatformFramework, ) CODEOWNERS = ["@OttoWinter"] @@ -44,3 +46,21 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "debug_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "debug_host.cpp": {PlatformFramework.HOST_NATIVE}, + "debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "debug_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index e48a4941b3..37990aeec5 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -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); } } diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 63b359bd5b..05ae60239d 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,6 +1,6 @@ from esphome import automation, pins import esphome.codegen as cg -from esphome.components import time +from esphome.components import esp32, time from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, @@ -11,6 +11,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32S3, ) +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_DEFAULT, @@ -27,6 +28,7 @@ from esphome.const import ( CONF_WAKEUP_PIN, PLATFORM_ESP32, PLATFORM_ESP8266, + PlatformFramework, ) WAKEUP_PINS = { @@ -114,12 +116,20 @@ def validate_pin_number(value): return value -def validate_config(config): - if get_esp32_variant() == VARIANT_ESP32C3 and CONF_ESP32_EXT1_WAKEUP in config: - raise cv.Invalid("ESP32-C3 does not support wakeup from touch.") - if get_esp32_variant() == VARIANT_ESP32C3 and CONF_TOUCH_WAKEUP in config: - raise cv.Invalid("ESP32-C3 does not support wakeup from ext1") - return config +def _validate_ex1_wakeup_mode(value): + if value == "ALL_LOW": + esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) + if value == "ANY_LOW": + esp32.only_on_variant( + supported=[ + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, + ], + msg_prefix="ANY_LOW", + )(value) + return value deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") @@ -146,6 +156,7 @@ WAKEUP_PIN_MODES = { esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t") Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup") EXT1_WAKEUP_MODES = { + "ANY_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_LOW, "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, } @@ -185,16 +196,28 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.only_on_esp32, + esp32.only_on_variant( + unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from ext1" + ), cv.Schema( { cv.Required(CONF_PINS): cv.ensure_list( pins.internal_gpio_input_pin_schema, validate_pin_number ), - cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), + cv.Required(CONF_MODE): cv.All( + cv.enum(EXT1_WAKEUP_MODES, upper=True), + _validate_ex1_wakeup_mode, + ), } ), ), - cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean), + cv.Optional(CONF_TOUCH_WAKEUP): cv.All( + cv.only_on_esp32, + esp32.only_on_variant( + unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from touch" + ), + cv.boolean, + ), } ).extend(cv.COMPONENT_SCHEMA), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), @@ -313,3 +336,14 @@ async def deep_sleep_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "deep_sleep_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + } +) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index b4c7a4e05b..fdc469e419 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -189,7 +189,7 @@ def get_download_types(storage_json): ] -def only_on_variant(*, supported=None, unsupported=None): +def only_on_variant(*, supported=None, unsupported=None, msg_prefix="This feature"): """Config validator for features only available on some ESP32 variants.""" if supported is not None and not isinstance(supported, list): supported = [supported] @@ -200,11 +200,11 @@ def only_on_variant(*, supported=None, unsupported=None): variant = get_esp32_variant() if supported is not None and variant not in supported: raise cv.Invalid( - f"This feature is only available on {', '.join(supported)}" + f"{msg_prefix} is only available on {', '.join(supported)}" ) if unsupported is not None and variant in unsupported: raise cv.Invalid( - f"This feature is not available on {', '.join(unsupported)}" + f"{msg_prefix} is not available on {', '.join(unsupported)}" ) return obj @@ -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] diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index b554b6d09c..27572063ca 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -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) { diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 81582eb09a..2c5697df82 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -25,10 +25,15 @@ namespace esphome { namespace esp32_ble { // Maximum number of BLE scan results to buffer +// Sized to handle bursts of advertisements while allowing for processing delays +// With 16 advertisements per batch and some safety margin: +// - Without PSRAM: 24 entries (1.5× batch size) +// - With PSRAM: 36 entries (2.25× batch size) +// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers #ifdef USE_PSRAM -static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32; +static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36; #else -static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20; +static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24; #endif // Maximum size of the BLE event queue - must be power of 2 for lock-free queue diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 138f318a5d..6e36f7d5a7 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -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") diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 6f05610ed6..c3d43c6bbf 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -109,6 +109,7 @@ void ESP32TouchComponent::loop() { // Only publish if state changed - this filters out repeated events if (new_state != child->last_state_) { + child->initial_state_published_ = true; child->last_state_ = new_state; child->publish_state(new_state); // Original ESP32: ISR only fires when touched, release is detected by timeout @@ -175,6 +176,9 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); + uint32_t mask = 0; + touch_ll_read_trigger_status_mask(&mask); + touch_ll_clear_trigger_status_mask(); touch_pad_clear_status(); // INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured @@ -184,6 +188,11 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { // as any pad remains touched. This allows us to detect both new touches and // continued touches, but releases must be detected by timeout in the main loop. + // IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2! + // ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE + // Therefore: touched = (value < threshold) + // This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold) + // Process all configured pads to check their current state // Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt, // so we must scan all configured pads to find which ones were touched @@ -201,19 +210,12 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { value = touch_ll_read_raw_data(pad); } - // Skip pads with 0 value - they haven't been measured in this cycle - // This is important: not all pads are measured every interrupt cycle, - // only those that the hardware has updated - if (value == 0) { + // Skip pads that aren’t in the trigger mask + bool is_touched = (mask >> pad) & 1; + if (!is_touched) { continue; } - // IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2! - // ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE - // Therefore: touched = (value < threshold) - // This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold) - bool is_touched = value < child->get_threshold(); - // Always send the current state - the main loop will filter for changes // We send both touched and untouched states because the ISR doesn't // track previous state (to keep ISR fast and simple) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 81daad8c56..01b20bdcb1 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -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") diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index ac07d02e37..619346b914 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -342,5 +342,11 @@ async def to_code(config): cg.add_define("USE_ETHERNET") + # Disable WiFi when using Ethernet to save memory + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False) + # Also disable WiFi/BT coexistence since WiFi is disabled + add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False) + if CORE.using_arduino: cg.add_library("WiFi", None) diff --git a/esphome/components/gl_r01_i2c/__init__.py b/esphome/components/gl_r01_i2c/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp new file mode 100644 index 0000000000..5a24c63525 --- /dev/null +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp @@ -0,0 +1,68 @@ +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "gl_r01_i2c.h" + +namespace esphome { +namespace gl_r01_i2c { + +static const char *const TAG = "gl_r01_i2c"; + +// Register definitions from datasheet +static const uint8_t REG_VERSION = 0x00; +static const uint8_t REG_DISTANCE = 0x02; +static const uint8_t REG_TRIGGER = 0x10; +static const uint8_t CMD_TRIGGER = 0xB0; +static const uint8_t RESTART_CMD1 = 0x5A; +static const uint8_t RESTART_CMD2 = 0xA5; +static const uint8_t READ_DELAY = 40; // minimum milliseconds from datasheet to safely read measurement result + +void GLR01I2CComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up GL-R01 I2C..."); + // Verify sensor presence + if (!this->read_byte_16(REG_VERSION, &this->version_)) { + ESP_LOGE(TAG, "Failed to communicate with GL-R01 I2C sensor!"); + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Found GL-R01 I2C with version 0x%04X", this->version_); +} + +void GLR01I2CComponent::dump_config() { + ESP_LOGCONFIG(TAG, "GL-R01 I2C:"); + ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_); + LOG_I2C_DEVICE(this); + LOG_SENSOR(" ", "Distance", this); +} + +void GLR01I2CComponent::update() { + // Trigger a new measurement + if (!this->write_byte(REG_TRIGGER, CMD_TRIGGER)) { + ESP_LOGE(TAG, "Failed to trigger measurement!"); + this->status_set_warning(); + return; + } + + // Schedule reading the result after the read delay + this->set_timeout(READ_DELAY, [this]() { this->read_distance_(); }); +} + +void GLR01I2CComponent::read_distance_() { + uint16_t distance = 0; + if (!this->read_byte_16(REG_DISTANCE, &distance)) { + ESP_LOGE(TAG, "Failed to read distance value!"); + this->status_set_warning(); + return; + } + + if (distance == 0xFFFF) { + ESP_LOGW(TAG, "Invalid measurement received!"); + this->status_set_warning(); + } else { + ESP_LOGV(TAG, "Distance: %umm", distance); + this->publish_state(distance); + this->status_clear_warning(); + } +} + +} // namespace gl_r01_i2c +} // namespace esphome diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.h b/esphome/components/gl_r01_i2c/gl_r01_i2c.h new file mode 100644 index 0000000000..9a7aa023fd --- /dev/null +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace gl_r01_i2c { + +class GLR01I2CComponent : public sensor::Sensor, public i2c::I2CDevice, public PollingComponent { + public: + void setup() override; + void dump_config() override; + void update() override; + + protected: + void read_distance_(); + uint16_t version_{0}; +}; + +} // namespace gl_r01_i2c +} // namespace esphome diff --git a/esphome/components/gl_r01_i2c/sensor.py b/esphome/components/gl_r01_i2c/sensor.py new file mode 100644 index 0000000000..58db72540e --- /dev/null +++ b/esphome/components/gl_r01_i2c/sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_DISTANCE, + STATE_CLASS_MEASUREMENT, + UNIT_MILLIMETER, +) + +CODEOWNERS = ["@pkejval"] +DEPENDENCIES = ["i2c"] + +gl_r01_i2c_ns = cg.esphome_ns.namespace("gl_r01_i2c") +GLR01I2CComponent = gl_r01_i2c_ns.class_( + "GLR01I2CComponent", i2c.I2CDevice, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + GLR01I2CComponent, + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x74)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index d3dbcba6ed..a67d73fbb7 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -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") diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 18373edb77..0d32bc97c2 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,6 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import esp32 from esphome.components.const import CONF_REQUEST_HEADERS +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ESP8266_DISABLE_SSL_SUPPORT, @@ -13,6 +14,7 @@ from esphome.const import ( CONF_URL, CONF_WATCHDOG_TIMEOUT, PLATFORM_HOST, + PlatformFramework, __version__, ) from esphome.core import CORE, Lambda @@ -319,3 +321,19 @@ async def http_request_action_to_code(config, action_id, template_arg, args): await automation.build_automation(trigger, [], conf) return var + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "http_request_host.cpp": {PlatformFramework.HOST_NATIVE}, + "http_request_arduino.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.RP2040_ARDUINO, + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "http_request_idf.cpp": {PlatformFramework.ESP32_IDF}, + } +) diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index f81703c087..6c9f1e2877 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -111,8 +111,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MOISTURE): sensor.sensor_schema( unit_of_measurement=UNIT_INTENSITY, accuracy_decimals=0, - device_class=DEVICE_CLASS_PRECIPITATION_INTENSITY, state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:weather-rainy", ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 6adb9b71aa..4172b23845 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -3,6 +3,7 @@ import logging from esphome import pins import esphome.codegen as cg from esphome.components import esp32 +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -18,6 +19,7 @@ from esphome.const import ( PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, + PlatformFramework, ) from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv @@ -205,3 +207,18 @@ def final_validate_device_schema( {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, extra=cv.ALLOW_EXTRA, ) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "i2c_bus_arduino.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.RP2040_ARDUINO, + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + } +) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 5d593ac3d4..f6d8673a08 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -10,8 +10,10 @@ from PIL import Image, UnidentifiedImageError from esphome import core, external_files import esphome.codegen as cg +from esphome.components.const import CONF_BYTE_ORDER import esphome.config_validation as cv from esphome.const import ( + CONF_DEFAULTS, CONF_DITHER, CONF_FILE, CONF_ICON, @@ -38,6 +40,7 @@ CONF_OPAQUE = "opaque" CONF_CHROMA_KEY = "chroma_key" CONF_ALPHA_CHANNEL = "alpha_channel" CONF_INVERT_ALPHA = "invert_alpha" +CONF_IMAGES = "images" TRANSPARENCY_TYPES = ( CONF_OPAQUE, @@ -188,6 +191,10 @@ class ImageRGB565(ImageEncoder): dither, invert_alpha, ) + self.big_endian = True + + def set_big_endian(self, big_endian: bool) -> None: + self.big_endian = big_endian def convert(self, image, path): return image.convert("RGBA") @@ -205,10 +212,16 @@ class ImageRGB565(ImageEncoder): g = 1 b = 0 rgb = (r << 11) | (g << 5) | b - self.data[self.index] = rgb >> 8 - self.index += 1 - self.data[self.index] = rgb & 0xFF - self.index += 1 + if self.big_endian: + self.data[self.index] = rgb >> 8 + self.index += 1 + self.data[self.index] = rgb & 0xFF + self.index += 1 + else: + self.data[self.index] = rgb & 0xFF + self.index += 1 + self.data[self.index] = rgb >> 8 + self.index += 1 if self.transparency == CONF_ALPHA_CHANNEL: if self.invert_alpha: a ^= 0xFF @@ -364,7 +377,7 @@ def validate_file_shorthand(value): value = cv.string_strict(value) parts = value.strip().split(":") if len(parts) == 2 and parts[0] in MDI_SOURCES: - match = re.match(r"[a-zA-Z0-9\-]+", parts[1]) + match = re.match(r"^[a-zA-Z0-9\-]+$", parts[1]) if match is None: raise cv.Invalid(f"Could not parse mdi icon name from '{value}'.") return download_gh_svg(parts[1], parts[0]) @@ -434,20 +447,29 @@ def validate_type(image_types): def validate_settings(value): - type = value[CONF_TYPE] + """ + Validate the settings for a single image configuration. + """ + conf_type = value[CONF_TYPE] + type_class = IMAGE_TYPE[conf_type] transparency = value[CONF_TRANSPARENCY].lower() - allow_config = IMAGE_TYPE[type].allow_config - if transparency not in allow_config: + if transparency not in type_class.allow_config: raise cv.Invalid( - f"Image format '{type}' cannot have transparency: {transparency}" + f"Image format '{conf_type}' cannot have transparency: {transparency}" ) invert_alpha = value.get(CONF_INVERT_ALPHA, False) if ( invert_alpha and transparency != CONF_ALPHA_CHANNEL - and CONF_INVERT_ALPHA not in allow_config + and CONF_INVERT_ALPHA not in type_class.allow_config ): raise cv.Invalid("No alpha channel to invert") + if value.get(CONF_BYTE_ORDER) is not None and not callable( + getattr(type_class, "set_big_endian", None) + ): + raise cv.Invalid( + f"Image format '{conf_type}' does not support byte order configuration" + ) if file := value.get(CONF_FILE): file = Path(file) if is_svg_file(file): @@ -456,31 +478,82 @@ def validate_settings(value): try: Image.open(file) except UnidentifiedImageError as exc: - raise cv.Invalid(f"File can't be opened as image: {file}") from exc + raise cv.Invalid( + f"File can't be opened as image: {file.absolute()}" + ) from exc return value +IMAGE_ID_SCHEMA = { + cv.Required(CONF_ID): cv.declare_id(Image_), + cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA), + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), +} + + +OPTIONS_SCHEMA = { + cv.Optional(CONF_RESIZE): cv.dimensions, + cv.Optional(CONF_DITHER, default="NONE"): cv.one_of( + "NONE", "FLOYDSTEINBERG", upper=True + ), + cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean, + cv.Optional(CONF_BYTE_ORDER): cv.one_of("BIG_ENDIAN", "LITTLE_ENDIAN", upper=True), + cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(), + cv.Optional(CONF_TYPE): validate_type(IMAGE_TYPE), +} + +OPTIONS = [key.schema for key in OPTIONS_SCHEMA] + +# image schema with no defaults, used with `CONF_IMAGES` in the config +IMAGE_SCHEMA_NO_DEFAULTS = { + **IMAGE_ID_SCHEMA, + **{cv.Optional(key): OPTIONS_SCHEMA[key] for key in OPTIONS}, +} + BASE_SCHEMA = cv.Schema( { - cv.Required(CONF_ID): cv.declare_id(Image_), - cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA), - cv.Optional(CONF_RESIZE): cv.dimensions, - cv.Optional(CONF_DITHER, default="NONE"): cv.one_of( - "NONE", "FLOYDSTEINBERG", upper=True - ), - cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean, - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + **IMAGE_ID_SCHEMA, + **OPTIONS_SCHEMA, } ).add_extra(validate_settings) IMAGE_SCHEMA = BASE_SCHEMA.extend( { cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE), - cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(), } ) +def validate_defaults(value): + """ + Validate the options for images with defaults + """ + defaults = value[CONF_DEFAULTS] + result = [] + for index, image in enumerate(value[CONF_IMAGES]): + type = image.get(CONF_TYPE, defaults.get(CONF_TYPE)) + if type is None: + raise cv.Invalid( + "Type is required either in the image config or in the defaults", + path=[CONF_IMAGES, index], + ) + type_class = IMAGE_TYPE[type] + # A default byte order should be simply ignored if the type does not support it + available_options = [*OPTIONS] + if ( + not callable(getattr(type_class, "set_big_endian", None)) + and CONF_BYTE_ORDER not in image + ): + available_options.remove(CONF_BYTE_ORDER) + config = { + **{key: image.get(key, defaults.get(key)) for key in available_options}, + **{key.schema: image[key.schema] for key in IMAGE_ID_SCHEMA}, + } + validate_settings(config) + result.append(config) + return result + + def typed_image_schema(image_type): """ Construct a schema for a specific image type, allowing transparency options @@ -523,10 +596,33 @@ def typed_image_schema(image_type): # The config schema can be a (possibly empty) single list of images, # or a dictionary of image types each with a list of images -CONFIG_SCHEMA = cv.Any( - cv.Schema({cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE}), - cv.ensure_list(IMAGE_SCHEMA), -) +# or a dictionary with keys `defaults:` and `images:` + + +def _config_schema(config): + if isinstance(config, list): + return cv.Schema([IMAGE_SCHEMA])(config) + if not isinstance(config, dict): + raise cv.Invalid( + "Badly formed image configuration, expected a list or a dictionary" + ) + if CONF_DEFAULTS in config or CONF_IMAGES in config: + return validate_defaults( + cv.Schema( + { + cv.Required(CONF_DEFAULTS): OPTIONS_SCHEMA, + cv.Required(CONF_IMAGES): cv.ensure_list(IMAGE_SCHEMA_NO_DEFAULTS), + } + )(config) + ) + if CONF_ID in config or CONF_FILE in config: + return cv.ensure_list(IMAGE_SCHEMA)([config]) + return cv.Schema( + {cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE} + )(config) + + +CONFIG_SCHEMA = _config_schema async def write_image(config, all_frames=False): @@ -585,6 +681,9 @@ async def write_image(config, all_frames=False): total_rows = height * frame_count encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha) + if byte_order := config.get(CONF_BYTE_ORDER): + # Check for valid type has already been done in validate_settings + encoder.set_big_endian(byte_order == "BIG_ENDIAN") for frame_index in range(frame_count): image.seek(frame_index) pixels = encoder.convert(image.resize((width, height)), path).getdata() diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 375d1088e8..8f1fb0ee9d 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -178,13 +178,8 @@ static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01}; static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } -static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) { - for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) { - if (header_footer[i] != buffer[i]) { - return false; // Mismatch in header/footer - } - } - return true; // Valid header/footer +static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) { + return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0; } void LD2410Component::dump_config() { @@ -300,14 +295,12 @@ void LD2410Component::send_command_(uint8_t command, const uint8_t *command_valu if (command_value != nullptr) { len += command_value_len; } - uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00}; + // 2 length bytes (low, high) + 2 command bytes (low, high) + uint8_t len_cmd[] = {len, 0x00, command, 0x00}; this->write_array(len_cmd, sizeof(len_cmd)); - // command value bytes if (command_value != nullptr) { - for (uint8_t i = 0; i < command_value_len; i++) { - this->write_byte(command_value[i]); - } + this->write_array(command_value, command_value_len); } // frame footer bytes this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); @@ -401,7 +394,7 @@ void LD2410Component::handle_periodic_data_() { /* Moving distance range: 18th byte Still distance range: 19th byte - Moving enery: 20~28th bytes + Moving energy: 20~28th bytes */ for (std::vector::size_type i = 0; i != this->gate_move_sensors_.size(); i++) { sensor::Sensor *s = this->gate_move_sensors_[i]; @@ -480,7 +473,7 @@ bool LD2410Component::handle_ack_data_() { ESP_LOGE(TAG, "Invalid status"); return true; } - if (ld2410::two_byte_to_int(this->buffer_data_[8], this->buffer_data_[9]) != 0x00) { + if (this->buffer_data_[8] || this->buffer_data_[9]) { ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]); return true; } @@ -534,8 +527,8 @@ bool LD2410Component::handle_ack_data_() { const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_); const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_); ESP_LOGV(TAG, - "Light function is: %s\n" - "Light threshold is: %u\n" + "Light function: %s\n" + "Light threshold: %u\n" "Out pin level: %s", light_function_str, this->light_threshold_, out_pin_level_str); #ifdef USE_SELECT @@ -600,7 +593,7 @@ bool LD2410Component::handle_ack_data_() { break; case CMD_QUERY: { // Query parameters response - if (this->buffer_data_[10] != 0xAA) + if (this->buffer_data_[10] != HEADER) return true; // value head=0xAA #ifdef USE_NUMBER /* @@ -656,17 +649,11 @@ void LD2410Component::readline_(int readch) { if (this->buffer_pos_ < 4) { return; // Not enough data to process yet } - if (this->buffer_data_[this->buffer_pos_ - 4] == DATA_FRAME_FOOTER[0] && - this->buffer_data_[this->buffer_pos_ - 3] == DATA_FRAME_FOOTER[1] && - this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[2] && - this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[3]) { + if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next message - } else if (this->buffer_data_[this->buffer_pos_ - 4] == CMD_FRAME_FOOTER[0] && - this->buffer_data_[this->buffer_pos_ - 3] == CMD_FRAME_FOOTER[1] && - this->buffer_data_[this->buffer_pos_ - 2] == CMD_FRAME_FOOTER[2] && - this->buffer_data_[this->buffer_pos_ - 1] == CMD_FRAME_FOOTER[3]) { + } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message @@ -772,7 +759,6 @@ void LD2410Component::set_max_distances_timeout() { 0x00}; this->set_config_mode_(true); this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value)); - delay(50); // NOLINT this->query_parameters_(); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_config_mode_(false); @@ -802,7 +788,6 @@ void LD2410Component::set_gate_threshold(uint8_t gate) { 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00, 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00}; this->send_command_(CMD_GATE_SENS, value, sizeof(value)); - delay(50); // NOLINT this->query_parameters_(); this->set_config_mode_(false); } @@ -833,7 +818,6 @@ void LD2410Component::set_light_out_control() { this->set_config_mode_(true); uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00}; this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value)); - delay(50); // NOLINT this->query_light_control_(); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_config_mode_(false); diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp index c6ea0a348b..d8632e9c19 100644 --- a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.binary_sensor"; +static const char *const TAG = "ld2420.binary_sensor"; void LD2420BinarySensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:"); + ESP_LOGCONFIG(TAG, "Binary Sensor:"); LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_); } diff --git a/esphome/components/ld2420/button/reconfig_buttons.cpp b/esphome/components/ld2420/button/reconfig_buttons.cpp index 3537c1d64a..fb8ec2b5a6 100644 --- a/esphome/components/ld2420/button/reconfig_buttons.cpp +++ b/esphome/components/ld2420/button/reconfig_buttons.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "LD2420.button"; +static const char *const TAG = "ld2420.button"; namespace esphome { namespace ld2420 { diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 8a7d7de23b..0baff368c8 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple"; // Memory-efficient lookup tables struct StringToUint8 { const char *str; - uint8_t value; + const uint8_t value; }; static constexpr StringToUint8 OP_MODE_BY_STR[] = { @@ -155,8 +155,9 @@ static constexpr const char *ERR_MESSAGE[] = { // Helper function for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { for (const auto &entry : arr) { - if (str == entry.str) + if (str == entry.str) { return entry.value; + } } return 0xFF; // Not found } @@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() { void LD2420Component::loop() { // If there is a active send command do not process it here, the send command call will handle it. - if (!this->get_cmd_active_()) { - if (!this->available()) - return; - static uint8_t buffer[2048]; - static uint8_t rx_data; - while (this->available()) { - rx_data = this->read(); - this->readline_(rx_data, buffer, sizeof(buffer)); - } + while (!this->cmd_active_ && this->available()) { + this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH); } } @@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() { // Store average and peak values this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; - if (this->gate_peak[gate] < peak) + if (this->gate_peak[gate] < peak) { this->gate_peak[gate] = peak; + } uint32_t calculated_value = (static_cast(this->gate_peak[gate]) + (move_factor * static_cast(this->gate_peak[gate]))); @@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) { } } else { // Set the current data back so we don't have new data that can be applied in error. - if (this->get_calibration_()) + if (this->get_calibration_()) { memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); + } this->set_calibration_(false); } } else { @@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) { } void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { - static int pos = 0; - - if (rx_data >= 0) { - if (pos < len - 1) { - buffer[pos++] = rx_data; - buffer[pos] = 0; - } else { - pos = 0; - } - if (pos >= 4) { - if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { - this->set_cmd_active_(false); // Set command state to inactive after responce. - this->handle_ack_data_(buffer, pos); - pos = 0; - } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && - (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { - this->handle_simple_mode_(buffer, pos); - pos = 0; - } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && - (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { - this->handle_energy_mode_(buffer, pos); - pos = 0; - } - } + if (rx_data < 0) { + return; // No data available + } + if (this->buffer_pos_ < len - 1) { + buffer[this->buffer_pos_++] = rx_data; + buffer[this->buffer_pos_] = 0; + } else { + // We should never get here, but just in case... + ESP_LOGW(TAG, "Max command length exceeded; ignoring"); + this->buffer_pos_ = 0; + } + if (this->buffer_pos_ < 4) { + return; // Not enough data to process yet + } + if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { + this->cmd_active_ = false; // Set command state to inactive after response + this->handle_ack_data_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; + } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) && + (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { + this->handle_simple_mode_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; + } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && + (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { + this->handle_energy_mode_(buffer, this->buffer_pos_); + this->buffer_pos_ = 0; } } @@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { // Resonable refresh rate for home assistant database size health const int32_t current_millis = App.get_loop_component_start_time(); - if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) + if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) { return; + } this->last_periodic_millis = current_millis; for (auto &listener : this->listeners_) { listener->on_distance(this->get_distance_()); @@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { } } outbuf[index] = '\0'; - if (index > 1) + if (index > 1) { this->set_distance_(strtol(outbuf, &endptr, 10)); + } if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { // Resonable refresh rate for home assistant database size health const int32_t current_millis = App.get_loop_component_start_time(); - if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) + if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) { return; + } this->last_normal_periodic_millis = current_millis; for (auto &listener : this->listeners_) listener->on_distance(this->get_distance_()); @@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { int LD2420Component::send_cmd_from_array(CmdFrameT frame) { uint32_t start_millis = millis(); uint8_t error = 0; - uint8_t ack_buffer[64]; - uint8_t cmd_buffer[64]; + uint8_t ack_buffer[MAX_LINE_LENGTH]; + uint8_t cmd_buffer[MAX_LINE_LENGTH]; this->cmd_reply_.ack = false; - if (frame.command != CMD_RESTART) - this->set_cmd_active_(true); // Restart does not reply, thus no ack state required. + if (frame.command != CMD_RESTART) { + this->cmd_active_ = true; + } // Restart does not reply, thus no ack state required uint8_t retry = 3; while (retry) { frame.length = 0; @@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); frame.length += sizeof(frame.footer); - for (uint16_t index = 0; index < frame.length; index++) { - this->write_byte(cmd_buffer[index]); - } + this->write_array(cmd_buffer, frame.length); error = 0; if (frame.command == CMD_RESTART) { @@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { while (!this->cmd_reply_.ack) { while (this->available()) { - this->readline_(read(), ack_buffer, sizeof(ack_buffer)); + this->readline_(this->read(), ack_buffer, sizeof(ack_buffer)); } delay_microseconds_safe(1450); // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. @@ -641,10 +641,12 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { break; } } - if (this->cmd_reply_.ack) + if (this->cmd_reply_.ack) { retry = 0; - if (this->cmd_reply_.error > 0) + } + if (this->cmd_reply_.error > 0) { this->handle_cmd_error(error); + } } return error; } @@ -764,8 +766,9 @@ void LD2420Component::set_system_mode(uint16_t mode) { cmd_frame.data_length += sizeof(unknown_parm); cmd_frame.footer = CMD_FRAME_FOOTER; ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); - if (this->send_cmd_from_array(cmd_frame) == 0) + if (this->send_cmd_from_array(cmd_frame) == 0) { this->set_mode_(mode); + } } void LD2420Component::get_firmware_version_() { @@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) { #ifdef USE_NUMBER void LD2420Component::init_gate_config_numbers() { - if (this->gate_timeout_number_ != nullptr) + if (this->gate_timeout_number_ != nullptr) { this->gate_timeout_number_->publish_state(static_cast(this->current_config.timeout)); - if (this->gate_select_number_ != nullptr) + } + if (this->gate_select_number_ != nullptr) { this->gate_select_number_->publish_state(0); - if (this->min_gate_distance_number_ != nullptr) + } + if (this->min_gate_distance_number_ != nullptr) { this->min_gate_distance_number_->publish_state(static_cast(this->current_config.min_gate)); - if (this->max_gate_distance_number_ != nullptr) + } + if (this->max_gate_distance_number_ != nullptr) { this->max_gate_distance_number_->publish_state(static_cast(this->current_config.max_gate)); - if (this->gate_move_sensitivity_factor_number_ != nullptr) + } + if (this->gate_move_sensitivity_factor_number_ != nullptr) { this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); - if (this->gate_still_sensitivity_factor_number_ != nullptr) + } + if (this->gate_still_sensitivity_factor_number_ != nullptr) { this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); + } for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { if (this->gate_still_threshold_numbers_[gate] != nullptr) { this->gate_still_threshold_numbers_[gate]->publish_state( diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index d574a25c89..812c408cfd 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -20,8 +20,9 @@ namespace esphome { namespace ld2420 { -static const uint8_t TOTAL_GATES = 16; static const uint8_t CALIBRATE_SAMPLES = 64; +static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer +static const uint8_t TOTAL_GATES = 16; enum OpMode : uint8_t { OP_NORMAL_MODE = 1, @@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice { float gate_move_sensitivity_factor{0.5}; float gate_still_sensitivity_factor{0.5}; - int32_t last_periodic_millis = millis(); - int32_t report_periodic_millis = millis(); - int32_t monitor_periodic_millis = millis(); - int32_t last_normal_periodic_millis = millis(); + int32_t last_periodic_millis{0}; + int32_t report_periodic_millis{0}; + int32_t monitor_periodic_millis{0}; + int32_t last_normal_periodic_millis{0}; uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES]; uint16_t gate_avg[TOTAL_GATES]; uint16_t gate_peak[TOTAL_GATES]; @@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice { void set_presence_(bool presence) { this->presence_ = presence; }; uint16_t get_distance_() { return this->distance_; }; void set_distance_(uint16_t distance) { this->distance_ = distance; }; - bool get_cmd_active_() { return this->cmd_active_; }; - void set_cmd_active_(bool active) { this->cmd_active_ = active; }; void handle_simple_mode_(const uint8_t *inbuf, int len); void handle_energy_mode_(uint8_t *buffer, int len); void handle_ack_data_(uint8_t *buffer, int len); @@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice { std::vector gate_move_threshold_numbers_ = std::vector(16); #endif - uint32_t max_distance_gate_; - uint32_t min_distance_gate_; + uint16_t distance_{0}; uint16_t system_mode_; uint16_t gate_energy_[TOTAL_GATES]; - uint16_t distance_{0}; - uint8_t config_checksum_{0}; + uint8_t buffer_pos_{0}; // where to resume processing/populating buffer + uint8_t buffer_data_[MAX_LINE_LENGTH]; char firmware_ver_[8]{"v0.0.0"}; bool cmd_active_{false}; bool presence_{false}; diff --git a/esphome/components/ld2420/number/gate_config_number.cpp b/esphome/components/ld2420/number/gate_config_number.cpp index e5eaafb46d..a373753770 100644 --- a/esphome/components/ld2420/number/gate_config_number.cpp +++ b/esphome/components/ld2420/number/gate_config_number.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "LD2420.number"; +static const char *const TAG = "ld2420.number"; namespace esphome { namespace ld2420 { diff --git a/esphome/components/ld2420/select/operating_mode_select.cpp b/esphome/components/ld2420/select/operating_mode_select.cpp index 1c59f443a5..2d576e7cc6 100644 --- a/esphome/components/ld2420/select/operating_mode_select.cpp +++ b/esphome/components/ld2420/select/operating_mode_select.cpp @@ -5,7 +5,7 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.select"; +static const char *const TAG = "ld2420.select"; void LD2420Select::control(const std::string &value) { this->publish_state(value); diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.cpp b/esphome/components/ld2420/sensor/ld2420_sensor.cpp index 97f0c594b7..723604f396 100644 --- a/esphome/components/ld2420/sensor/ld2420_sensor.cpp +++ b/esphome/components/ld2420/sensor/ld2420_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.sensor"; +static const char *const TAG = "ld2420.sensor"; void LD2420Sensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 Sensor:"); + ESP_LOGCONFIG(TAG, "Sensor:"); LOG_SENSOR(" ", "Distance", this->distance_sensor_); } diff --git a/esphome/components/ld2420/text_sensor/text_sensor.cpp b/esphome/components/ld2420/text_sensor/text_sensor.cpp index 1dcdcf7d60..73af3b3660 100644 --- a/esphome/components/ld2420/text_sensor/text_sensor.cpp +++ b/esphome/components/ld2420/text_sensor/text_sensor.cpp @@ -5,10 +5,10 @@ namespace esphome { namespace ld2420 { -static const char *const TAG = "LD2420.text_sensor"; +static const char *const TAG = "ld2420.text_sensor"; void LD2420TextSensor::dump_config() { - ESP_LOGCONFIG(TAG, "LD2420 TextSensor:"); + ESP_LOGCONFIG(TAG, "Text Sensor:"); LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); } diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 149e5d1179..17d5d46ffd 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -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", "soft") # include in every file cg.add_platformio_option("build_src_flags", "-include Arduino.h") # dummy version code diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 8302239d6a..baa4507d2f 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -97,12 +97,12 @@ class AddressableLight : public LightOutput, public Component { } virtual ESPColorView get_view_internal(int32_t index) const = 0; - bool effect_active_{false}; ESPColorCorrection correction_{}; + LightState *state_parent_{nullptr}; #ifdef USE_POWER_SUPPLY power_supply::PowerSupplyRequester power_; #endif - LightState *state_parent_{nullptr}; + bool effect_active_{false}; }; class AddressableLightTransformer : public LightTransitionTransformer { @@ -114,9 +114,9 @@ class AddressableLightTransformer : public LightTransitionTransformer { protected: AddressableLight &light_; - Color target_color_{}; float last_transition_progress_{0.0f}; float accumulated_alpha_{0.0f}; + Color target_color_{}; }; } // namespace light diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h index 39ce5700c6..979a1acb07 100644 --- a/esphome/components/light/esp_color_correction.h +++ b/esphome/components/light/esp_color_correction.h @@ -69,8 +69,8 @@ class ESPColorCorrection { protected: uint8_t gamma_table_[256]; uint8_t gamma_reverse_table_[256]; - uint8_t local_brightness_{255}; Color max_brightness_; + uint8_t local_brightness_{255}; }; } // namespace light diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 78b0ac9feb..a3ffe22591 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -2,12 +2,28 @@ #include "light_call.h" #include "light_state.h" #include "esphome/core/log.h" +#include "esphome/core/optional.h" namespace esphome { namespace light { static const char *const TAG = "light"; +// Macro to reduce repetitive setter code +#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \ + LightCall &LightCall::set_##name(optional(name)) { \ + if ((name).has_value()) { \ + this->name##_ = (name).value(); \ + } \ + this->set_flag_(flag, (name).has_value()); \ + return *this; \ + } \ + LightCall &LightCall::set_##name(type name) { \ + this->name##_ = name; \ + this->set_flag_(flag, true); \ + return *this; \ + } + static const LogString *color_mode_to_human(ColorMode color_mode) { if (color_mode == ColorMode::UNKNOWN) return LOG_STR("Unknown"); @@ -32,41 +48,43 @@ void LightCall::perform() { const char *name = this->parent_->get_name().c_str(); LightColorValues v = this->validate_(); - if (this->publish_) { + if (this->get_publish_()) { ESP_LOGD(TAG, "'%s' Setting:", name); // Only print color mode when it's being changed ColorMode current_color_mode = this->parent_->remote_values.get_color_mode(); - if (this->color_mode_.value_or(current_color_mode) != current_color_mode) { + ColorMode target_color_mode = this->has_color_mode() ? this->color_mode_ : current_color_mode; + if (target_color_mode != current_color_mode) { ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode()))); } // Only print state when it's being changed bool current_state = this->parent_->remote_values.is_on(); - if (this->state_.value_or(current_state) != current_state) { + bool target_state = this->has_state() ? this->state_ : current_state; + if (target_state != current_state) { ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on())); } - if (this->brightness_.has_value()) { + if (this->has_brightness()) { ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); } - if (this->color_brightness_.has_value()) { + if (this->has_color_brightness()) { ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f); } - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { + if (this->has_red() || this->has_green() || this->has_blue()) { ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, v.get_blue() * 100.0f); } - if (this->white_.has_value()) { + if (this->has_white()) { ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f); } - if (this->color_temperature_.has_value()) { + if (this->has_color_temperature()) { ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); } - if (this->cold_white_.has_value() || this->warm_white_.has_value()) { + if (this->has_cold_white() || this->has_warm_white()) { ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f, v.get_warm_white() * 100.0f); } @@ -74,58 +92,57 @@ void LightCall::perform() { if (this->has_flash_()) { // FLASH - if (this->publish_) { - ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f); + if (this->get_publish_()) { + ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f); } - this->parent_->start_flash_(v, *this->flash_length_, this->publish_); + this->parent_->start_flash_(v, this->flash_length_, this->get_publish_()); } else if (this->has_transition_()) { // TRANSITION - if (this->publish_) { - ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f); + if (this->get_publish_()) { + ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f); } // Special case: Transition and effect can be set when turning off if (this->has_effect_()) { - if (this->publish_) { + if (this->get_publish_()) { ESP_LOGD(TAG, " Effect: 'None'"); } this->parent_->stop_effect_(); } - this->parent_->start_transition_(v, *this->transition_length_, this->publish_); + this->parent_->start_transition_(v, this->transition_length_, this->get_publish_()); } else if (this->has_effect_()) { // EFFECT - auto effect = this->effect_; const char *effect_s; - if (effect == 0u) { + if (this->effect_ == 0u) { effect_s = "None"; } else { - effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); + effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str(); } - if (this->publish_) { + if (this->get_publish_()) { ESP_LOGD(TAG, " Effect: '%s'", effect_s); } - this->parent_->start_effect_(*this->effect_); + this->parent_->start_effect_(this->effect_); // Also set light color values when starting an effect // For example to turn off the light this->parent_->set_immediately_(v, true); } else { // INSTANT CHANGE - this->parent_->set_immediately_(v, this->publish_); + this->parent_->set_immediately_(v, this->get_publish_()); } if (!this->has_transition_()) { this->parent_->target_state_reached_callback_.call(); } - if (this->publish_) { + if (this->get_publish_()) { this->parent_->publish_state(); } - if (this->save_) { + if (this->get_save_()) { this->parent_->save_remote_values_(); } } @@ -135,82 +152,80 @@ LightColorValues LightCall::validate_() { auto traits = this->parent_->get_traits(); // Color mode check - if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { - ESP_LOGW(TAG, "'%s' does not support color mode %s", name, - LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); - this->color_mode_.reset(); + if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) { + ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_))); + this->set_flag_(FLAG_HAS_COLOR_MODE, false); } // Ensure there is always a color mode set - if (!this->color_mode_.has_value()) { + if (!this->has_color_mode()) { this->color_mode_ = this->compute_color_mode_(); + this->set_flag_(FLAG_HAS_COLOR_MODE, true); } - auto color_mode = *this->color_mode_; + auto color_mode = this->color_mode_; // Transform calls that use non-native parameters for the current mode. this->transform_parameters_(); // Brightness exists check - if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { + if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { ESP_LOGW(TAG, "'%s': setting brightness not supported", name); - this->brightness_.reset(); + this->set_flag_(FLAG_HAS_BRIGHTNESS, false); } // Transition length possible check - if (this->transition_length_.has_value() && *this->transition_length_ != 0 && - !(color_mode & ColorCapability::BRIGHTNESS)) { + if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) { ESP_LOGW(TAG, "'%s': transitions not supported", name); - this->transition_length_.reset(); + this->set_flag_(FLAG_HAS_TRANSITION, false); } // Color brightness exists check - if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { + if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); - this->color_brightness_.reset(); + this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false); } // RGB exists check - if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) || - (this->blue_.has_value() && *this->blue_ > 0.0f)) { + if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) || + (this->has_blue() && this->blue_ > 0.0f)) { if (!(color_mode & ColorCapability::RGB)) { ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); - this->red_.reset(); - this->green_.reset(); - this->blue_.reset(); + this->set_flag_(FLAG_HAS_RED, false); + this->set_flag_(FLAG_HAS_GREEN, false); + this->set_flag_(FLAG_HAS_BLUE, false); } } // White value exists check - if (this->white_.has_value() && *this->white_ > 0.0f && + if (this->has_white() && this->white_ > 0.0f && !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); - this->white_.reset(); + this->set_flag_(FLAG_HAS_WHITE, false); } // Color temperature exists check - if (this->color_temperature_.has_value() && + if (this->has_color_temperature() && !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); - this->color_temperature_.reset(); + this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false); } // Cold/warm white value exists check - if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || - (this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) { + if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); - this->cold_white_.reset(); - this->warm_white_.reset(); + this->set_flag_(FLAG_HAS_COLD_WHITE, false); + this->set_flag_(FLAG_HAS_WARM_WHITE, false); } } #define VALIDATE_RANGE_(name_, upper_name, min, max) \ - if (name_##_.has_value()) { \ - auto val = *name_##_; \ + if (this->has_##name_()) { \ + auto val = this->name_##_; \ if (val < (min) || val > (max)) { \ ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \ (min), (max)); \ - name_##_ = clamp(val, (min), (max)); \ + this->name_##_ = clamp(val, (min), (max)); \ } \ } #define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f) @@ -227,110 +242,116 @@ LightColorValues LightCall::validate_() { VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds()) // Flag whether an explicit turn off was requested, in which case we'll also stop the effect. - bool explicit_turn_off_request = this->state_.has_value() && !*this->state_; + bool explicit_turn_off_request = this->has_state() && !this->state_; // Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on). - if (this->brightness_.has_value() && *this->brightness_ == 0.0f) { - this->state_ = optional(false); - this->brightness_ = optional(1.0f); + if (this->has_brightness() && this->brightness_ == 0.0f) { + this->state_ = false; + this->set_flag_(FLAG_HAS_STATE, true); + this->brightness_ = 1.0f; } // Set color brightness to 100% if currently zero and a color is set. - if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f) - this->color_brightness_ = optional(1.0f); + if (this->has_red() || this->has_green() || this->has_blue()) { + if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) { + this->color_brightness_ = 1.0f; + this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true); + } } // Create color values for the light with this call applied. auto v = this->parent_->remote_values; - if (this->color_mode_.has_value()) - v.set_color_mode(*this->color_mode_); - if (this->state_.has_value()) - v.set_state(*this->state_); - if (this->brightness_.has_value()) - v.set_brightness(*this->brightness_); - if (this->color_brightness_.has_value()) - v.set_color_brightness(*this->color_brightness_); - if (this->red_.has_value()) - v.set_red(*this->red_); - if (this->green_.has_value()) - v.set_green(*this->green_); - if (this->blue_.has_value()) - v.set_blue(*this->blue_); - if (this->white_.has_value()) - v.set_white(*this->white_); - if (this->color_temperature_.has_value()) - v.set_color_temperature(*this->color_temperature_); - if (this->cold_white_.has_value()) - v.set_cold_white(*this->cold_white_); - if (this->warm_white_.has_value()) - v.set_warm_white(*this->warm_white_); + if (this->has_color_mode()) + v.set_color_mode(this->color_mode_); + if (this->has_state()) + v.set_state(this->state_); + if (this->has_brightness()) + v.set_brightness(this->brightness_); + if (this->has_color_brightness()) + v.set_color_brightness(this->color_brightness_); + if (this->has_red()) + v.set_red(this->red_); + if (this->has_green()) + v.set_green(this->green_); + if (this->has_blue()) + v.set_blue(this->blue_); + if (this->has_white()) + v.set_white(this->white_); + if (this->has_color_temperature()) + v.set_color_temperature(this->color_temperature_); + if (this->has_cold_white()) + v.set_cold_white(this->cold_white_); + if (this->has_warm_white()) + v.set_warm_white(this->warm_white_); v.normalize_color(); // Flash length check - if (this->has_flash_() && *this->flash_length_ == 0) { + if (this->has_flash_() && this->flash_length_ == 0) { ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); - this->flash_length_.reset(); + this->set_flag_(FLAG_HAS_FLASH, false); } // validate transition length/flash length/effect not used at the same time bool supports_transition = color_mode & ColorCapability::BRIGHTNESS; // If effect is already active, remove effect start - if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) { - this->effect_.reset(); + if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) { + this->set_flag_(FLAG_HAS_EFFECT, false); } // validate effect index - if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { - ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_); - this->effect_.reset(); + if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) { + ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_); + this->set_flag_(FLAG_HAS_EFFECT, false); } if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); - this->transition_length_.reset(); - this->flash_length_.reset(); + this->set_flag_(FLAG_HAS_TRANSITION, false); + this->set_flag_(FLAG_HAS_FLASH, false); } if (this->has_flash_() && this->has_transition_()) { ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); - this->transition_length_.reset(); + this->set_flag_(FLAG_HAS_TRANSITION, false); } - if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) && + if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) && supports_transition) { // nothing specified and light supports transitions, set default transition length this->transition_length_ = this->parent_->default_transition_length_; + this->set_flag_(FLAG_HAS_TRANSITION, true); } - if (this->transition_length_.value_or(0) == 0) { + if (this->has_transition_() && this->transition_length_ == 0) { // 0 transition is interpreted as no transition (instant change) - this->transition_length_.reset(); + this->set_flag_(FLAG_HAS_TRANSITION, false); } if (this->has_transition_() && !supports_transition) { ESP_LOGW(TAG, "'%s': transitions not supported", name); - this->transition_length_.reset(); + this->set_flag_(FLAG_HAS_TRANSITION, false); } // If not a flash and turning the light off, then disable the light // Do not use light color values directly, so that effects can set 0% brightness // Reason: When user turns off the light in frontend, the effect should also stop - if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { + bool target_state = this->has_state() ? this->state_ : v.is_on(); + if (!this->has_flash_() && !target_state) { if (this->has_effect_()) { ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); - this->effect_.reset(); + this->set_flag_(FLAG_HAS_EFFECT, false); } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { // Auto turn off effect this->effect_ = 0; + this->set_flag_(FLAG_HAS_EFFECT, true); } } // Disable saving for flashes if (this->has_flash_()) - this->save_ = false; + this->set_flag_(FLAG_SAVE, false); return v; } @@ -343,24 +364,27 @@ void LightCall::transform_parameters_() { // - RGBWW lights with color_interlock=true, which also sets "brightness" and // "color_temperature" (without color_interlock, CW/WW are set directly) // - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature" - if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && // - (*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // - !(*this->color_mode_ & ColorCapability::WHITE) && // - !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // + if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && // + (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // + !(this->color_mode_ & ColorCapability::WHITE) && // + !(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", this->parent_->get_name().c_str()); - if (this->color_temperature_.has_value()) { - const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); + if (this->has_color_temperature()) { + const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); const float ww_fraction = (color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); const float cw_fraction = 1.0f - ww_fraction; const float max_cw_ww = std::max(ww_fraction, cw_fraction); this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + this->set_flag_(FLAG_HAS_COLD_WHITE, true); + this->set_flag_(FLAG_HAS_WARM_WHITE, true); } - if (this->white_.has_value()) { - this->brightness_ = *this->white_; + if (this->has_white()) { + this->brightness_ = this->white_; + this->set_flag_(FLAG_HAS_BRIGHTNESS, true); } } } @@ -378,7 +402,7 @@ ColorMode LightCall::compute_color_mode_() { // Don't change if the light is being turned off. ColorMode current_mode = this->parent_->remote_values.get_color_mode(); - if (this->state_.has_value() && !*this->state_) + if (this->has_state() && !this->state_) return current_mode; // If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to @@ -411,12 +435,12 @@ ColorMode LightCall::compute_color_mode_() { return color_mode; } std::set LightCall::get_suitable_color_modes_() { - bool has_white = this->white_.has_value() && *this->white_ > 0.0f; - bool has_ct = this->color_temperature_.has_value(); - bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || - (this->warm_white_.has_value() && *this->warm_white_ > 0.0f); - bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) || - (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()); + bool has_white = this->has_white() && this->white_ > 0.0f; + bool has_ct = this->has_color_temperature(); + bool has_cwww = + (this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f); + bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) || + (this->has_red() || this->has_green() || this->has_blue()); #define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3) #define ENTRY(white, ct, cwww, rgb, ...) \ @@ -491,7 +515,7 @@ LightCall &LightCall::from_light_color_values(const LightColorValues &values) { return *this; } ColorMode LightCall::get_active_color_mode_() { - return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode()); + return this->has_color_mode() ? this->color_mode_ : this->parent_->remote_values.get_color_mode(); } LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) { if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS) @@ -505,7 +529,7 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) { } LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) { if (this->parent_->get_traits().supports_color_mode(color_mode)) - this->color_mode_ = color_mode; + this->set_color_mode(color_mode); return *this; } LightCall &LightCall::set_color_brightness_if_supported(float brightness) { @@ -549,110 +573,19 @@ LightCall &LightCall::set_warm_white_if_supported(float warm_white) { this->set_warm_white(warm_white); return *this; } -LightCall &LightCall::set_state(optional state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_state(bool state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_transition_length(optional transition_length) { - this->transition_length_ = transition_length; - return *this; -} -LightCall &LightCall::set_transition_length(uint32_t transition_length) { - this->transition_length_ = transition_length; - return *this; -} -LightCall &LightCall::set_flash_length(optional flash_length) { - this->flash_length_ = flash_length; - return *this; -} -LightCall &LightCall::set_flash_length(uint32_t flash_length) { - this->flash_length_ = flash_length; - return *this; -} -LightCall &LightCall::set_brightness(optional brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_brightness(float brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_color_mode(optional color_mode) { - this->color_mode_ = color_mode; - return *this; -} -LightCall &LightCall::set_color_mode(ColorMode color_mode) { - this->color_mode_ = color_mode; - return *this; -} -LightCall &LightCall::set_color_brightness(optional brightness) { - this->color_brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_color_brightness(float brightness) { - this->color_brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_red(optional red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_red(float red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_green(optional green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_green(float green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_blue(optional blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_blue(float blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_white(optional white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_white(float white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_color_temperature(optional color_temperature) { - this->color_temperature_ = color_temperature; - return *this; -} -LightCall &LightCall::set_color_temperature(float color_temperature) { - this->color_temperature_ = color_temperature; - return *this; -} -LightCall &LightCall::set_cold_white(optional cold_white) { - this->cold_white_ = cold_white; - return *this; -} -LightCall &LightCall::set_cold_white(float cold_white) { - this->cold_white_ = cold_white; - return *this; -} -LightCall &LightCall::set_warm_white(optional warm_white) { - this->warm_white_ = warm_white; - return *this; -} -LightCall &LightCall::set_warm_white(float warm_white) { - this->warm_white_ = warm_white; - return *this; -} +IMPLEMENT_LIGHT_CALL_SETTER(state, bool, FLAG_HAS_STATE) +IMPLEMENT_LIGHT_CALL_SETTER(transition_length, uint32_t, FLAG_HAS_TRANSITION) +IMPLEMENT_LIGHT_CALL_SETTER(flash_length, uint32_t, FLAG_HAS_FLASH) +IMPLEMENT_LIGHT_CALL_SETTER(brightness, float, FLAG_HAS_BRIGHTNESS) +IMPLEMENT_LIGHT_CALL_SETTER(color_mode, ColorMode, FLAG_HAS_COLOR_MODE) +IMPLEMENT_LIGHT_CALL_SETTER(color_brightness, float, FLAG_HAS_COLOR_BRIGHTNESS) +IMPLEMENT_LIGHT_CALL_SETTER(red, float, FLAG_HAS_RED) +IMPLEMENT_LIGHT_CALL_SETTER(green, float, FLAG_HAS_GREEN) +IMPLEMENT_LIGHT_CALL_SETTER(blue, float, FLAG_HAS_BLUE) +IMPLEMENT_LIGHT_CALL_SETTER(white, float, FLAG_HAS_WHITE) +IMPLEMENT_LIGHT_CALL_SETTER(color_temperature, float, FLAG_HAS_COLOR_TEMPERATURE) +IMPLEMENT_LIGHT_CALL_SETTER(cold_white, float, FLAG_HAS_COLD_WHITE) +IMPLEMENT_LIGHT_CALL_SETTER(warm_white, float, FLAG_HAS_WARM_WHITE) LightCall &LightCall::set_effect(optional effect) { if (effect.has_value()) this->set_effect(*effect); @@ -660,18 +593,22 @@ LightCall &LightCall::set_effect(optional effect) { } LightCall &LightCall::set_effect(uint32_t effect_number) { this->effect_ = effect_number; + this->set_flag_(FLAG_HAS_EFFECT, true); return *this; } LightCall &LightCall::set_effect(optional effect_number) { - this->effect_ = effect_number; + if (effect_number.has_value()) { + this->effect_ = effect_number.value(); + } + this->set_flag_(FLAG_HAS_EFFECT, effect_number.has_value()); return *this; } LightCall &LightCall::set_publish(bool publish) { - this->publish_ = publish; + this->set_flag_(FLAG_PUBLISH, publish); return *this; } LightCall &LightCall::set_save(bool save) { - this->save_ = save; + this->set_flag_(FLAG_SAVE, save); return *this; } LightCall &LightCall::set_rgb(float red, float green, float blue) { diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index bca2ac7b07..7e04e1a767 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/optional.h" #include "light_color_values.h" #include @@ -10,6 +9,11 @@ namespace light { class LightState; /** This class represents a requested change in a light state. + * + * Light state changes are tracked using a bitfield flags_ to minimize memory usage. + * Each possible light property has a flag indicating whether it has been set. + * This design keeps LightCall at ~56 bytes to minimize heap fragmentation on + * ESP8266 and other memory-constrained devices. */ class LightCall { public: @@ -131,6 +135,19 @@ class LightCall { /// Set whether this light call should trigger a save state to recover them at startup.. LightCall &set_save(bool save); + // Getter methods to check if values are set + bool has_state() const { return (flags_ & FLAG_HAS_STATE) != 0; } + bool has_brightness() const { return (flags_ & FLAG_HAS_BRIGHTNESS) != 0; } + bool has_color_brightness() const { return (flags_ & FLAG_HAS_COLOR_BRIGHTNESS) != 0; } + bool has_red() const { return (flags_ & FLAG_HAS_RED) != 0; } + bool has_green() const { return (flags_ & FLAG_HAS_GREEN) != 0; } + bool has_blue() const { return (flags_ & FLAG_HAS_BLUE) != 0; } + bool has_white() const { return (flags_ & FLAG_HAS_WHITE) != 0; } + bool has_color_temperature() const { return (flags_ & FLAG_HAS_COLOR_TEMPERATURE) != 0; } + bool has_cold_white() const { return (flags_ & FLAG_HAS_COLD_WHITE) != 0; } + bool has_warm_white() const { return (flags_ & FLAG_HAS_WARM_WHITE) != 0; } + bool has_color_mode() const { return (flags_ & FLAG_HAS_COLOR_MODE) != 0; } + /** Set the RGB color of the light by RGB values. * * Please note that this only changes the color of the light, not the brightness. @@ -170,27 +187,62 @@ class LightCall { /// Some color modes also can be set using non-native parameters, transform those calls. void transform_parameters_(); - bool has_transition_() { return this->transition_length_.has_value(); } - bool has_flash_() { return this->flash_length_.has_value(); } - bool has_effect_() { return this->effect_.has_value(); } + // Bitfield flags - each flag indicates whether a corresponding value has been set. + enum FieldFlags : uint16_t { + FLAG_HAS_STATE = 1 << 0, + FLAG_HAS_TRANSITION = 1 << 1, + FLAG_HAS_FLASH = 1 << 2, + FLAG_HAS_EFFECT = 1 << 3, + FLAG_HAS_BRIGHTNESS = 1 << 4, + FLAG_HAS_COLOR_BRIGHTNESS = 1 << 5, + FLAG_HAS_RED = 1 << 6, + FLAG_HAS_GREEN = 1 << 7, + FLAG_HAS_BLUE = 1 << 8, + FLAG_HAS_WHITE = 1 << 9, + FLAG_HAS_COLOR_TEMPERATURE = 1 << 10, + FLAG_HAS_COLD_WHITE = 1 << 11, + FLAG_HAS_WARM_WHITE = 1 << 12, + FLAG_HAS_COLOR_MODE = 1 << 13, + FLAG_PUBLISH = 1 << 14, + FLAG_SAVE = 1 << 15, + }; + + bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; } + bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; } + bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; } + bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; } + bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; } + + // Helper to set flag + void set_flag_(FieldFlags flag, bool value) { + if (value) { + this->flags_ |= flag; + } else { + this->flags_ &= ~flag; + } + } LightState *parent_; - optional state_; - optional transition_length_; - optional flash_length_; - optional color_mode_; - optional brightness_; - optional color_brightness_; - optional red_; - optional green_; - optional blue_; - optional white_; - optional color_temperature_; - optional cold_white_; - optional warm_white_; - optional effect_; - bool publish_{true}; - bool save_{true}; + + // Light state values - use flags_ to check if a value has been set. + // Group 4-byte aligned members first + uint32_t transition_length_; + uint32_t flash_length_; + uint32_t effect_; + float brightness_; + float color_brightness_; + float red_; + float green_; + float blue_; + float white_; + float color_temperature_; + float cold_white_; + float warm_white_; + + // Smaller members at the end for better packing + uint16_t flags_{FLAG_PUBLISH | FLAG_SAVE}; // Tracks which values are set + ColorMode color_mode_; + bool state_; }; } // namespace light diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index d8eaa6ae24..5653a8d2a5 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -46,8 +46,7 @@ class LightColorValues { public: /// Construct the LightColorValues with all attributes enabled, but state set to off. LightColorValues() - : color_mode_(ColorMode::UNKNOWN), - state_(0.0f), + : state_(0.0f), brightness_(1.0f), color_brightness_(1.0f), red_(1.0f), @@ -56,7 +55,8 @@ class LightColorValues { white_(1.0f), color_temperature_{0.0f}, cold_white_{1.0f}, - warm_white_{1.0f} {} + warm_white_{1.0f}, + color_mode_(ColorMode::UNKNOWN) {} LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green, float blue, float white, float color_temperature, float cold_white, float warm_white) { @@ -292,7 +292,6 @@ class LightColorValues { void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); } protected: - ColorMode color_mode_; float state_; ///< ON / OFF, float for transition float brightness_; float color_brightness_; @@ -303,6 +302,7 @@ class LightColorValues { float color_temperature_; ///< Color Temperature in Mired float cold_white_; float warm_white_; + ColorMode color_mode_; }; } // namespace light diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index f21fb8a06e..72cb99223e 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -31,9 +31,7 @@ enum LightRestoreMode : uint8_t { struct LightStateRTCState { LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green, float blue, float white, float color_temp, float cold_white, float warm_white) - : color_mode(color_mode), - state(state), - brightness(brightness), + : brightness(brightness), color_brightness(color_brightness), red(red), green(green), @@ -41,10 +39,12 @@ struct LightStateRTCState { white(white), color_temp(color_temp), cold_white(cold_white), - warm_white(warm_white) {} + warm_white(warm_white), + effect(0), + color_mode(color_mode), + state(state) {} LightStateRTCState() = default; - ColorMode color_mode{ColorMode::UNKNOWN}; - bool state{false}; + // Group 4-byte aligned members first float brightness{1.0f}; float color_brightness{1.0f}; float red{1.0f}; @@ -55,6 +55,9 @@ struct LightStateRTCState { float cold_white{1.0f}; float warm_white{1.0f}; uint32_t effect{0}; + // Group smaller members at the end + ColorMode color_mode{ColorMode::UNKNOWN}; + bool state{false}; }; /** This class represents the communication layer between the front-end MQTT layer and the @@ -216,6 +219,8 @@ class LightState : public EntityBase, public Component { std::unique_ptr transformer_{nullptr}; /// List of effects for this light. std::vector effects_; + /// Object used to store the persisted values of the light. + ESPPreferenceObject rtc_; /// Value for storing the index of the currently active effect. 0 if no effect is active uint32_t active_effect_index_{}; /// Default transition length for all transitions in ms. @@ -224,15 +229,11 @@ class LightState : public EntityBase, public Component { uint32_t flash_transition_length_{}; /// Gamma correction factor for the light. float gamma_correct_{}; - /// Whether the light value should be written in the next cycle. bool next_write_{true}; // for effects, true if a transformer (transition) is active. bool is_transformer_active_ = false; - /// Object used to store the persisted values of the light. - ESPPreferenceObject rtc_; - /** Callback to call when new values for the frontend are available. * * "Remote values" are light color values that are reported to the frontend and have a lower diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index a557bd39b1..8d49acff97 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -59,9 +59,9 @@ class LightTransitionTransformer : public LightTransformer { // transition from 0 to 1 on x = [0, 1] static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } - bool changing_color_mode_{false}; LightColorValues end_values_{}; LightColorValues intermediate_values_{}; + bool changing_color_mode_{false}; }; class LightFlashTransformer : public LightTransformer { @@ -117,8 +117,8 @@ class LightFlashTransformer : public LightTransformer { protected: LightState &state_; - uint32_t transition_length_; std::unique_ptr transformer_{nullptr}; + uint32_t transition_length_; bool begun_lightstate_restore_; }; diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 3d4907aa6e..9ac2999696 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -21,6 +21,7 @@ from esphome.components.libretiny.const import ( COMPONENT_LN882X, COMPONENT_RTL87XX, ) +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ARGS, @@ -42,6 +43,7 @@ from esphome.const import ( PLATFORM_LN882X, PLATFORM_RP2040, PLATFORM_RTL87XX, + PlatformFramework, ) from esphome.core import CORE, Lambda, coroutine_with_priority @@ -444,3 +446,25 @@ async def logger_set_level_to_code(config, action_id, template_arg, args): lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) return cg.new_Pvariable(action_id, template_arg, lambda_) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "logger_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "logger_host.cpp": {PlatformFramework.HOST_NATIVE}, + "logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "logger_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "task_log_buffer.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + } +) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index a2c2aa0320..db807f7e53 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -90,6 +90,25 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. +// +// This function handles format strings stored in flash memory (PROGMEM) to save RAM. +// The buffer is used in a special way to avoid allocating extra memory: +// +// Memory layout during execution: +// Step 1: Copy format string from flash to buffer +// tx_buffer_: [format_string][null][.....................] +// tx_buffer_at_: ------------------^ +// msg_start: saved here -----------^ +// +// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning +// and writes formatted output starting at msg_start position +// tx_buffer_: [format_string][null][formatted_message][null] +// tx_buffer_at_: -------------------------------------^ +// +// Step 3: Output the formatted message (starting at msg_start) +// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start +// which points to: [formatted_message][null] +// void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) @@ -121,7 +140,9 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas if (this->baud_rate_ > 0) { this->write_msg_(this->tx_buffer_ + msg_start); } - this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start); + size_t msg_length = + this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position + this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length); global_recursion_guard_ = false; } @@ -185,7 +206,8 @@ void Logger::loop() { this->tx_buffer_size_); this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); this->tx_buffer_[this->tx_buffer_at_] = '\0'; - this->log_callback_.call(message->level, message->tag, this->tx_buffer_); + size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_ + this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len); // At this point all the data we need from message has been transferred to the tx_buffer // so we can release the message to allow other tasks to use it as soon as possible. this->log_buffer_->release_message_main_loop(received_token); @@ -214,7 +236,7 @@ void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->lo UARTSelection Logger::get_uart() const { return this->uart_; } #endif -void Logger::add_on_log_callback(std::function &&callback) { +void Logger::add_on_log_callback(std::function &&callback) { this->log_callback_.add(std::move(callback)); } float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 38faf73d84..fb68e75a51 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -143,7 +143,7 @@ class Logger : public Component { inline uint8_t level_for(const char *tag); /// Register a callback that will be called for every log message sent - void add_on_log_callback(std::function &&callback); + void add_on_log_callback(std::function &&callback); // add a listener for log level changes void add_listener(std::function &&callback) { this->level_callback_.add(std::move(callback)); } @@ -192,7 +192,7 @@ class Logger : public Component { if (this->baud_rate_ > 0) { this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console } - this->log_callback_.call(level, tag, this->tx_buffer_); + this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_); } // Write the body of the log message to the buffer @@ -246,7 +246,7 @@ class Logger : public Component { // Large objects (internally aligned) std::map log_levels_{}; - CallbackManager log_callback_{}; + CallbackManager log_callback_{}; CallbackManager level_callback_{}; #ifdef USE_ESPHOME_TASK_LOG_BUFFER std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer @@ -355,7 +355,7 @@ class Logger : public Component { } inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); + static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); } @@ -385,7 +385,7 @@ class LoggerMessageTrigger : public Trigger public: explicit LoggerMessageTrigger(Logger *parent, uint8_t level) { this->level_ = level; - parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) { + parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) { if (level <= this->level_) { this->trigger(level, tag, message); } diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 41445fa3b4..2fde0f7d49 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -184,7 +184,9 @@ void HOT Logger::write_msg_(const char *msg) { ) { puts(msg); } else { - uart_write_bytes(this->uart_num_, msg, strlen(msg)); + // Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen + size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg); + uart_write_bytes(this->uart_num_, msg, len); uart_write_bytes(this->uart_num_, "\n", 1); } } diff --git a/esphome/components/lps22/__init__.py b/esphome/components/lps22/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/lps22/lps22.cpp b/esphome/components/lps22/lps22.cpp new file mode 100644 index 0000000000..526286ba72 --- /dev/null +++ b/esphome/components/lps22/lps22.cpp @@ -0,0 +1,75 @@ +#include "lps22.h" + +namespace esphome { +namespace lps22 { + +static constexpr const char *const TAG = "lps22"; + +static constexpr uint8_t WHO_AM_I = 0x0F; +static constexpr uint8_t LPS22HB_ID = 0xB1; +static constexpr uint8_t LPS22HH_ID = 0xB3; +static constexpr uint8_t CTRL_REG2 = 0x11; +static constexpr uint8_t CTRL_REG2_ONE_SHOT_MASK = 0b1; +static constexpr uint8_t STATUS = 0x27; +static constexpr uint8_t STATUS_T_DA_MASK = 0b10; +static constexpr uint8_t STATUS_P_DA_MASK = 0b01; +static constexpr uint8_t TEMP_L = 0x2b; +static constexpr uint8_t PRES_OUT_XL = 0x28; +static constexpr uint8_t REF_P_XL = 0x28; +static constexpr uint8_t READ_ATTEMPTS = 10; +static constexpr uint8_t READ_INTERVAL = 5; +static constexpr float PRESSURE_SCALE = 1.0f / 4096.0f; +static constexpr float TEMPERATURE_SCALE = 0.01f; + +void LPS22Component::setup() { + uint8_t value = 0x00; + this->read_register(WHO_AM_I, &value, 1); + if (value != LPS22HB_ID && value != LPS22HH_ID) { + ESP_LOGW(TAG, "device IDs as %02x, which isn't a known LPS22HB or LPS22HH ID", value); + this->mark_failed(); + } +} + +void LPS22Component::dump_config() { + ESP_LOGCONFIG(TAG, "LPS22:"); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); +} + +void LPS22Component::update() { + uint8_t value = 0x00; + this->read_register(CTRL_REG2, &value, 1); + value |= CTRL_REG2_ONE_SHOT_MASK; + this->write_register(CTRL_REG2, &value, 1); + this->set_retry(READ_INTERVAL, READ_ATTEMPTS, [this](uint8_t _) { return this->try_read_(); }); +} + +RetryResult LPS22Component::try_read_() { + uint8_t value = 0x00; + this->read_register(STATUS, &value, 1); + const uint8_t expected_status_mask = STATUS_T_DA_MASK | STATUS_P_DA_MASK; + if ((value & expected_status_mask) != expected_status_mask) { + ESP_LOGD(TAG, "STATUS not ready: %x", value); + return RetryResult::RETRY; + } + + if (this->temperature_sensor_ != nullptr) { + uint8_t t_buf[2]{0}; + this->read_register(TEMP_L, t_buf, 2); + int16_t encoded = static_cast(encode_uint16(t_buf[1], t_buf[0])); + float temp = TEMPERATURE_SCALE * static_cast(encoded); + this->temperature_sensor_->publish_state(temp); + } + if (this->pressure_sensor_ != nullptr) { + uint8_t p_buf[3]{0}; + this->read_register(PRES_OUT_XL, p_buf, 3); + uint32_t p_lsb = encode_uint24(p_buf[2], p_buf[1], p_buf[0]); + this->pressure_sensor_->publish_state(PRESSURE_SCALE * static_cast(p_lsb)); + } + return RetryResult::DONE; +} + +} // namespace lps22 +} // namespace esphome diff --git a/esphome/components/lps22/lps22.h b/esphome/components/lps22/lps22.h new file mode 100644 index 0000000000..549ea524ea --- /dev/null +++ b/esphome/components/lps22/lps22.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace lps22 { + +class LPS22Component : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; } + + void setup() override; + void update() override; + void dump_config() override; + + protected: + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + + RetryResult try_read_(); +}; + +} // namespace lps22 +} // namespace esphome diff --git a/esphome/components/lps22/sensor.py b/esphome/components/lps22/sensor.py new file mode 100644 index 0000000000..08e97ee7b7 --- /dev/null +++ b/esphome/components/lps22/sensor.py @@ -0,0 +1,58 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, +) + +CODEOWNERS = ["@nagisa"] +DEPENDENCIES = ["i2c"] + +lps22 = cg.esphome_ns.namespace("lps22") + +LPS22Component = lps22.class_("LPS22Component", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LPS22Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5D)) # can also be 0x5C +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index ed230d43aa..e32d39cede 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components.esp32 import add_idf_component +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_DISABLED, @@ -8,6 +9,7 @@ from esphome.const import ( CONF_PROTOCOL, CONF_SERVICE, CONF_SERVICES, + PlatformFramework, ) from esphome.core import CORE, coroutine_with_priority @@ -108,3 +110,21 @@ async def to_code(config): ) cg.add(var.add_extra_service(exp)) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "mdns_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "mdns_host.cpp": {PlatformFramework.HOST_NATIVE}, + "mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "mdns_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index f0d5a95d43..1a6fcabf42 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -5,6 +5,7 @@ from esphome.automation import Condition import esphome.codegen as cg from esphome.components import logger from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AVAILABILITY, @@ -54,6 +55,7 @@ from esphome.const import ( PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, + PlatformFramework, ) from esphome.core import CORE, coroutine_with_priority @@ -596,3 +598,13 @@ async def mqtt_enable_to_code(config, action_id, template_arg, args): async def mqtt_disable_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) return cg.new_Pvariable(action_id, template_arg, paren) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "mqtt_backend_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + } +) diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index a096408aa5..623206a0cd 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -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"); diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 20e0b4a499..ab7fd15a35 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -57,14 +57,15 @@ void MQTTClientComponent::setup() { }); #ifdef USE_LOGGER if (this->is_log_message_enabled() && logger::global_logger != nullptr) { - logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { - if (level <= this->log_level_ && this->is_connected()) { - this->publish({.topic = this->log_message_.topic, - .payload = message, - .qos = this->log_message_.qos, - .retain = this->log_message_.retain}); - } - }); + logger::global_logger->add_on_log_callback( + [this](int level, const char *tag, const char *message, size_t message_len) { + if (level <= this->log_level_ && this->is_connected()) { + this->publish({.topic = this->log_message_.topic, + .payload = std::string(message, message_len), + .qos = this->log_message_.qos, + .retain = this->log_message_.retain}); + } + }); } #endif diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index fb75daf4ba..8adc49d68c 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,5 +1,7 @@ import esphome.codegen as cg from esphome.components import uart +from esphome.config_helpers import filter_source_files_from_platform +from esphome.const import PlatformFramework nextion_ns = cg.esphome_ns.namespace("nextion") Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) @@ -8,3 +10,17 @@ nextion_ref = Nextion.operator("ref") CONF_NEXTION_ID = "nextion_id" CONF_PUBLISH_STATE = "publish_state" CONF_SEND_TO_NEXTION = "send_to_nextion" + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "nextion_upload_arduino.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.RP2040_ARDUINO, + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF}, + } +) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index b6d4cc3f23..3628ac2f63 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -44,7 +44,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti return; if (send_to_nextion) { - if (this->nextion_->is_sleeping() || !this->visible_) { + if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp index cfb4e3600c..32929d6845 100644 --- a/esphome/components/nextion/nextion_component.cpp +++ b/esphome/components/nextion/nextion_component.cpp @@ -8,8 +8,8 @@ void NextionComponent::set_background_color(Color bco) { return; // This is a variable. no need to set color } this->bco_ = bco; - this->bco_needs_update_ = true; - this->bco_is_set_ = true; + this->component_flags_.bco_needs_update = true; + this->component_flags_.bco_is_set = true; this->update_component_settings(); } @@ -19,8 +19,8 @@ void NextionComponent::set_background_pressed_color(Color bco2) { } this->bco2_ = bco2; - this->bco2_needs_update_ = true; - this->bco2_is_set_ = true; + this->component_flags_.bco2_needs_update = true; + this->component_flags_.bco2_is_set = true; this->update_component_settings(); } @@ -29,8 +29,8 @@ void NextionComponent::set_foreground_color(Color pco) { return; // This is a variable. no need to set color } this->pco_ = pco; - this->pco_needs_update_ = true; - this->pco_is_set_ = true; + this->component_flags_.pco_needs_update = true; + this->component_flags_.pco_is_set = true; this->update_component_settings(); } @@ -39,8 +39,8 @@ void NextionComponent::set_foreground_pressed_color(Color pco2) { return; // This is a variable. no need to set color } this->pco2_ = pco2; - this->pco2_needs_update_ = true; - this->pco2_is_set_ = true; + this->component_flags_.pco2_needs_update = true; + this->component_flags_.pco2_is_set = true; this->update_component_settings(); } @@ -49,8 +49,8 @@ void NextionComponent::set_font_id(uint8_t font_id) { return; // This is a variable. no need to set color } this->font_id_ = font_id; - this->font_id_needs_update_ = true; - this->font_id_is_set_ = true; + this->component_flags_.font_id_needs_update = true; + this->component_flags_.font_id_is_set = true; this->update_component_settings(); } @@ -58,20 +58,20 @@ void NextionComponent::set_visible(bool visible) { if (this->variable_name_ == this->variable_name_to_send_) { return; // This is a variable. no need to set color } - this->visible_ = visible; - this->visible_needs_update_ = true; - this->visible_is_set_ = true; + this->component_flags_.visible = visible; + this->component_flags_.visible_needs_update = true; + this->component_flags_.visible_is_set = true; this->update_component_settings(); } void NextionComponent::update_component_settings(bool force_update) { - if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ || - (!this->visible_needs_update_ && !this->visible_)) { + if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->component_flags_.visible_is_set || + (!this->component_flags_.visible_needs_update && !this->component_flags_.visible)) { this->needs_to_send_update_ = true; return; } - if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) { + if (this->component_flags_.visible_needs_update || (force_update && this->component_flags_.visible_is_set)) { std::string name_to_send = this->variable_name_; size_t pos = name_to_send.find_last_of('.'); @@ -79,9 +79,9 @@ void NextionComponent::update_component_settings(bool force_update) { name_to_send = name_to_send.substr(pos + 1); } - this->visible_needs_update_ = false; + this->component_flags_.visible_needs_update = false; - if (this->visible_) { + if (this->component_flags_.visible) { this->nextion_->show_component(name_to_send.c_str()); this->send_state_to_nextion(); } else { @@ -90,26 +90,26 @@ void NextionComponent::update_component_settings(bool force_update) { } } - if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) { + if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) { this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_); - this->bco_needs_update_ = false; + this->component_flags_.bco_needs_update = false; } - if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) { + if (this->component_flags_.bco2_needs_update || (force_update && this->component_flags_.bco2_is_set)) { this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_); - this->bco2_needs_update_ = false; + this->component_flags_.bco2_needs_update = false; } - if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { + if (this->component_flags_.pco_needs_update || (force_update && this->component_flags_.pco_is_set)) { this->nextion_->set_component_foreground_color(this->variable_name_.c_str(), this->pco_); - this->pco_needs_update_ = false; + this->component_flags_.pco_needs_update = false; } - if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { + if (this->component_flags_.pco2_needs_update || (force_update && this->component_flags_.pco2_is_set)) { this->nextion_->set_component_pressed_foreground_color(this->variable_name_.c_str(), this->pco2_); - this->pco2_needs_update_ = false; + this->component_flags_.pco2_needs_update = false; } - if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) { + if (this->component_flags_.font_id_needs_update || (force_update && this->component_flags_.font_id_is_set)) { this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_); - this->font_id_needs_update_ = false; + this->component_flags_.font_id_needs_update = false; } } } // namespace nextion diff --git a/esphome/components/nextion/nextion_component.h b/esphome/components/nextion/nextion_component.h index 2f3c4f3c16..add9e11cf1 100644 --- a/esphome/components/nextion/nextion_component.h +++ b/esphome/components/nextion/nextion_component.h @@ -21,29 +21,64 @@ class NextionComponent : public NextionComponentBase { void set_visible(bool visible); protected: + /** + * @brief Constructor initializes component state with visible=true (default state) + */ + NextionComponent() { + component_flags_ = {}; // Zero-initialize all state + component_flags_.visible = 1; // Set default visibility to true + } + NextionBase *nextion_; - bool bco_needs_update_ = false; - bool bco_is_set_ = false; - Color bco_; - bool bco2_needs_update_ = false; - bool bco2_is_set_ = false; - Color bco2_; - bool pco_needs_update_ = false; - bool pco_is_set_ = false; - Color pco_; - bool pco2_needs_update_ = false; - bool pco2_is_set_ = false; - Color pco2_; + // Color and styling properties + Color bco_; // Background color + Color bco2_; // Pressed background color + Color pco_; // Foreground color + Color pco2_; // Pressed foreground color uint8_t font_id_ = 0; - bool font_id_needs_update_ = false; - bool font_id_is_set_ = false; - bool visible_ = true; - bool visible_needs_update_ = false; - bool visible_is_set_ = false; + /** + * @brief Component state management using compact bitfield structure + * + * Stores all component state flags and properties in a single 16-bit bitfield + * for efficient memory usage and improved cache locality. + * + * Each component property maintains two state flags: + * - needs_update: Indicates the property requires synchronization with the display + * - is_set: Tracks whether the property has been explicitly configured + * + * The visible field stores both the update flags and the actual visibility state. + */ + struct ComponentState { + // Background color flags + uint16_t bco_needs_update : 1; + uint16_t bco_is_set : 1; - // void send_state_to_nextion() = 0; + // Pressed background color flags + uint16_t bco2_needs_update : 1; + uint16_t bco2_is_set : 1; + + // Foreground color flags + uint16_t pco_needs_update : 1; + uint16_t pco_is_set : 1; + + // Pressed foreground color flags + uint16_t pco2_needs_update : 1; + uint16_t pco2_is_set : 1; + + // Font ID flags + uint16_t font_id_needs_update : 1; + uint16_t font_id_is_set : 1; + + // Visibility flags + uint16_t visible_needs_update : 1; + uint16_t visible_is_set : 1; + uint16_t visible : 1; // Actual visibility state + + // Reserved bits for future expansion + uint16_t reserved : 3; + } component_flags_; }; } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 0ed9da95d4..03b7261239 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -53,7 +53,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { if (this->wave_chan_id_ == UINT8_MAX) { if (send_to_nextion) { - if (this->nextion_->is_sleeping() || !this->visible_) { + if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index fe71182496..21636f2bfa 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -28,7 +28,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { return; if (send_to_nextion) { - if (this->nextion_->is_sleeping() || !this->visible_) { + if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index e08cbb02ca..9b6deeda87 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -26,7 +26,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s return; if (send_to_nextion) { - if (this->nextion_->is_sleeping() || !this->visible_) { + if (this->nextion_->is_sleeping() || !this->component_flags_.visible) { this->needs_to_send_update_ = true; } else { this->nextion_->add_no_result_to_queue_with_set(this, state); diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index cf5a7f5ef1..d3a2481693 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -1,5 +1,6 @@ #include "nfc.h" #include +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -7,29 +8,9 @@ namespace nfc { static const char *const TAG = "nfc"; -std::string format_uid(std::vector &uid) { - char buf[(uid.size() * 2) + uid.size() - 1]; - int offset = 0; - for (size_t i = 0; i < uid.size(); i++) { - const char *format = "%02X"; - if (i + 1 < uid.size()) - format = "%02X-"; - offset += sprintf(buf + offset, format, uid[i]); - } - return std::string(buf); -} +std::string format_uid(const std::vector &uid) { return format_hex_pretty(uid, '-', false); } -std::string format_bytes(std::vector &bytes) { - char buf[(bytes.size() * 2) + bytes.size() - 1]; - int offset = 0; - for (size_t i = 0; i < bytes.size(); i++) { - const char *format = "%02X"; - if (i + 1 < bytes.size()) - format = "%02X "; - offset += sprintf(buf + offset, format, bytes[i]); - } - return std::string(buf); -} +std::string format_bytes(const std::vector &bytes) { return format_hex_pretty(bytes, ' ', false); } uint8_t guess_tag_type(uint8_t uid_length) { if (uid_length == 4) { diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index 2e5c5cd9c5..9879cfdb03 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -2,8 +2,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "ndef_record.h" #include "ndef_message.h" +#include "ndef_record.h" #include "nfc_tag.h" #include @@ -53,8 +53,8 @@ static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; -std::string format_uid(std::vector &uid); -std::string format_bytes(std::vector &bytes); +std::string format_uid(const std::vector &uid); +std::string format_bytes(const std::vector &bytes); uint8_t guess_tag_type(uint8_t uid_length); uint8_t get_mifare_classic_ndef_start_index(std::vector &data); diff --git a/esphome/components/opt3001/sensor.py b/esphome/components/opt3001/sensor.py index a5bbf0e8dd..8490b0bd49 100644 --- a/esphome/components/opt3001/sensor.py +++ b/esphome/components/opt3001/sensor.py @@ -1,11 +1,7 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import ( - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, - UNIT_LUX, -) +import esphome.config_validation as cv +from esphome.const import DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX DEPENDENCIES = ["i2c"] CODEOWNERS = ["@ccutrer"] diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 627c55e910..4d5b8a61e2 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,5 +1,6 @@ from esphome import automation import esphome.codegen as cg +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, @@ -7,6 +8,7 @@ from esphome.const import ( CONF_OTA, CONF_PLATFORM, CONF_TRIGGER_ID, + PlatformFramework, ) from esphome.core import CORE, coroutine_with_priority @@ -120,3 +122,18 @@ async def ota_to_code(var, config): use_state_callback = True if use_state_callback: cg.add_define("USE_OTA_STATE_CALLBACK") + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "ota_backend_arduino_esp32.cpp": {PlatformFramework.ESP32_ARDUINO}, + "ota_backend_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "ota_backend_arduino_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 6684d43ff7..b6ce24bc1b 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -314,6 +314,9 @@ void PacketTransport::send_data_(bool all) { } void PacketTransport::update() { + if (!this->ping_pong_enable_) { + return; + } auto now = millis() / 1000; if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { this->resend_ping_key_ = this->ping_pong_enable_; diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 5de7d8c9c4..dffc088085 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -1,6 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import esp32, esp32_rmt, remote_base +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -15,6 +16,7 @@ from esphome.const import ( CONF_TYPE, CONF_USE_DMA, CONF_VALUE, + PlatformFramework, ) from esphome.core import CORE, TimePeriod @@ -170,3 +172,19 @@ async def to_code(config): cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) cg.add(var.set_filter_us(config[CONF_FILTER])) cg.add(var.set_idle_us(config[CONF_IDLE])) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "remote_receiver_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "remote_receiver_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "remote_receiver_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index 713cee0186..47a46ff56b 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import esp32, esp32_rmt, remote_base +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_CARRIER_DUTY_PERCENT, @@ -12,6 +13,7 @@ from esphome.const import ( CONF_PIN, CONF_RMT_SYMBOLS, CONF_USE_DMA, + PlatformFramework, ) from esphome.core import CORE @@ -95,3 +97,19 @@ async def to_code(config): await automation.build_automation( var.get_complete_trigger(), [], on_complete_config ) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "remote_transmitter_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "remote_transmitter_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "remote_transmitter_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index ecbeb83bb4..11ed97831e 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -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") diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index ce23c1f800..dd8635f0c0 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -118,7 +118,7 @@ optional QuantileFilter::new_value(float value) { size_t queue_size = quantile_queue.size(); if (queue_size) { size_t position = ceilf(queue_size * this->quantile_) - 1; - ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position + 1, queue_size); + ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %zu/%zu", this, position + 1, queue_size); result = quantile_queue[position]; } } diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 26031a8da5..e085a09eac 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.core import CORE CODEOWNERS = ["@esphome/core"] @@ -40,3 +41,18 @@ async def to_code(config): elif impl == IMPLEMENTATION_BSD_SOCKETS: cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") cg.add_define("USE_SOCKET_SELECT_SUPPORT") + + +def FILTER_SOURCE_FILES() -> list[str]: + """Return list of socket implementation files that aren't selected by the user.""" + impl = CORE.config["socket"][CONF_IMPLEMENTATION] + + # Build list of files to exclude based on selected implementation + excluded = [] + if impl != IMPLEMENTATION_LWIP_TCP: + excluded.append("lwip_raw_tcp_impl.cpp") + if impl != IMPLEMENTATION_BSD_SOCKETS: + excluded.append("bsd_sockets_impl.cpp") + if impl != IMPLEMENTATION_LWIP_SOCKETS: + excluded.append("lwip_sockets_impl.cpp") + return excluded diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 55a4b9c8f6..58bfc3f411 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -13,6 +13,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32S3, ) +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_CLK_PIN, @@ -31,6 +32,7 @@ from esphome.const import ( PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, + PlatformFramework, ) from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv @@ -423,3 +425,18 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, extra=cv.ALLOW_EXTRA, ) + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "spi_arduino.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.RP2040_ARDUINO, + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + } +) diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 0547a77184..1f039cff78 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -224,7 +224,7 @@ bool SSD1306::is_sh1106_() const { } bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || this->model_ == SH1107_MODEL_128_128; } bool SSD1306::is_ssd1305_() const { - return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64; + return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_32; } void SSD1306::update() { this->do_update_(); diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 5878af43b2..5c346ea616 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -5,13 +5,8 @@ from esphome.config_helpers import Extend, Remove, merge_config import esphome.config_validation as cv from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS from esphome.yaml_util import ESPHomeDataBase, make_data_base -from .jinja import ( - Jinja, - JinjaStr, - has_jinja, - TemplateError, - TemplateRuntimeError, -) + +from .jinja import Jinja, JinjaStr, TemplateError, TemplateRuntimeError, has_jinja CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/substitutions/jinja.py b/esphome/components/substitutions/jinja.py index 9ecdbab844..cf393d2a5d 100644 --- a/esphome/components/substitutions/jinja.py +++ b/esphome/components/substitutions/jinja.py @@ -1,6 +1,7 @@ import logging import math import re + import jinja2 as jinja from jinja2.nativetypes import NativeEnvironment diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 492febe283..b6aeaf072c 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -167,8 +167,8 @@ def validate_config(config): if config[CONF_MODULATION] == "LORA": if config[CONF_BANDWIDTH] not in lora_bws: raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") - if config[CONF_PREAMBLE_SIZE] > 0 and config[CONF_PREAMBLE_SIZE] < 6: - raise cv.Invalid("Minimum preamble size is 6 with LORA") + if config[CONF_PREAMBLE_SIZE] < 6: + raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA") if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: raise cv.Invalid("Payload length must be set when spreading factor is 6") else: @@ -200,7 +200,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), - cv.Required(CONF_PREAMBLE_SIZE): cv.int_range(min=1, max=65535), + cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535), cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RX_START, default=True): cv.boolean, cv.Required(CONF_RF_SWITCH): cv.boolean, diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index 4d034801cc..33b556db07 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -164,8 +164,8 @@ def validate_config(config): raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") if CONF_DIO0_PIN not in config: raise cv.Invalid("Cannot use LoRa without dio0_pin") - if 0 < config[CONF_PREAMBLE_SIZE] < 6: - raise cv.Invalid("Minimum preamble size is 6 with LORA") + if config[CONF_PREAMBLE_SIZE] < 6: + raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA") if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: raise cv.Invalid("Payload length must be set when spreading factor is 6") else: diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 9d2cda549b..e322a6951d 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -21,10 +21,12 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { void Syslog::setup() { logger::global_logger->add_on_log_callback( - [this](int level, const char *tag, const char *message) { this->log_(level, tag, message); }); + [this](int level, const char *tag, const char *message, size_t message_len) { + this->log_(level, tag, message, message_len); + }); } -void Syslog::log_(const int level, const char *tag, const char *message) const { +void Syslog::log_(const int level, const char *tag, const char *message, size_t message_len) const { if (level > this->log_level_) return; // Syslog PRI calculation: facility * 8 + severity @@ -34,7 +36,7 @@ void Syslog::log_(const int level, const char *tag, const char *message) const { } int pri = this->facility_ * 8 + severity; auto timestamp = this->time_->now().strftime("%b %d %H:%M:%S"); - unsigned len = strlen(message); + size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { message += 7; diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index 421a9bee73..e3b2f7dae5 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -17,7 +17,7 @@ class Syslog : public Component, public Parented { protected: int log_level_; - void log_(int level, const char *tag, const char *message) const; + void log_(int level, const char *tag, const char *message, size_t message_len) const; time::RealTimeClock *time_; bool strip_{true}; int facility_{16}; diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index a0908a299c..7d4c6360fe 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -2,6 +2,7 @@ import re from esphome import automation, pins import esphome.codegen as cg +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AFTER, @@ -27,6 +28,7 @@ from esphome.const import ( CONF_TX_PIN, CONF_UART_ID, PLATFORM_HOST, + PlatformFramework, ) from esphome.core import CORE import esphome.final_validate as fv @@ -438,3 +440,19 @@ async def uart_write_to_code(config, action_id, template_arg, args): else: cg.add(var.set_data_static(data)) return var + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "uart_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO}, + "uart_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "uart_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "uart_component_host.cpp": {PlatformFramework.HOST_NATIVE}, + "uart_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "uart_component_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + } +) diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index fc5f772f41..edf6c94b07 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -70,7 +70,7 @@ static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) { ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2); } -void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { +static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { if (devc_desc == NULL) { return; } @@ -92,8 +92,8 @@ void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations); } -void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, - print_class_descriptor_cb class_specific_cb) { +static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, + print_class_descriptor_cb class_specific_cb) { if (cfg_desc == nullptr) { return; } @@ -128,9 +128,9 @@ void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, static std::string get_descriptor_string(const usb_str_desc_t *desc) { char buffer[256]; if (desc == nullptr) - return "(unknown)"; + return "(unspecified)"; char *p = buffer; - for (size_t i = 0; i != desc->bLength / 2; i++) { + for (int i = 0; i != desc->bLength / 2; i++) { auto c = desc->wData[i]; if (c < 0x100) *p++ = static_cast(c); @@ -169,7 +169,7 @@ void USBClient::setup() { this->mark_failed(); return; } - for (auto trq : this->trq_pool_) { + for (auto *trq : this->trq_pool_) { usb_host_transfer_alloc(64, 0, &trq->transfer); trq->client = this; } @@ -197,7 +197,8 @@ void USBClient::loop() { ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct); if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) { usb_device_info_t dev_info; - if ((err = usb_host_device_info(this->device_handle_, &dev_info)) != ESP_OK) { + err = usb_host_device_info(this->device_handle_, &dev_info); + if (err != ESP_OK) { ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err)); this->disconnect(); break; @@ -336,7 +337,7 @@ static void transfer_callback(usb_transfer_t *xfer) { * @throws None. */ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { - auto trq = this->get_trq_(); + auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); return; @@ -349,7 +350,6 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); this->release_trq(trq); - this->disconnect(); } } @@ -364,7 +364,7 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u * @throws None. */ void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { - auto trq = this->get_trq_(); + auto *trq = this->get_trq_(); if (trq == nullptr) { ESP_LOGE(TAG, "Too many requests queued"); return; diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index 267385d1bd..f7d60c307a 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -43,7 +43,7 @@ static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate. static constexpr uint8_t SET_CHARS = 0x19; // Set special characters. static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command. -std::vector USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) { +std::vector USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev_hdl) { const usb_config_desc_t *config_desc; const usb_device_desc_t *device_desc; int conf_offset = 0, ep_offset; diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index e599409f0c..934306f480 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -18,52 +18,48 @@ namespace usb_uart { */ static optional get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) { int conf_offset, ep_offset; - const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{}; - uint8_t interface_number = 0; - // look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out) + // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out) + CdcEps eps{}; + eps.bulk_interface_number = 0xFF; for (;;) { - auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset); + const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset); if (!intf_desc) { ESP_LOGE(TAG, "usb_parse_interface_descriptor failed"); return nullopt; } - if (intf_desc->bNumEndpoints == 1) { + ESP_LOGD(TAG, "intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d", + intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol, + intf_desc->bNumEndpoints); + for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) { ep_offset = conf_offset; - notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); - if (!notify_ep) { - ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed"); + const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset); + if (!ep) { + ESP_LOGE(TAG, "Ran out of interfaces at %d before finding all endpoints", i); return nullopt; } - if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT) - notify_ep = nullptr; - } else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) { - interface_number = intf_desc->bInterfaceNumber; - ep_offset = conf_offset; - out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); - if (!out_ep) { - ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed"); - return nullopt; + ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes); + if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) { + eps.notify_ep = ep; + eps.interrupt_interface_number = intf_desc->bInterfaceNumber; + } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN && + (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) { + eps.in_ep = ep; + eps.bulk_interface_number = intf_desc->bInterfaceNumber; + } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) && + (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) { + eps.out_ep = ep; + eps.bulk_interface_number = intf_desc->bInterfaceNumber; + } else { + ESP_LOGE(TAG, "Unexpected endpoint attributes: %02X", ep->bmAttributes); + continue; } - if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK) - out_ep = nullptr; - ep_offset = conf_offset; - in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset); - if (!in_ep) { - ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed"); - return nullopt; - } - if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK) - in_ep = nullptr; } - if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr) - break; + if (eps.in_ep != nullptr && eps.out_ep != nullptr && eps.notify_ep != nullptr) + return eps; } - if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN) - return CdcEps{notify_ep, in_ep, out_ep, interface_number}; - return CdcEps{notify_ep, out_ep, in_ep, interface_number}; } -std::vector USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) { +std::vector USBUartTypeCdcAcm::parse_descriptors(usb_device_handle_t dev_hdl) { const usb_config_desc_t *config_desc; const usb_device_desc_t *device_desc; int desc_offset = 0; @@ -78,7 +74,7 @@ std::vector USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t de ESP_LOGE(TAG, "get_active_config_descriptor failed"); return {}; } - if (device_desc->bDeviceClass == USB_CLASS_COMM) { + if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) { // single CDC-ACM device if (auto eps = get_cdc(config_desc, 0)) { ESP_LOGV(TAG, "Found CDC-ACM device"); @@ -194,7 +190,7 @@ void USBUartComponent::start_input(USBUartChannel *channel) { if (!channel->initialised_ || channel->input_started_ || channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize) return; - auto ep = channel->cdc_dev_.in_ep; + const auto *ep = channel->cdc_dev_.in_ep; auto callback = [this, channel](const usb_host::TransferStatus &status) { ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); if (!status.success) { @@ -227,7 +223,7 @@ void USBUartComponent::start_output(USBUartChannel *channel) { if (channel->output_buffer_.is_empty()) { return; } - auto ep = channel->cdc_dev_.out_ep; + const auto *ep = channel->cdc_dev_.out_ep; auto callback = [this, channel](const usb_host::TransferStatus &status) { ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code); channel->output_started_ = false; @@ -259,15 +255,15 @@ static void fix_mps(const usb_ep_desc_t *ep) { } } void USBUartTypeCdcAcm::on_connected() { - auto cdc_devs = this->parse_descriptors_(this->device_handle_); + auto cdc_devs = this->parse_descriptors(this->device_handle_); if (cdc_devs.empty()) { this->status_set_error("No CDC-ACM device found"); this->disconnect(); return; } ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size()); - auto i = 0; - for (auto channel : this->channels_) { + size_t i = 0; + for (auto *channel : this->channels_) { if (i == cdc_devs.size()) { ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_); this->status_set_warning("No configuration found for channel"); @@ -277,10 +273,11 @@ void USBUartTypeCdcAcm::on_connected() { fix_mps(channel->cdc_dev_.in_ep); fix_mps(channel->cdc_dev_.out_ep); channel->initialised_ = true; - auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0); + auto err = + usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_, - channel->cdc_dev_.interface_number); + channel->cdc_dev_.bulk_interface_number); this->status_set_error("usb_host_interface_claim failed"); this->disconnect(); return; @@ -290,7 +287,7 @@ void USBUartTypeCdcAcm::on_connected() { } void USBUartTypeCdcAcm::on_disconnected() { - for (auto channel : this->channels_) { + for (auto *channel : this->channels_) { if (channel->cdc_dev_.in_ep != nullptr) { usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); @@ -303,7 +300,7 @@ void USBUartTypeCdcAcm::on_disconnected() { usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); } - usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number); + usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number); channel->initialised_ = false; channel->input_started_ = false; channel->output_started_ = false; @@ -314,7 +311,7 @@ void USBUartTypeCdcAcm::on_disconnected() { } void USBUartTypeCdcAcm::enable_channels() { - for (auto channel : this->channels_) { + for (auto *channel : this->channels_) { if (!channel->initialised_) continue; channel->input_started_ = false; diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index fd0fb2c59a..a103c51add 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -25,7 +25,8 @@ struct CdcEps { const usb_ep_desc_t *notify_ep; const usb_ep_desc_t *in_ep; const usb_ep_desc_t *out_ep; - uint8_t interface_number; + uint8_t bulk_interface_number; + uint8_t interrupt_interface_number; }; enum UARTParityOptions { @@ -123,7 +124,7 @@ class USBUartTypeCdcAcm : public USBUartComponent { USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {} protected: - virtual std::vector parse_descriptors_(usb_device_handle_t dev_hdl); + virtual std::vector parse_descriptors(usb_device_handle_t dev_hdl); void on_connected() override; virtual void enable_channels(); void on_disconnected() override; @@ -134,7 +135,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm { USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {} protected: - std::vector parse_descriptors_(usb_device_handle_t dev_hdl) override; + std::vector parse_descriptors(usb_device_handle_t dev_hdl) override; void enable_channels() override; }; class USBUartTypeCH34X : public USBUartTypeCdcAcm { diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 575234e780..75c6b84b79 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -3132,7 +3132,7 @@ void HOT GDEY0583T81::display() { } else { // Partial out (PTOUT), makes the display exit partial mode this->command(0x92); - ESP_LOGD(TAG, "Partial update done, next full update after %d cycles", + ESP_LOGD(TAG, "Partial update done, next full update after %" PRIu32 " cycles", this->full_update_every_ - this->at_update_ - 1); } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 20ff1a7c29..8ced5b7e18 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -287,7 +287,8 @@ void WebServer::setup() { if (logger::global_logger != nullptr && this->expose_log_) { logger::global_logger->add_on_log_callback( // logs are not deferred, the memory overhead would be too large - [this](int level, const char *tag, const char *message) { + [this](int level, const char *tag, const char *message, size_t message_len) { + (void) message_len; this->events_.try_send_nodefer(message, "log", millis()); }); } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 0c15881d1e..ef1b03a73b 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -78,7 +78,7 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; This is because only minimal changes were made to the ESPAsyncWebServer lib_dep, it was undesirable to put deferred update logic into that library. We need one deferred queue per connection so instead of one AsyncEventSource with multiple clients, we have multiple event sources with one client each. This is slightly awkward which is why it's - implemented in a more straightforward way for ESP-IDF. Arudino platform will eventually go away and this workaround + implemented in a more straightforward way for ESP-IDF. Arduino platform will eventually go away and this workaround can be forgotten. */ #ifdef USE_ARDUINO diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index e8ae9b1b4e..61f37556ba 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -3,6 +3,7 @@ from esphome.automation import Condition import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant from esphome.components.network import IPAddress +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AP, @@ -39,6 +40,7 @@ from esphome.const import ( CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, + PlatformFramework, ) from esphome.core import CORE, HexInt, coroutine_with_priority import esphome.final_validate as fv @@ -526,3 +528,18 @@ async def wifi_set_sta_to_code(config, action_id, template_arg, args): await automation.build_automation(var.get_error_trigger(), [], on_error_config) await cg.register_component(var, config) return var + + +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "wifi_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO}, + "wifi_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "wifi_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "wifi_component_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, + "wifi_component_pico_w.cpp": {PlatformFramework.RP2040_ARDUINO}, + } +) diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index 54242bc259..50ce4e8e34 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -1,4 +1,20 @@ -from esphome.const import CONF_ID +from collections.abc import Callable + +from esphome.const import ( + CONF_ID, + CONF_LEVEL, + CONF_LOGGER, + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + PlatformFramework, +) +from esphome.core import CORE + +# Pre-build lookup map from (platform, framework) tuples to PlatformFramework enum +_PLATFORM_FRAMEWORK_LOOKUP = { + (pf.value[0].value, pf.value[1].value): pf for pf in PlatformFramework +} class Extend: @@ -103,3 +119,60 @@ def merge_config(full_old, full_new): return new return merge(full_old, full_new) + + +def filter_source_files_from_platform( + files_map: dict[str, set[PlatformFramework]], +) -> Callable[[], list[str]]: + """Helper to build a FILTER_SOURCE_FILES function from platform mapping. + + Args: + files_map: Dict mapping filename to set of PlatformFramework enums + that should compile this file + + Returns: + Function that returns list of files to exclude for current platform + """ + + def filter_source_files() -> list[str]: + # Get current platform/framework + core_data = CORE.data.get(KEY_CORE, {}) + target_platform = core_data.get(KEY_TARGET_PLATFORM) + target_framework = core_data.get(KEY_TARGET_FRAMEWORK) + + if not target_platform or not target_framework: + return [] + + # Direct lookup of current PlatformFramework + current_platform_framework = _PLATFORM_FRAMEWORK_LOOKUP.get( + (target_platform, target_framework) + ) + + if not current_platform_framework: + return [] + + # Return files that should be excluded for current platform + return [ + filename + for filename, platforms in files_map.items() + if current_platform_framework not in platforms + ] + + return filter_source_files + + +def get_logger_level() -> str: + """Get the configured logger level. + + This is used by components to determine what logging features to include + based on the configured log level. + + Returns: + The configured logger level string, defaults to "DEBUG" if not configured + """ + # Check if logger config exists + if CONF_LOGGER not in CORE.config: + return "DEBUG" + + logger_config = CORE.config[CONF_LOGGER] + return logger_config.get(CONF_LEVEL, "DEBUG") diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 09b132a458..b1691fa43e 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -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)?") diff --git a/esphome/const.py b/esphome/const.py index 4aeb5179e6..a30df6ef35 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,20 +1,65 @@ """Constants used by esphome.""" -__version__ = "2025.7.0-dev" +from enum import Enum + +from esphome.enum import StrEnum + +__version__ = "2025.8.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" ) -PLATFORM_BK72XX = "bk72xx" -PLATFORM_ESP32 = "esp32" -PLATFORM_ESP8266 = "esp8266" -PLATFORM_HOST = "host" -PLATFORM_LIBRETINY_OLDSTYLE = "libretiny" -PLATFORM_LN882X = "ln882x" -PLATFORM_RP2040 = "rp2040" -PLATFORM_RTL87XX = "rtl87xx" + +class Platform(StrEnum): + """Platform identifiers for ESPHome.""" + + BK72XX = "bk72xx" + ESP32 = "esp32" + ESP8266 = "esp8266" + HOST = "host" + LIBRETINY_OLDSTYLE = "libretiny" + LN882X = "ln882x" + RP2040 = "rp2040" + RTL87XX = "rtl87xx" + + +class Framework(StrEnum): + """Framework identifiers for ESPHome.""" + + ARDUINO = "arduino" + ESP_IDF = "esp-idf" + NATIVE = "host" + + +class PlatformFramework(Enum): + """Combined platform-framework identifiers with tuple values.""" + + # ESP32 variants + ESP32_ARDUINO = (Platform.ESP32, Framework.ARDUINO) + ESP32_IDF = (Platform.ESP32, Framework.ESP_IDF) + + # Arduino framework platforms + ESP8266_ARDUINO = (Platform.ESP8266, Framework.ARDUINO) + RP2040_ARDUINO = (Platform.RP2040, Framework.ARDUINO) + BK72XX_ARDUINO = (Platform.BK72XX, Framework.ARDUINO) + RTL87XX_ARDUINO = (Platform.RTL87XX, Framework.ARDUINO) + LN882X_ARDUINO = (Platform.LN882X, Framework.ARDUINO) + + # Host platform (native) + HOST_NATIVE = (Platform.HOST, Framework.NATIVE) + + +# Maintain backward compatibility by reassigning after enum definition +PLATFORM_BK72XX = Platform.BK72XX +PLATFORM_ESP32 = Platform.ESP32 +PLATFORM_ESP8266 = Platform.ESP8266 +PLATFORM_HOST = Platform.HOST +PLATFORM_LIBRETINY_OLDSTYLE = Platform.LIBRETINY_OLDSTYLE +PLATFORM_LN882X = Platform.LN882X +PLATFORM_RP2040 = Platform.RP2040 +PLATFORM_RTL87XX = Platform.RTL87XX SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} diff --git a/esphome/core/application.h b/esphome/core/application.h index 6ee05309ca..f2b5cb5c89 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -368,6 +368,17 @@ class Application { uint8_t get_app_state() const { return this->app_state_; } +// Helper macro for entity getter method declarations - reduces code duplication +// When USE_DEVICE_ID is enabled in the future, this can be conditionally compiled to add device_id parameter +#define GET_ENTITY_METHOD(entity_type, entity_name, entities_member) \ + entity_type *get_##entity_name##_by_key(uint32_t key, bool include_internal = false) { \ + for (auto *obj : this->entities_member##_) { \ + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) \ + return obj; \ + } \ + return nullptr; \ + } + #ifdef USE_DEVICES const std::vector &get_devices() { return this->devices_; } #endif @@ -376,218 +387,92 @@ class Application { #endif #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } - binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->binary_sensors_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(binary_sensor::BinarySensor, binary_sensor, binary_sensors) #endif #ifdef USE_SWITCH const std::vector &get_switches() { return this->switches_; } - switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->switches_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(switch_::Switch, switch, switches) #endif #ifdef USE_BUTTON const std::vector &get_buttons() { return this->buttons_; } - button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->buttons_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(button::Button, button, buttons) #endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } - sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->sensors_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(sensor::Sensor, sensor, sensors) #endif #ifdef USE_TEXT_SENSOR const std::vector &get_text_sensors() { return this->text_sensors_; } - text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->text_sensors_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(text_sensor::TextSensor, text_sensor, text_sensors) #endif #ifdef USE_FAN const std::vector &get_fans() { return this->fans_; } - fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->fans_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(fan::Fan, fan, fans) #endif #ifdef USE_COVER const std::vector &get_covers() { return this->covers_; } - cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->covers_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(cover::Cover, cover, covers) #endif #ifdef USE_LIGHT const std::vector &get_lights() { return this->lights_; } - light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->lights_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(light::LightState, light, lights) #endif #ifdef USE_CLIMATE const std::vector &get_climates() { return this->climates_; } - climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->climates_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(climate::Climate, climate, climates) #endif #ifdef USE_NUMBER const std::vector &get_numbers() { return this->numbers_; } - number::Number *get_number_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->numbers_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(number::Number, number, numbers) #endif #ifdef USE_DATETIME_DATE const std::vector &get_dates() { return this->dates_; } - datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->dates_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(datetime::DateEntity, date, dates) #endif #ifdef USE_DATETIME_TIME const std::vector &get_times() { return this->times_; } - datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->times_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(datetime::TimeEntity, time, times) #endif #ifdef USE_DATETIME_DATETIME const std::vector &get_datetimes() { return this->datetimes_; } - datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->datetimes_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(datetime::DateTimeEntity, datetime, datetimes) #endif #ifdef USE_TEXT const std::vector &get_texts() { return this->texts_; } - text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->texts_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(text::Text, text, texts) #endif #ifdef USE_SELECT const std::vector &get_selects() { return this->selects_; } - select::Select *get_select_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->selects_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(select::Select, select, selects) #endif #ifdef USE_LOCK const std::vector &get_locks() { return this->locks_; } - lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->locks_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(lock::Lock, lock, locks) #endif #ifdef USE_VALVE const std::vector &get_valves() { return this->valves_; } - valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->valves_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(valve::Valve, valve, valves) #endif #ifdef USE_MEDIA_PLAYER const std::vector &get_media_players() { return this->media_players_; } - media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->media_players_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(media_player::MediaPlayer, media_player, media_players) #endif #ifdef USE_ALARM_CONTROL_PANEL const std::vector &get_alarm_control_panels() { return this->alarm_control_panels_; } - alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->alarm_control_panels_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels) #endif #ifdef USE_EVENT const std::vector &get_events() { return this->events_; } - event::Event *get_event_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->events_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(event::Event, event, events) #endif #ifdef USE_UPDATE const std::vector &get_updates() { return this->updates_; } - update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->updates_) { - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - } - return nullptr; - } + GET_ENTITY_METHOD(update::UpdateEntity, update, updates) #endif Scheduler scheduler; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 9ef30081aa..9d863e56cd 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -26,17 +26,17 @@ static const char *const TAG = "component"; // 1. Components are never destroyed in ESPHome // 2. Failed components remain failed (no recovery mechanism) // 3. Memory usage is minimal (only failures with custom messages are stored) -static std::unique_ptr>> &get_component_error_messages() { - static std::unique_ptr>> instance; - return instance; -} +// Using namespace-scope static to avoid guard variables (saves 16 bytes total) +// This is safe because ESPHome is single-threaded during initialization +namespace { +// Error messages for failed components +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +std::unique_ptr>> component_error_messages; // Setup priority overrides - freed after setup completes -// Typically < 5 entries, lazy allocated -static std::unique_ptr>> &get_setup_priority_overrides() { - static std::unique_ptr>> instance; - return instance; -} +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +std::unique_ptr>> setup_priority_overrides; +} // namespace namespace setup_priority { @@ -130,8 +130,8 @@ void Component::call_dump_config() { if (this->is_failed()) { // Look up error message from global vector const char *error_msg = "unspecified"; - if (get_component_error_messages()) { - for (const auto &pair : *get_component_error_messages()) { + if (component_error_messages) { + for (const auto &pair : *component_error_messages) { if (pair.first == this) { error_msg = pair.second; break; @@ -285,18 +285,18 @@ void Component::status_set_error(const char *message) { ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); if (strcmp(message, "unspecified") != 0) { // Lazy allocate the error messages vector if needed - if (!get_component_error_messages()) { - get_component_error_messages() = std::make_unique>>(); + if (!component_error_messages) { + component_error_messages = std::make_unique>>(); } // Check if this component already has an error message - for (auto &pair : *get_component_error_messages()) { + for (auto &pair : *component_error_messages) { if (pair.first == this) { pair.second = message; return; } } // Add new error message - get_component_error_messages()->emplace_back(this, message); + component_error_messages->emplace_back(this, message); } } void Component::status_clear_warning() { @@ -322,9 +322,9 @@ void Component::status_momentary_error(const std::string &name, uint32_t length) void Component::dump_config() {} float Component::get_actual_setup_priority() const { // Check if there's an override in the global vector - if (get_setup_priority_overrides()) { + if (setup_priority_overrides) { // Linear search is fine for small n (typically < 5 overrides) - for (const auto &pair : *get_setup_priority_overrides()) { + for (const auto &pair : *setup_priority_overrides) { if (pair.first == this) { return pair.second; } @@ -334,14 +334,14 @@ float Component::get_actual_setup_priority() const { } void Component::set_setup_priority(float priority) { // Lazy allocate the vector if needed - if (!get_setup_priority_overrides()) { - get_setup_priority_overrides() = std::make_unique>>(); + if (!setup_priority_overrides) { + setup_priority_overrides = std::make_unique>>(); // Reserve some space to avoid reallocations (most configs have < 10 overrides) - get_setup_priority_overrides()->reserve(10); + setup_priority_overrides->reserve(10); } // Check if this component already has an override - for (auto &pair : *get_setup_priority_overrides()) { + for (auto &pair : *setup_priority_overrides) { if (pair.first == this) { pair.second = priority; return; @@ -349,7 +349,7 @@ void Component::set_setup_priority(float priority) { } // Add new override - get_setup_priority_overrides()->emplace_back(this, priority); + setup_priority_overrides->emplace_back(this, priority); } bool Component::has_overridden_loop() const { @@ -414,7 +414,7 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {} void clear_setup_priority_overrides() { // Free the setup priority map completely - get_setup_priority_overrides().reset(); + setup_priority_overrides.reset(); } } // namespace esphome diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index aab5c2a72d..d27c4e70ba 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -4,6 +4,8 @@ #ifdef USE_API #include "esphome/components/api/api_server.h" +#endif +#ifdef USE_API_SERVICES #include "esphome/components/api/user_services.h" #endif @@ -148,7 +150,7 @@ void ComponentIterator::advance() { } break; #endif -#ifdef USE_API +#ifdef USE_API_SERVICES case IteratorState ::SERVICE: if (this->at_ >= api::global_api_server->get_user_services().size()) { advance_platform = true; @@ -383,7 +385,7 @@ void ComponentIterator::advance() { } bool ComponentIterator::on_end() { return true; } bool ComponentIterator::on_begin() { return true; } -#ifdef USE_API +#ifdef USE_API_SERVICES bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } #endif #ifdef USE_CAMERA diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index eda786be7f..ea2c8004ac 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -10,7 +10,7 @@ namespace esphome { -#ifdef USE_API +#ifdef USE_API_SERVICES namespace api { class UserServiceDescriptor; } // namespace api @@ -45,7 +45,7 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif -#ifdef USE_API +#ifdef USE_API_SERVICES virtual bool on_service(api::UserServiceDescriptor *service); #endif #ifdef USE_CAMERA @@ -122,7 +122,7 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif -#ifdef USE_API +#ifdef USE_API_SERVICES SERVICE, #endif #ifdef USE_CAMERA diff --git a/esphome/core/config.py b/esphome/core/config.py index 641c73a292..f73369f28f 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -6,6 +6,7 @@ from pathlib import Path from esphome import automation, core import esphome.codegen as cg +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AREA, @@ -35,6 +36,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VERSION, KEY_CORE, + PlatformFramework, __version__ as ESPHOME_VERSION, ) from esphome.core import CORE, coroutine_with_priority @@ -551,3 +553,16 @@ async def to_code(config: ConfigType) -> None: cg.add(dev.set_area_id(area_id_hash)) cg.add(cg.App.register_device(dev)) + + +# Platform-specific source files for core +FILTER_SOURCE_FILES = filter_source_files_from_platform( + { + "ring_buffer.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + # Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered + # as they are only included when needed by the preprocessor + } +) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 4115b97391..8ed8f4b5aa 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -33,6 +33,7 @@ #define USE_DEEP_SLEEP #define USE_DEVICES #define USE_DISPLAY +#define USE_ENTITY_ICON #define USE_ESP32_IMPROV_STATE_CALLBACK #define USE_EVENT #define USE_FAN @@ -107,7 +108,7 @@ #define USE_API_CLIENT_DISCONNECTED_TRIGGER #define USE_API_NOISE #define USE_API_PLAINTEXT -#define USE_API_YAML_SERVICES +#define USE_API_SERVICES #define USE_MD5 #define USE_MQTT #define USE_NETWORK diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 6afd02ff65..2ea9c77a3e 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -27,12 +27,22 @@ void EntityBase::set_name(const char *name) { // Entity Icon std::string EntityBase::get_icon() const { +#ifdef USE_ENTITY_ICON if (this->icon_c_str_ == nullptr) { return ""; } return this->icon_c_str_; +#else + return ""; +#endif +} +void EntityBase::set_icon(const char *icon) { +#ifdef USE_ENTITY_ICON + this->icon_c_str_ = icon; +#else + // No-op when USE_ENTITY_ICON is not defined +#endif } -void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; } // Entity Object ID std::string EntityBase::get_object_id() const { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 4819b66108..00b1264ed0 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -80,7 +80,9 @@ class EntityBase { StringRef name_; const char *object_id_c_str_{nullptr}; +#ifdef USE_ENTITY_ICON const char *icon_c_str_{nullptr}; +#endif uint32_t object_id_hash_{}; #ifdef USE_DEVICES Device *device_{}; diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 2442fbca4b..5ad16ac76c 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -1,6 +1,7 @@ from collections.abc import Callable import logging +import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( CONF_DEVICE_ID, @@ -108,6 +109,8 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: if CONF_INTERNAL in config: add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: + # Add USE_ENTITY_ICON define when icons are used + cg.add_define("USE_ENTITY_ICON") add(var.set_icon(config[CONF_ICON])) if CONF_ENTITY_CATEGORY in config: add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) @@ -184,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] diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 72722169d4..b46077af02 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -258,53 +258,60 @@ std::string format_hex(const uint8_t *data, size_t length) { std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } -std::string format_hex_pretty(const uint8_t *data, size_t length) { - if (length == 0) +std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length) { + if (data == nullptr || length == 0) return ""; std::string ret; - ret.resize(3 * length - 1); + uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise + ret.resize(multiple * length - (separator ? 1 : 0)); for (size_t i = 0; i < length; i++) { - ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); - ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); - if (i != length - 1) - ret[3 * i + 2] = '.'; + ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (separator && i != length - 1) + ret[multiple * i + 2] = separator; } - if (length > 4) - return ret + " (" + to_string(length) + ")"; + if (show_length && length > 4) + return ret + " (" + std::to_string(length) + ")"; return ret; } -std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const std::vector &data, char separator, bool show_length) { + return format_hex_pretty(data.data(), data.size(), separator, show_length); +} -std::string format_hex_pretty(const uint16_t *data, size_t length) { - if (length == 0) +std::string format_hex_pretty(const uint16_t *data, size_t length, char separator, bool show_length) { + if (data == nullptr || length == 0) return ""; std::string ret; - ret.resize(5 * length - 1); + uint8_t multiple = separator ? 5 : 4; // 5 if separator is not \0, 4 otherwise + ret.resize(multiple * length - (separator ? 1 : 0)); for (size_t i = 0; i < length; i++) { - ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); - ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); - ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); - ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F); - if (i != length - 1) - ret[5 * i + 2] = '.'; + ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + ret[multiple * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + ret[multiple * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + ret[multiple * i + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (separator && i != length - 1) + ret[multiple * i + 4] = separator; } - if (length > 4) - return ret + " (" + to_string(length) + ")"; + if (show_length && length > 4) + return ret + " (" + std::to_string(length) + ")"; return ret; } -std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } -std::string format_hex_pretty(const std::string &data) { +std::string format_hex_pretty(const std::vector &data, char separator, bool show_length) { + return format_hex_pretty(data.data(), data.size(), separator, show_length); +} +std::string format_hex_pretty(const std::string &data, char separator, bool show_length) { if (data.empty()) return ""; std::string ret; - ret.resize(3 * data.length() - 1); + uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise + ret.resize(multiple * data.length() - (separator ? 1 : 0)); for (size_t i = 0; i < data.length(); i++) { - ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); - ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); - if (i != data.length() - 1) - ret[3 * i + 2] = '.'; + ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (separator && i != data.length() - 1) + ret[multiple * i + 2] = separator; } - if (data.length() > 4) + if (show_length && data.length() > 4) return ret + " (" + std::to_string(data.length()) + ")"; return ret; } @@ -360,9 +367,22 @@ int8_t step_to_accuracy_decimals(float step) { return str.length() - dot_pos - 1; } -static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; +// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes) +static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +// Helper function to find the index of a base64 character in the lookup table. +// Returns the character's position (0-63) if found, or 0 if not found. +// NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters. +// This is safe because is_base64() is ALWAYS checked before calling this function, +// preventing invalid characters from ever reaching here. The base64_decode function +// stops processing at the first invalid character due to the is_base64() check in its +// while loop condition, making this edge case harmless in practice. +static inline uint8_t base64_find_char(char c) { + const char *pos = strchr(BASE64_CHARS, c); + return pos ? (pos - BASE64_CHARS) : 0; +} static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } @@ -384,7 +404,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { char_array_4[3] = char_array_3[2] & 0x3f; for (i = 0; (i < 4); i++) - ret += BASE64_CHARS[char_array_4[i]]; + ret += BASE64_CHARS[static_cast(char_array_4[i])]; i = 0; } } @@ -399,7 +419,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) - ret += BASE64_CHARS[char_array_4[j]]; + ret += BASE64_CHARS[static_cast(char_array_4[j])]; while ((i++ < 3)) ret += '='; @@ -426,12 +446,15 @@ std::vector base64_decode(const std::string &encoded_string) { uint8_t char_array_4[4], char_array_3[3]; std::vector ret; + // SAFETY: The loop condition checks is_base64() before processing each character. + // This ensures base64_find_char() is only called on valid base64 characters, + // preventing the edge case where invalid chars would return 0 (same as 'A'). while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) { char_array_4[i++] = encoded_string[in]; in++; if (i == 4) { for (i = 0; i < 4; i++) - char_array_4[i] = BASE64_CHARS.find(char_array_4[i]); + char_array_4[i] = base64_find_char(char_array_4[i]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); @@ -448,7 +471,7 @@ std::vector base64_decode(const std::string &encoded_string) { char_array_4[j] = 0; for (j = 0; j < 4; j++) - char_array_4[j] = BASE64_CHARS.find(char_array_4[j]); + char_array_4[j] = base64_find_char(char_array_4[j]); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index d92cf07702..58f162ff9d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -344,20 +344,149 @@ template std::string format_hex(const std::array &dat return format_hex(data.data(), data.size()); } -/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. -std::string format_hex_pretty(const uint8_t *data, size_t length); -/// Format the word array \p data of length \p len in pretty-printed, human-readable hex. -std::string format_hex_pretty(const uint16_t *data, size_t length); -/// Format the vector \p data in pretty-printed, human-readable hex. -std::string format_hex_pretty(const std::vector &data); -/// Format the vector \p data in pretty-printed, human-readable hex. -std::string format_hex_pretty(const std::vector &data); -/// Format the string \p data in pretty-printed, human-readable hex. -std::string format_hex_pretty(const std::string &data); -/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. -template::value, int> = 0> std::string format_hex_pretty(T val) { +/** Format a byte array in pretty-printed, human-readable hex format. + * + * Converts binary data to a hexadecimal string representation with customizable formatting. + * Each byte is displayed as a two-digit uppercase hex value, separated by the specified separator. + * Optionally includes the total byte count in parentheses at the end. + * + * @param data Pointer to the byte array to format. + * @param length Number of bytes in the array. + * @param separator Character to use between hex bytes (default: '.'). + * @param show_length Whether to append the byte count in parentheses (default: true). + * @return Formatted hex string, e.g., "A1.B2.C3.D4.E5 (5)" or "A1:B2:C3" depending on parameters. + * + * @note Returns empty string if data is nullptr or length is 0. + * @note The length will only be appended if show_length is true AND the length is greater than 4. + * + * Example: + * @code + * uint8_t data[] = {0xA1, 0xB2, 0xC3}; + * format_hex_pretty(data, 3); // Returns "A1.B2.C3" (no length shown for <= 4 parts) + * uint8_t data2[] = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5}; + * format_hex_pretty(data2, 5); // Returns "A1.B2.C3.D4.E5 (5)" + * format_hex_pretty(data2, 5, ':'); // Returns "A1:B2:C3:D4:E5 (5)" + * format_hex_pretty(data2, 5, '.', false); // Returns "A1.B2.C3.D4.E5" + * @endcode + */ +std::string format_hex_pretty(const uint8_t *data, size_t length, char separator = '.', bool show_length = true); + +/** Format a 16-bit word array in pretty-printed, human-readable hex format. + * + * Similar to the byte array version, but formats 16-bit words as 4-digit hex values. + * + * @param data Pointer to the 16-bit word array to format. + * @param length Number of 16-bit words in the array. + * @param separator Character to use between hex words (default: '.'). + * @param show_length Whether to append the word count in parentheses (default: true). + * @return Formatted hex string with 4-digit hex values per word. + * + * @note The length will only be appended if show_length is true AND the length is greater than 4. + * + * Example: + * @code + * uint16_t data[] = {0xA1B2, 0xC3D4}; + * format_hex_pretty(data, 2); // Returns "A1B2.C3D4" (no length shown for <= 4 parts) + * uint16_t data2[] = {0xA1B2, 0xC3D4, 0xE5F6}; + * format_hex_pretty(data2, 3); // Returns "A1B2.C3D4.E5F6 (3)" + * @endcode + */ +std::string format_hex_pretty(const uint16_t *data, size_t length, char separator = '.', bool show_length = true); + +/** Format a byte vector in pretty-printed, human-readable hex format. + * + * Convenience overload for std::vector. Formats each byte as a two-digit + * uppercase hex value with customizable separator. + * + * @param data Vector of bytes to format. + * @param separator Character to use between hex bytes (default: '.'). + * @param show_length Whether to append the byte count in parentheses (default: true). + * @return Formatted hex string representation of the vector contents. + * + * @note The length will only be appended if show_length is true AND the vector size is greater than 4. + * + * Example: + * @code + * std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; + * format_hex_pretty(data); // Returns "DE.AD.BE.EF" (no length shown for <= 4 parts) + * std::vector data2 = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA}; + * format_hex_pretty(data2); // Returns "DE.AD.BE.EF.CA (5)" + * format_hex_pretty(data2, '-'); // Returns "DE-AD-BE-EF-CA (5)" + * @endcode + */ +std::string format_hex_pretty(const std::vector &data, char separator = '.', bool show_length = true); + +/** Format a 16-bit word vector in pretty-printed, human-readable hex format. + * + * Convenience overload for std::vector. Each 16-bit word is formatted + * as a 4-digit uppercase hex value in big-endian order. + * + * @param data Vector of 16-bit words to format. + * @param separator Character to use between hex words (default: '.'). + * @param show_length Whether to append the word count in parentheses (default: true). + * @return Formatted hex string representation of the vector contents. + * + * @note The length will only be appended if show_length is true AND the vector size is greater than 4. + * + * Example: + * @code + * std::vector data = {0x1234, 0x5678}; + * format_hex_pretty(data); // Returns "1234.5678" (no length shown for <= 4 parts) + * std::vector data2 = {0x1234, 0x5678, 0x9ABC}; + * format_hex_pretty(data2); // Returns "1234.5678.9ABC (3)" + * @endcode + */ +std::string format_hex_pretty(const std::vector &data, char separator = '.', bool show_length = true); + +/** Format a string's bytes in pretty-printed, human-readable hex format. + * + * Treats each character in the string as a byte and formats it in hex. + * Useful for debugging binary data stored in std::string containers. + * + * @param data String whose bytes should be formatted as hex. + * @param separator Character to use between hex bytes (default: '.'). + * @param show_length Whether to append the byte count in parentheses (default: true). + * @return Formatted hex string representation of the string's byte contents. + * + * @note The length will only be appended if show_length is true AND the string length is greater than 4. + * + * Example: + * @code + * std::string data = "ABC"; // ASCII: 0x41, 0x42, 0x43 + * format_hex_pretty(data); // Returns "41.42.43" (no length shown for <= 4 parts) + * std::string data2 = "ABCDE"; + * format_hex_pretty(data2); // Returns "41.42.43.44.45 (5)" + * @endcode + */ +std::string format_hex_pretty(const std::string &data, char separator = '.', bool show_length = true); + +/** Format an unsigned integer in pretty-printed, human-readable hex format. + * + * Converts the integer to big-endian byte order and formats each byte as hex. + * The most significant byte appears first in the output string. + * + * @tparam T Unsigned integer type (uint8_t, uint16_t, uint32_t, uint64_t, etc.). + * @param val The unsigned integer value to format. + * @param separator Character to use between hex bytes (default: '.'). + * @param show_length Whether to append the byte count in parentheses (default: true). + * @return Formatted hex string with most significant byte first. + * + * @note The length will only be appended if show_length is true AND sizeof(T) is greater than 4. + * + * Example: + * @code + * uint32_t value = 0x12345678; + * format_hex_pretty(value); // Returns "12.34.56.78" (no length shown for <= 4 parts) + * uint64_t value2 = 0x123456789ABCDEF0; + * format_hex_pretty(value2); // Returns "12.34.56.78.9A.BC.DE.F0 (8)" + * format_hex_pretty(value2, ':'); // Returns "12:34:56:78:9A:BC:DE:F0 (8)" + * format_hex_pretty(0x1234); // Returns "12.34" + * @endcode + */ +template::value, int> = 0> +std::string format_hex_pretty(T val, char separator = '.', bool show_length = true) { val = convert_big_endian(val); - return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); + return format_hex_pretty(reinterpret_cast(&val), sizeof(T), separator, show_length); } /// Format the byte array \p data of length \p len in binary. diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 515f6fd355..c6893b128f 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -62,16 +62,14 @@ static void validate_static_string(const char *name) { void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, uint32_t delay, std::function func) { // Get the name as const char* - const char *name_cstr = - is_static_string ? static_cast(name_ptr) : static_cast(name_ptr)->c_str(); + const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); - // Cancel existing timer if name is not empty - if (name_cstr != nullptr && name_cstr[0] != '\0') { - this->cancel_item_(component, name_cstr, type); - } - - if (delay == SCHEDULER_DONT_RUN) + if (delay == SCHEDULER_DONT_RUN) { + // Still need to cancel existing timer if name is not empty + LockGuard guard{this->lock_}; + this->cancel_item_locked_(component, name_cstr, type); return; + } // Create and populate the scheduler item auto item = make_unique(); @@ -87,6 +85,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type if (delay == 0 && type == SchedulerItem::TIMEOUT) { // Put in defer queue for guaranteed FIFO execution LockGuard guard{this->lock_}; + this->cancel_item_locked_(component, name_cstr, type); this->defer_queue_.push_back(std::move(item)); return; } @@ -122,7 +121,13 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type } #endif - this->push_(std::move(item)); + LockGuard guard{this->lock_}; + // If name is provided, do atomic cancel-and-add + // 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)); } void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function func) { @@ -134,10 +139,10 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u this->set_timer_common_(component, SchedulerItem::TIMEOUT, false, &name, timeout, std::move(func)); } bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) { - return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); + return this->cancel_item_(component, false, &name, SchedulerItem::TIMEOUT); } bool HOT Scheduler::cancel_timeout(Component *component, const char *name) { - return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); + return this->cancel_item_(component, true, name, SchedulerItem::TIMEOUT); } void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, std::function func) { @@ -149,10 +154,10 @@ void HOT Scheduler::set_interval(Component *component, const char *name, uint32_ this->set_timer_common_(component, SchedulerItem::INTERVAL, true, name, interval, std::move(func)); } bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { - return this->cancel_item_(component, name, SchedulerItem::INTERVAL); + return this->cancel_item_(component, false, &name, SchedulerItem::INTERVAL); } bool HOT Scheduler::cancel_interval(Component *component, const char *name) { - return this->cancel_item_(component, name, SchedulerItem::INTERVAL); + return this->cancel_item_(component, true, name, SchedulerItem::INTERVAL); } struct RetryArgs { @@ -211,6 +216,9 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) } optional HOT Scheduler::next_schedule_in() { + // IMPORTANT: This method should only be called from the main thread (loop task). + // It calls empty_() and accesses items_[0] without holding a lock, which is only + // safe when called from the main thread. Other threads must not call this method. if (this->empty_()) return {}; auto &item = this->items_[0]; @@ -230,6 +238,10 @@ void HOT Scheduler::call() { // - No deferred items exist in to_add_, so processing order doesn't affect correctness // ESP8266 and RP2040 don't use this queue - they fall back to the heap-based approach // (ESP8266: single-core, RP2040: empty mutex implementation). + // + // Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still + // processed here. They are removed from the queue normally via pop_front() but skipped + // during execution by should_skip_item_(). This is intentional - no memory leak occurs. while (!this->defer_queue_.empty()) { // The outer check is done without a lock for performance. If the queue // appears non-empty, we lock and process an item. We don't need to check @@ -261,10 +273,12 @@ void HOT Scheduler::call() { ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now, this->millis_major_, this->last_millis_); while (!this->empty_()) { - this->lock_.lock(); - auto item = std::move(this->items_[0]); - this->pop_raw_(); - this->lock_.unlock(); + std::unique_ptr item; + { + LockGuard guard{this->lock_}; + item = std::move(this->items_[0]); + this->pop_raw_(); + } const char *name = item->get_name(); ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64, @@ -278,33 +292,35 @@ void HOT Scheduler::call() { { LockGuard guard{this->lock_}; this->items_ = std::move(old_items); + // Rebuild heap after moving items back + std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); } } #endif // ESPHOME_DEBUG_SCHEDULER - auto to_remove_was = to_remove_; - auto items_was = this->items_.size(); // If we have too many items to remove - if (to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) { + if (this->to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) { + // We hold the lock for the entire cleanup operation because: + // 1. We're rebuilding the entire items_ list, so we need exclusive access throughout + // 2. Other threads must see either the old state or the new state, not intermediate states + // 3. The operation is already expensive (O(n)), so lock overhead is negligible + // 4. No operations inside can block or take other locks, so no deadlock risk + LockGuard guard{this->lock_}; + std::vector> valid_items; - while (!this->empty_()) { - LockGuard guard{this->lock_}; - auto item = std::move(this->items_[0]); - this->pop_raw_(); - valid_items.push_back(std::move(item)); + + // Move all non-removed items to valid_items + for (auto &item : this->items_) { + if (!item->remove) { + valid_items.push_back(std::move(item)); + } } - { - LockGuard guard{this->lock_}; - this->items_ = std::move(valid_items); - } - - // The following should not happen unless I'm missing something - if (to_remove_ != 0) { - ESP_LOGW(TAG, "to_remove_ was %" PRIu32 " now: %" PRIu32 " items where %zu now %zu. Please report this", - to_remove_was, to_remove_, items_was, items_.size()); - to_remove_ = 0; - } + // Replace items_ with the filtered list + this->items_ = std::move(valid_items); + // Rebuild the heap structure since items are no longer in heap order + std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); + this->to_remove_ = 0; } while (!this->empty_()) { @@ -336,26 +352,25 @@ void HOT Scheduler::call() { } { - this->lock_.lock(); + LockGuard guard{this->lock_}; // new scope, item from before might have been moved in the vector auto item = std::move(this->items_[0]); - // Only pop after function call, this ensures we were reachable // during the function call and know if we were cancelled. this->pop_raw_(); - this->lock_.unlock(); - if (item->remove) { // We were removed/cancelled in the function call, stop - to_remove_--; + this->to_remove_--; continue; } if (item->type == SchedulerItem::INTERVAL) { item->next_execution_ = now + item->interval; - this->push_(std::move(item)); + // Add new item directly to to_add_ + // since we have the lock held + this->to_add_.push_back(std::move(item)); } } } @@ -375,36 +390,37 @@ void HOT Scheduler::process_to_add() { this->to_add_.clear(); } void HOT Scheduler::cleanup_() { + // Fast path: if nothing to remove, just return + // Reading to_remove_ without lock is safe because: + // 1. We only call this from the main thread during call() + // 2. If it's 0, there's definitely nothing to cleanup + // 3. If it becomes non-zero after we check, cleanup will happen on the next loop iteration + // 4. Not all platforms support atomics, so we accept this race in favor of performance + // 5. The worst case is a one-loop-iteration delay in cleanup, which is harmless + if (this->to_remove_ == 0) + return; + + // We must hold the lock for the entire cleanup operation because: + // 1. We're modifying items_ (via pop_raw_) which requires exclusive access + // 2. We're decrementing to_remove_ which is also modified by other threads + // (though all modifications are already under lock) + // 3. Other threads read items_ when searching for items to cancel in cancel_item_locked_() + // 4. We need a consistent view of items_ and to_remove_ throughout the operation + // Without the lock, we could access items_ while another thread is reading it, + // leading to race conditions + LockGuard guard{this->lock_}; while (!this->items_.empty()) { auto &item = this->items_[0]; if (!item->remove) return; - - to_remove_--; - - { - LockGuard guard{this->lock_}; - this->pop_raw_(); - } + this->to_remove_--; + this->pop_raw_(); } } void HOT Scheduler::pop_raw_() { std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); this->items_.pop_back(); } -void HOT Scheduler::push_(std::unique_ptr item) { - LockGuard guard{this->lock_}; - this->to_add_.push_back(std::move(item)); -} -// Helper function to check if item matches criteria for cancellation -bool HOT Scheduler::matches_item_(const std::unique_ptr &item, Component *component, - const char *name_cstr, SchedulerItem::Type type) { - if (item->component != component || item->type != type || item->remove) { - return false; - } - const char *item_name = item->get_name(); - return item_name != nullptr && strcmp(name_cstr, item_name) == 0; -} // Helper to execute a scheduler item void HOT Scheduler::execute_item_(SchedulerItem *item) { @@ -417,55 +433,57 @@ void HOT Scheduler::execute_item_(SchedulerItem *item) { } // Common implementation for cancel operations -bool HOT Scheduler::cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, - SchedulerItem::Type type) { +bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, const void *name_ptr, + SchedulerItem::Type type) { // Get the name as const char* - const char *name_cstr = - is_static_string ? static_cast(name_ptr) : static_cast(name_ptr)->c_str(); - - // Handle null or empty names - if (name_cstr == nullptr) - return false; + const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); // obtain lock because this function iterates and can be called from non-loop task context LockGuard guard{this->lock_}; - bool ret = false; + return this->cancel_item_locked_(component, name_cstr, type); +} + +// 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 #if !defined(USE_ESP8266) && !defined(USE_RP2040) - // Only check defer_queue_ on platforms that have it - for (auto &item : this->defer_queue_) { - if (this->matches_item_(item, component, name_cstr, type)) { - item->remove = true; - ret = true; + // Only check defer queue for timeouts (intervals never go there) + if (type == SchedulerItem::TIMEOUT) { + for (auto &item : this->defer_queue_) { + if (this->matches_item_(item, component, name_cstr, type)) { + item->remove = true; + total_cancelled++; + } } } #endif + // Cancel items in the main heap for (auto &item : this->items_) { if (this->matches_item_(item, component, name_cstr, type)) { item->remove = true; - ret = true; - this->to_remove_++; // Only track removals for heap items + total_cancelled++; + this->to_remove_++; // Track removals for heap items } } + // Cancel items in to_add_ for (auto &item : this->to_add_) { if (this->matches_item_(item, component, name_cstr, type)) { item->remove = true; - ret = true; + total_cancelled++; + // Don't track removals for to_add_ items } } - return ret; -} - -bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) { - return this->cancel_item_common_(component, false, &name, type); -} - -bool HOT Scheduler::cancel_item_(Component *component, const char *name, SchedulerItem::Type type) { - return this->cancel_item_common_(component, true, name, type); + return total_cancelled > 0; } uint64_t Scheduler::millis_() { diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index bf5e63cccf..39cee5a876 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "esphome/core/component.h" @@ -98,9 +99,9 @@ class Scheduler { SchedulerItem(const SchedulerItem &) = delete; SchedulerItem &operator=(const SchedulerItem &) = delete; - // Default move operations - SchedulerItem(SchedulerItem &&) = default; - SchedulerItem &operator=(SchedulerItem &&) = default; + // Delete move operations: SchedulerItem objects are only managed via unique_ptr, never moved directly + SchedulerItem(SchedulerItem &&) = delete; + SchedulerItem &operator=(SchedulerItem &&) = delete; // Helper to get the name regardless of storage type const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } @@ -139,17 +140,39 @@ class Scheduler { uint64_t millis_(); void cleanup_(); void pop_raw_(); - void push_(std::unique_ptr item); - // Common implementation for cancel operations - bool cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type); private: - bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type); - bool cancel_item_(Component *component, const char *name, SchedulerItem::Type type); + // Helper to cancel items by name - must be called with lock held + bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type); - // Helper functions for cancel operations - bool matches_item_(const std::unique_ptr &item, Component *component, const char *name_cstr, - SchedulerItem::Type type); + // Helper to extract name as const char* from either static string or std::string + inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) { + return is_static_string ? static_cast(name_ptr) : static_cast(name_ptr)->c_str(); + } + + // Common implementation for cancel operations + bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type); + + // Helper function to check if item matches criteria for cancellation + inline bool HOT matches_item_(const std::unique_ptr &item, Component *component, const char *name_cstr, + SchedulerItem::Type type) { + if (item->component != component || item->type != type || item->remove) { + return false; + } + const char *item_name = item->get_name(); + if (item_name == nullptr) { + return false; + } + // Fast path: if pointers are equal + // This is effective because the core ESPHome codebase uses static strings (const char*) + // for component names. The std::string overloads exist only for compatibility with + // external components, but are rarely used in practice. + if (item_name == name_cstr) { + return true; + } + // Slow path: compare string contents + return strcmp(name_cstr, item_name) == 0; + } // Helper to execute a scheduler item void execute_item_(SchedulerItem *item); @@ -159,6 +182,12 @@ class Scheduler { return item->remove || (item->component != nullptr && item->component->is_failed()); } + // Check if the scheduler has no items. + // IMPORTANT: This method should only be called from the main thread (loop task). + // It performs cleanup of removed items and checks if the queue is empty. + // The items_.empty() check at the end is done without a lock for performance, + // which is safe because this is only called from the main thread while other + // threads only add items (never remove them). bool empty_() { this->cleanup_(); return this->items_.empty(); diff --git a/esphome/dashboard/entries.py b/esphome/dashboard/entries.py index e4825298f7..b138cfd272 100644 --- a/esphome/dashboard/entries.py +++ b/esphome/dashboard/entries.py @@ -9,6 +9,7 @@ import os from typing import TYPE_CHECKING, Any from esphome import const, util +from esphome.enum import StrEnum from esphome.storage_json import StorageJSON, ext_storage_path from .const import ( @@ -18,7 +19,6 @@ from .const import ( EVENT_ENTRY_STATE_CHANGED, EVENT_ENTRY_UPDATED, ) -from .enum import StrEnum from .util.subprocess import async_run_system_command if TYPE_CHECKING: diff --git a/esphome/dashboard/enum.py b/esphome/enum.py similarity index 100% rename from esphome/dashboard/enum.py rename to esphome/enum.py diff --git a/esphome/loader.py b/esphome/loader.py index 79a1d7f576..7b2472521a 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -112,8 +112,17 @@ class ComponentManifest: This will return all cpp source files that are located in the same folder as the loaded .py file (does not look through subdirectories) """ - ret = [] + ret: list[FileResource] = [] + # Get filter function for source files + filter_source_files_func = getattr(self.module, "FILTER_SOURCE_FILES", None) + + # Get list of files to exclude + excluded_files = ( + set(filter_source_files_func()) if filter_source_files_func else set() + ) + + # Process all resources for resource in ( r.name for r in importlib.resources.files(self.package).iterdir() @@ -124,6 +133,11 @@ class ComponentManifest: if not importlib.resources.files(self.package).joinpath(resource).is_file(): # Not a resource = this is a directory (yeah this is confusing) continue + + # Skip excluded files + if resource in excluded_files: + continue + ret.append(FileResource(self.package, resource)) return ret diff --git a/esphome/wizard.py b/esphome/wizard.py index 1826487aa4..6f7cbd1ff4 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -411,7 +411,7 @@ def wizard(path): safe_print("Options:") for board_id, board_data in boards_list: safe_print(f" - {board_id} - {board_data['name']}") - boards.append(board_id) + boards.append(board_id.lower()) while True: board = safe_input(color(AnsiFore.BOLD_WHITE, "(board): ")) diff --git a/platformio.ini b/platformio.ini index 0d67e23222..54c72eb28d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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] @@ -211,6 +212,7 @@ build_unflags = extends = common:arduino platform = libretiny@1.9.1 framework = arduino +lib_compat_mode = soft lib_deps = droscy/esp_wireguard@0.4.2 ; wireguard build_flags = diff --git a/requirements.txt b/requirements.txt index a6bcebaeea..ea264a8ac4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.9.0 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==34.1.0 +aioesphomeapi==34.2.1 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import diff --git a/requirements_test.txt b/requirements_test.txt index ef1fc4f2d6..67eae63a31 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==3.3.7 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.12.2 # also change in .pre-commit-config.yaml when updating +ruff==0.12.3 # also change in .pre-commit-config.yaml when updating pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating pre-commit diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 2266dda81c..3ae1b195e4 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -249,6 +249,42 @@ class TypeInfo(ABC): return 4 # 28 bits return 5 # 32 bits (maximum for uint32_t) + def _get_simple_size_calculation( + self, name: str, force: bool, base_method: str, value_expr: str = None + ) -> str: + """Helper for simple size calculations. + + Args: + name: Field name + force: Whether this is for a repeated field + base_method: Base method name (e.g., "add_int32_field") + value_expr: Optional value expression (defaults to name) + """ + field_id_size = self.calculate_field_id_size() + method = f"{base_method}_repeated" if force else base_method + value = value_expr if value_expr else name + return f"ProtoSize::{method}(total_size, {field_id_size}, {value});" + + def _get_fixed_size_calculation( + self, name: str, force: bool, num_bytes: int, zero_check: str + ) -> str: + """Helper for fixed-size field calculations. + + Args: + name: Field name + force: Whether this is for a repeated field + num_bytes: Number of bytes (4 or 8) + zero_check: Expression to check for zero value (e.g., "!= 0.0f") + """ + field_id_size = self.calculate_field_id_size() + # Fixed-size repeated fields are handled differently in RepeatedTypeInfo + # so we should never get force=True here + assert not force, ( + "Fixed-size repeated fields should be handled by RepeatedTypeInfo" + ) + method = f"add_fixed_field<{num_bytes}>" + return f"ProtoSize::{method}(total_size, {field_id_size}, {name} {zero_check});" + @abstractmethod def get_size_calculation(self, name: str, force: bool = False) -> str: """Calculate the size needed for encoding this field. @@ -258,6 +294,14 @@ class TypeInfo(ABC): force: Whether to force encoding the field even if it has a default value """ + def get_fixed_size_bytes(self) -> int | None: + """Get the number of bytes for fixed-size fields (float, double, fixed32, etc). + + Returns: + The number of bytes (4 or 8) for fixed-size fields, None for variable-size fields. + """ + return None + @abstractmethod def get_estimated_size(self) -> int: """Get estimated size in bytes for this field with typical values. @@ -290,14 +334,15 @@ class DoubleType(TypeInfo): wire_type = WireType.FIXED64 # Uses wire type 1 according to protobuf spec def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%g", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%g", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0.0, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 8, "!= 0.0") + + def get_fixed_size_bytes(self) -> int: + return 8 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes for double @@ -312,14 +357,15 @@ class FloatType(TypeInfo): wire_type = WireType.FIXED32 # Uses wire type 5 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%g", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%g", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0.0f, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 4, "!= 0.0f") + + def get_fixed_size_bytes(self) -> int: + return 4 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes for float @@ -334,14 +380,12 @@ class Int64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_int64_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_int64_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -356,14 +400,12 @@ class UInt64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%llu", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_uint64_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_uint64_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -378,14 +420,12 @@ class Int32Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%" PRId32, {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_int32_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_int32_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -400,14 +440,15 @@ class Fixed64Type(TypeInfo): wire_type = WireType.FIXED64 # Uses wire type 1 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%llu", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 8, "!= 0") + + def get_fixed_size_bytes(self) -> int: + return 8 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed @@ -422,14 +463,15 @@ class Fixed32Type(TypeInfo): wire_type = WireType.FIXED32 # Uses wire type 5 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%" PRIu32, {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRIu32, {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 4, "!= 0") + + def get_fixed_size_bytes(self) -> int: + return 4 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed @@ -448,9 +490,7 @@ class BoolType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_bool_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_bool_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 1 # field ID + 1 byte @@ -471,9 +511,7 @@ class StringType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_string_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string @@ -498,20 +536,33 @@ class MessageType(TypeInfo): @property def encode_func(self) -> str: - return f"encode_message<{self.cpp_type}>" + return "encode_message" @property def decode_length(self) -> str: - return f"value.as_message<{self.cpp_type}>()" + # Override to return None for message types because we can't use template-based + # decoding when the specific message type isn't known at compile time. + # Instead, we use the non-template decode_to_message() method which allows + # runtime polymorphism through virtual function calls. + return None + + @property + def decode_length_content(self) -> str: + # Custom decode that doesn't use templates + return dedent( + f"""\ + case {self.number}: {{ + value.decode_to_message(this->{self.field_name}); + return true; + }}""" + ) def dump(self, name: str) -> str: o = f"{name}.dump_to(out);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_message_object(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_message_object") def get_estimated_size(self) -> int: return ( @@ -538,9 +589,7 @@ class BytesType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_string_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes @@ -555,14 +604,12 @@ class UInt32Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%" PRIu32, {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRIu32, {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_uint32_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_uint32_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -576,23 +623,27 @@ class EnumType(TypeInfo): @property def decode_varint(self) -> str: - return f"value.as_enum<{self.cpp_type}>()" + return f"static_cast<{self.cpp_type}>(value.as_uint32())" default_value = "" wire_type = WireType.VARINT # Uses wire type 0 @property def encode_func(self) -> str: - return f"encode_enum<{self.cpp_type}>" + return "encode_uint32" + + @property + def encode_content(self) -> str: + return f"buffer.{self.encode_func}({self.number}, static_cast(this->{self.field_name}));" def dump(self, name: str) -> str: o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_enum_field(total_size, {field_id_size}, static_cast({name}), {force_str(force)});" - return o + return self._get_simple_size_calculation( + name, force, "add_enum_field", f"static_cast({name})" + ) def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 1 # field ID + 1 byte typical enum @@ -607,14 +658,15 @@ class SFixed32Type(TypeInfo): wire_type = WireType.FIXED32 # Uses wire type 5 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%" PRId32, {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 4, "!= 0") + + def get_fixed_size_bytes(self) -> int: + return 4 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed @@ -629,14 +681,15 @@ class SFixed64Type(TypeInfo): wire_type = WireType.FIXED64 # Uses wire type 1 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});" - return o + return self._get_fixed_size_calculation(name, force, 8, "!= 0") + + def get_fixed_size_bytes(self) -> int: + return 8 def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed @@ -651,14 +704,12 @@ class SInt32Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%" PRId32, {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_sint32_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_sint32_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -673,14 +724,12 @@ class SInt64Type(TypeInfo): wire_type = WireType.VARINT # Uses wire type 0 def dump(self, name: str) -> str: - o = f'sprintf(buffer, "%lld", {name});\n' + o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n' o += "out.append(buffer);" return o def get_size_calculation(self, name: str, force: bool = False) -> str: - field_id_size = self.calculate_field_id_size() - o = f"ProtoSize::add_sint64_field(total_size, {field_id_size}, {name}, {force_str(force)});" - return o + return self._get_simple_size_calculation(name, force, "add_sint64_field") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -727,6 +776,16 @@ class RepeatedTypeInfo(TypeInfo): @property def decode_length_content(self) -> str: content = self._ti.decode_length + if content is None and isinstance(self._ti, MessageType): + # Special handling for non-template message decoding + return dedent( + f"""\ + case {self.number}: {{ + this->{self.field_name}.emplace_back(); + value.decode_to_message(this->{self.field_name}.back()); + return true; + }}""" + ) if content is None: return None return dedent( @@ -771,7 +830,10 @@ class RepeatedTypeInfo(TypeInfo): @property def encode_content(self) -> str: o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + if isinstance(self._ti, EnumType): + o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" + else: + o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" o += "}" return o @@ -795,11 +857,23 @@ class RepeatedTypeInfo(TypeInfo): field_id_size = self._ti.calculate_field_id_size() o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});" return o + # For other repeated types, use the underlying type's size calculation with force=True o = f"if (!{name}.empty()) {{\n" - o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" - o += f" {self._ti.get_size_calculation('it', True)}\n" - o += " }\n" + + # Check if this is a fixed-size type by seeing if it has a fixed byte count + num_bytes = self._ti.get_fixed_size_bytes() + if num_bytes is not None: + # Fixed types have constant size per element, so we can multiply + field_id_size = self._ti.calculate_field_id_size() + # Pre-calculate the total bytes per element + bytes_per_element = field_id_size + num_bytes + o += f" total_size += {name}.size() * {bytes_per_element};\n" + else: + # Other types need the actual value + o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" + o += f" {self._ti.get_size_calculation('it', True)}\n" + o += " }\n" o += "}" return o @@ -985,15 +1059,31 @@ def build_message_type( # Get message ID if it's a service message message_id: int | None = get_opt(desc, pb.id) + # Get source direction to determine if we need decode/encode methods + source: int = get_opt(desc, pb.source, SOURCE_BOTH) + needs_decode = source in (SOURCE_BOTH, SOURCE_CLIENT) + needs_encode = source in (SOURCE_BOTH, SOURCE_SERVER) + # Add MESSAGE_TYPE method if this is a service message if message_id is not None: + # Validate that message_id fits in uint8_t + if message_id > 255: + raise ValueError( + f"Message ID {message_id} for {desc.name} exceeds uint8_t maximum (255)" + ) + # Add static constexpr for message type - public_content.append(f"static constexpr uint16_t MESSAGE_TYPE = {message_id};") + public_content.append(f"static constexpr uint8_t MESSAGE_TYPE = {message_id};") # Add estimated size constant estimated_size = calculate_message_estimated_size(desc) + # Validate that estimated_size fits in uint8_t + if estimated_size > 255: + raise ValueError( + f"Estimated size {estimated_size} for {desc.name} exceeds uint8_t maximum (255)" + ) public_content.append( - f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};" + f"static constexpr uint8_t ESTIMATED_SIZE = {estimated_size};" ) # Add message_name method inline in header @@ -1016,18 +1106,21 @@ def build_message_type( protected_content.extend(ti.protected_content) public_content.extend(ti.public_content) - # Always include encode/decode logic for all fields - encode.append(ti.encode_content) - size_calc.append(ti.get_size_calculation(f"this->{ti.field_name}")) + # Only collect encode logic if this message needs it + if needs_encode: + encode.append(ti.encode_content) + size_calc.append(ti.get_size_calculation(f"this->{ti.field_name}")) - if ti.decode_varint_content: - decode_varint.append(ti.decode_varint_content) - if ti.decode_length_content: - decode_length.append(ti.decode_length_content) - if ti.decode_32bit_content: - decode_32bit.append(ti.decode_32bit_content) - if ti.decode_64bit_content: - decode_64bit.append(ti.decode_64bit_content) + # Only collect decode methods if this message needs them + if needs_decode: + if ti.decode_varint_content: + decode_varint.append(ti.decode_varint_content) + if ti.decode_length_content: + decode_length.append(ti.decode_length_content) + if ti.decode_32bit_content: + decode_32bit.append(ti.decode_32bit_content) + if ti.decode_64bit_content: + decode_64bit.append(ti.decode_64bit_content) if ti.dump_content: dump.append(ti.dump_content) @@ -1073,8 +1166,8 @@ def build_message_type( prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;" protected_content.insert(0, prot) - # Only generate encode method if there are fields to encode - if encode: + # Only generate encode method if this message needs encoding and has fields + if needs_encode and encode: o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120: o += f" {encode[0]} " @@ -1085,10 +1178,10 @@ def build_message_type( cpp += o prot = "void encode(ProtoWriteBuffer buffer) const override;" public_content.append(prot) - # If no fields to encode, the default implementation in ProtoMessage will be used + # If no fields to encode or message doesn't need encoding, the default implementation in ProtoMessage will be used - # Add calculate_size method only if there are fields - if size_calc: + # Add calculate_size method only if this message needs encoding and has fields + if needs_encode and size_calc: o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{" # For a single field, just inline it for simplicity if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120: @@ -1101,7 +1194,7 @@ def build_message_type( cpp += o prot = "void calculate_size(uint32_t &total_size) const override;" public_content.append(prot) - # If no fields to calculate size for, the default implementation in ProtoMessage will be used + # If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used # dump_to method declaration in header prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" @@ -1366,7 +1459,6 @@ def main() -> None: #include "esphome/core/defines.h" #include "proto.h" -#include "api_pb2_size.h" namespace esphome { namespace api { @@ -1376,7 +1468,6 @@ namespace api { cpp = FILE_HEADER cpp += """\ #include "api_pb2.h" - #include "api_pb2_size.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -1701,7 +1792,6 @@ static const char *const TAG = "api.service"; exec_clang_format(root / "api_pb2_service.cpp") exec_clang_format(root / "api_pb2.h") exec_clang_format(root / "api_pb2.cpp") - exec_clang_format(root / "api_pb2_dump.h") exec_clang_format(root / "api_pb2_dump.cpp") except ImportError: pass diff --git a/script/ci-custom.py b/script/ci-custom.py index d0b518251f..1310a93230 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -270,7 +270,7 @@ def lint_newline(fname): return "File contains Windows newline. Please set your editor to Unix newline mode." -@lint_content_check(exclude=["*.svg"]) +@lint_content_check(exclude=["*.svg", ".clang-tidy.hash"]) def lint_end_newline(fname, content): if content and not content.endswith("\n"): return "File does not end with a newline, please add an empty line at the end of the file." diff --git a/script/clang-tidy b/script/clang-tidy index 5baaaf6b3a..b5905e0e4e 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -22,6 +22,7 @@ from helpers import ( git_ls_files, load_idedata, print_error_for_file, + print_file_list, root_path, temp_header_file, ) @@ -218,13 +219,14 @@ def main(): ) args = parser.parse_args() - idedata = load_idedata(args.environment) - options = clang_options(idedata) - files = [] for path in git_ls_files(["*.cpp"]): files.append(os.path.relpath(path, os.getcwd())) + # Print initial file count if it's large + if len(files) > 50: + print(f"Found {len(files)} total files to process") + if args.files: # Match against files specified on command-line file_name_re = re.compile("|".join(args.files)) @@ -240,10 +242,28 @@ def main(): if args.split_num: files = split_list(files, args.split_num)[args.split_at - 1] + print(f"Split {args.split_at}/{args.split_num}: checking {len(files)} files") + # Print file count before adding header file + print(f"\nTotal files to check: {len(files)}") + + # Early exit if no files to check + if not files: + print("No files to check - exiting early") + return 0 + + # Only build header file if we have actual files to check if args.all_headers and args.split_at in (None, 1): build_all_include() files.insert(0, temp_header_file) + print(f"Added all-include header file, new total: {len(files)}") + + # Print final file list before loading idedata + print_file_list(files, "Final files to process:") + + # Load idedata and options only if we have files to check + idedata = load_idedata(args.environment) + options = clang_options(idedata) tmpdir = None if args.fix: diff --git a/script/clang_tidy_hash.py b/script/clang_tidy_hash.py new file mode 100755 index 0000000000..86f4c4e158 --- /dev/null +++ b/script/clang_tidy_hash.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Calculate and manage hash for clang-tidy configuration.""" + +from __future__ import annotations + +import argparse +import hashlib +from pathlib import Path +import re +import sys + +# Add the script directory to path to import helpers +script_dir = Path(__file__).parent +sys.path.insert(0, str(script_dir)) + + +def read_file_lines(path: Path) -> list[str]: + """Read lines from a file.""" + with open(path) as f: + return f.readlines() + + +def parse_requirement_line(line: str) -> tuple[str, str] | None: + """Parse a requirement line and return (package, original_line) or None. + + Handles formats like: + - package==1.2.3 + - package==1.2.3 # comment + - package>=1.2.3,<2.0.0 + """ + original_line = line.strip() + + # Extract the part before any comment for parsing + parse_line = line + if "#" in parse_line: + parse_line = parse_line[: parse_line.index("#")] + + parse_line = parse_line.strip() + if not parse_line: + return None + + # Use regex to extract package name + # This matches package names followed by version operators + match = re.match(r"^([a-zA-Z0-9_-]+)(==|>=|<=|>|<|!=|~=)(.+)$", parse_line) + if match: + return (match.group(1), original_line) # Return package name and original line + + return None + + +def get_clang_tidy_version_from_requirements() -> str: + """Get clang-tidy version from requirements_dev.txt""" + requirements_path = Path(__file__).parent.parent / "requirements_dev.txt" + lines = read_file_lines(requirements_path) + + for line in lines: + parsed = parse_requirement_line(line) + if parsed and parsed[0] == "clang-tidy": + # Return the original line (preserves comments) + return parsed[1] + + return "clang-tidy version not found" + + +def extract_platformio_flags() -> str: + """Extract clang-tidy related flags from platformio.ini""" + flags: list[str] = [] + in_clangtidy_section = False + + platformio_path = Path(__file__).parent.parent / "platformio.ini" + lines = read_file_lines(platformio_path) + for line in lines: + line = line.strip() + if line.startswith("[flags:clangtidy]"): + in_clangtidy_section = True + continue + elif line.startswith("[") and in_clangtidy_section: + break + elif in_clangtidy_section and line and not line.startswith("#"): + flags.append(line) + + return "\n".join(sorted(flags)) + + +def read_file_bytes(path: Path) -> bytes: + """Read bytes from a file.""" + with open(path, "rb") as f: + return f.read() + + +def calculate_clang_tidy_hash() -> str: + """Calculate hash of clang-tidy configuration and version""" + hasher = hashlib.sha256() + + # Hash .clang-tidy file + clang_tidy_path = Path(__file__).parent.parent / ".clang-tidy" + content = read_file_bytes(clang_tidy_path) + hasher.update(content) + + # Hash clang-tidy version from requirements_dev.txt + version = get_clang_tidy_version_from_requirements() + hasher.update(version.encode()) + + # Hash relevant platformio.ini sections + pio_flags = extract_platformio_flags() + hasher.update(pio_flags.encode()) + + return hasher.hexdigest() + + +def read_stored_hash() -> str | None: + """Read the stored hash from file""" + hash_file = Path(__file__).parent.parent / ".clang-tidy.hash" + if hash_file.exists(): + lines = read_file_lines(hash_file) + return lines[0].strip() if lines else None + return None + + +def write_file_content(path: Path, content: str) -> None: + """Write content to a file.""" + with open(path, "w") as f: + f.write(content) + + +def write_hash(hash_value: str) -> None: + """Write hash to file""" + hash_file = Path(__file__).parent.parent / ".clang-tidy.hash" + write_file_content(hash_file, hash_value) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Manage clang-tidy configuration hash") + parser.add_argument( + "--check", + action="store_true", + help="Check if full scan needed (exit 0 if needed)", + ) + parser.add_argument("--update", action="store_true", help="Update the hash file") + parser.add_argument( + "--update-if-changed", + action="store_true", + help="Update hash only if configuration changed (for pre-commit)", + ) + parser.add_argument( + "--verify", action="store_true", help="Verify hash matches (for CI)" + ) + + args = parser.parse_args() + + current_hash = calculate_clang_tidy_hash() + stored_hash = read_stored_hash() + + if args.check: + # Exit 0 if full scan needed (hash changed or no hash file) + sys.exit(0 if current_hash != stored_hash else 1) + + elif args.update: + write_hash(current_hash) + print(f"Hash updated: {current_hash}") + + elif args.update_if_changed: + if current_hash != stored_hash: + write_hash(current_hash) + print(f"Clang-tidy hash updated: {current_hash}") + # Exit 0 so pre-commit can stage the file + sys.exit(0) + else: + print("Clang-tidy hash unchanged") + sys.exit(0) + + elif args.verify: + if current_hash != stored_hash: + print("ERROR: Clang-tidy configuration has changed but hash not updated!") + print(f"Expected: {current_hash}") + print(f"Found: {stored_hash}") + print("\nPlease run: script/clang_tidy_hash.py --update") + sys.exit(1) + print("Hash verification passed") + + else: + print(f"Current hash: {current_hash}") + print(f"Stored hash: {stored_hash}") + print(f"Match: {current_hash == stored_hash}") + + +if __name__ == "__main__": + main() diff --git a/script/determine-jobs.py b/script/determine-jobs.py new file mode 100755 index 0000000000..fc5c397c65 --- /dev/null +++ b/script/determine-jobs.py @@ -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() diff --git a/script/helpers.py b/script/helpers.py index 1a0349e434..ff63bbc5b6 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,8 +1,14 @@ +from __future__ import annotations + +from functools import cache import json +import os import os.path from pathlib import Path import re import subprocess +import time +from typing import Any import colorama @@ -11,14 +17,42 @@ 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") -def styled(color, msg, reset=True): +# 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 suffix = colorama.Style.RESET_ALL if reset else "" return prefix + msg + suffix -def print_error_for_file(file, body): +def print_error_for_file(file: str, body: str | None) -> None: print( styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file) @@ -29,17 +63,22 @@ def print_error_for_file(file, body): print() -def build_all_include(): +def build_all_include() -> None: # Build a cpp file that includes all header files in this repo. # Otherwise header-only integrations would not be tested by clang-tidy - headers = [] - for path in walk_files(basepath): - filetypes = (".h",) - ext = os.path.splitext(path)[1] - if ext in filetypes: - path = os.path.relpath(path, root_path) - include_p = path.replace(os.path.sep, "/") - headers.append(f'#include "{include_p}"') + + # Use git ls-files to find all .h files in the esphome directory + # This is much faster than walking the filesystem + cmd = ["git", "ls-files", "esphome/**/*.h"] + proc = subprocess.run(cmd, capture_output=True, text=True, check=True) + + # Process git output - git already returns paths relative to repo root + headers = [ + f'#include "{include_p}"' + for line in proc.stdout.strip().split("\n") + if (include_p := line.replace(os.path.sep, "/")) + ] + headers.sort() headers.append("") content = "\n".join(headers) @@ -48,29 +87,87 @@ def build_all_include(): p.write_text(content, encoding="utf-8") -def walk_files(path): - for root, _, files in os.walk(path): - for name in files: - yield os.path.join(root, name) - - -def get_output(*args): +def get_output(*args: str) -> str: with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: output, _ = proc.communicate() return output.decode("utf-8") -def get_err(*args): +def get_err(*args: str) -> str: with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: _, err = proc.communicate() return err.decode("utf-8") -def splitlines_no_ends(string): +def splitlines_no_ends(string: str) -> list[str]: return [s.strip() for s in string.splitlines()] -def changed_files(branch="dev"): +def _get_pr_number_from_github_env() -> str | None: + """Extract PR number from GitHub environment variables. + + Returns: + PR number as string, or None if not found + """ + # First try parsing GITHUB_REF (fastest) + github_ref = os.environ.get("GITHUB_REF", "") + if "/pull/" in github_ref: + return github_ref.split("/pull/")[1].split("/")[0] + + # Fallback to GitHub event file + github_event_path = os.environ.get("GITHUB_EVENT_PATH") + if github_event_path and os.path.exists(github_event_path): + with open(github_event_path) as f: + event_data = json.load(f) + pr_data = event_data.get("pull_request", {}) + if pr_number := pr_data.get("number"): + return str(pr_number) + + return None + + +@cache +def _get_changed_files_github_actions() -> list[str] | None: + """Get changed files in GitHub Actions environment. + + Returns: + List of changed files, or None if should fall back to git method + """ + event_name = os.environ.get("GITHUB_EVENT_NAME") + + # For pull requests + if event_name == "pull_request": + pr_number = _get_pr_number_from_github_env() + if pr_number: + # Use GitHub CLI to get changed files directly + cmd = ["gh", "pr", "diff", pr_number, "--name-only"] + return _get_changed_files_from_command(cmd) + + # For pushes (including squash-and-merge) + elif event_name == "push": + # For push events, we want to check what changed in this commit + try: + # Get the changed files in the last commit + return _get_changed_files_from_command( + ["git", "diff", "HEAD~1..HEAD", "--name-only"] + ) + except: # noqa: E722 + # Fall back to the original method if this fails + pass + + return None + + +def changed_files(branch: str | None = None) -> list[str]: + # In GitHub Actions, we can use the API to get changed files more efficiently + if os.environ.get("GITHUB_ACTIONS") == "true": + github_files = _get_changed_files_github_actions() + if github_files is not None: + return github_files + + # Original implementation for local development + 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"))) for remote in check_remotes: @@ -83,25 +180,165 @@ def changed_files(branch="dev"): pass else: raise ValueError("Git not configured") - command = ["git", "diff", merge_base, "--name-only"] - changed = splitlines_no_ends(get_output(*command)) - changed = [os.path.relpath(f, os.getcwd()) for f in changed] - changed.sort() - return changed + return _get_changed_files_from_command(["git", "diff", merge_base, "--name-only"]) -def filter_changed(files): +def _get_changed_files_from_command(command: list[str]) -> list[str]: + """Run a git command to get changed files and return them as a list.""" + proc = subprocess.run(command, capture_output=True, text=True, check=False) + if proc.returncode != 0: + raise Exception(f"Command failed: {' '.join(command)}\nstderr: {proc.stderr}") + + changed_files = splitlines_no_ends(proc.stdout) + changed_files = [os.path.relpath(f, os.getcwd()) for f in changed_files if f] + changed_files.sort() + return changed_files + + +def get_changed_components() -> list[str] | None: + """Get list of changed components using list-components.py script. + + This function: + 1. First checks if any core C++/header files (esphome/core/*.{cpp,h,hpp,cc,cxx,c}) changed - if so, returns None + 2. Otherwise delegates to ./script/list-components.py --changed which: + - Analyzes all changed files + - Determines which components are affected (including dependencies) + - Returns a list of component names that need to be checked + + Returns: + - None: Core C++/header files changed, need full scan + - Empty list: No components changed (only non-component files changed) + - List of strings: Names of components that need checking (e.g., ["wifi", "mqtt"]) + """ + # Check if any core C++ or header files changed first changed = changed_files() - files = [f for f in files if f in changed] - print("Changed files:") - if not files: - print(" No changed files!") - for c in files: - print(f" {c}") + core_cpp_changed = any( + f.startswith("esphome/core/") + and f.endswith(CPP_FILE_EXTENSIONS[:-1]) # Exclude .tcc for core files + for f in changed + ) + if core_cpp_changed: + print("Core C++/header files changed - will run full clang-tidy scan") + return None + + # Use list-components.py to get changed components + script_path = os.path.join(root_path, "script", "list-components.py") + cmd = [script_path, "--changed"] + + try: + result = subprocess.run( + cmd, capture_output=True, text=True, check=True, close_fds=False + ) + 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") + return None + + +def _filter_changed_ci(files: list[str]) -> list[str]: + """Filter files based on changed components in CI environment. + + This function implements intelligent filtering to reduce CI runtime by only + checking files that could be affected by the changes. It handles three scenarios: + + 1. Core C++/header files changed (returns None from get_changed_components): + - Triggered when any C++/header file in esphome/core/ is modified + - Action: Check ALL files (full scan) + - Reason: Core C++/header files are used throughout the codebase + + 2. No components changed (returns empty list from get_changed_components): + - Triggered when only non-component files changed (e.g., scripts, configs) + - Action: Check only the specific non-component files that changed + - Example: If only script/clang-tidy changed, only check that file + + 3. Specific components changed (returns list of component names): + - Component detection done by: ./script/list-components.py --changed + - That script analyzes which components are affected by the changed files + INCLUDING their dependencies + - Action: Check ALL files in each component that list-components.py identifies + - Example: If wifi.cpp changed, list-components.py might return ["wifi", "network"] + if network depends on wifi. We then check ALL files in both + esphome/components/wifi/ and esphome/components/network/ + - Reason: Component files often have interdependencies (headers, base classes) + + Args: + files: List of all files that clang-tidy would normally check + + Returns: + Filtered list of files to check + """ + components = get_changed_components() + if components is None: + # Scenario 1: Core files changed or couldn't determine components + # Action: Return all files for full scan + return files + + if not components: + # Scenario 2: No components changed - only non-component files changed + # 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_PATH) + ] + if not files: + print("No files changed") + return files + + # Scenario 3: Specific components changed + # Action: Check ALL files in each changed component + # Convert component list to set for O(1) lookups + component_set = set(components) + print(f"Changed components: {', '.join(sorted(components))}") + + # The 'files' parameter contains ALL files in the codebase that clang-tidy would check. + # We filter this down to only files in the changed components. + # We check ALL files in each changed component (not just the changed files) + # because changes in one file can affect other files in the same component. + filtered_files = [] + for f in files: + 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: + filtered_files.append(f) + + return filtered_files + + +def _filter_changed_local(files: list[str]) -> list[str]: + """Filter files based on git changes for local development. + + Args: + files: List of all files to filter + + Returns: + Filtered list of files to check + """ + # For local development, just check changed files directly + changed = changed_files() + return [f for f in files if f in changed] + + +def filter_changed(files: list[str]) -> list[str]: + """Filter files to only those that changed or are in changed components. + + Args: + files: List of files to filter + """ + # When running from CI, use component-based filtering + if os.environ.get("GITHUB_ACTIONS") == "true": + files = _filter_changed_ci(files) + else: + files = _filter_changed_local(files) + + print_file_list(files, "Files to check after filtering:") return files -def filter_grep(files, value): +def filter_grep(files: list[str], value: str) -> list[str]: matched = [] for file in files: with open(file, encoding="utf-8") as handle: @@ -111,7 +348,7 @@ def filter_grep(files, value): return matched -def git_ls_files(patterns=None): +def git_ls_files(patterns: list[str] | None = None) -> dict[str, int]: command = ["git", "ls-files", "-s"] if patterns is not None: command.extend(patterns) @@ -121,7 +358,10 @@ def git_ls_files(patterns=None): 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}'...") + platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" changed = False @@ -142,7 +382,10 @@ def load_idedata(environment): changed = True if not changed: - return json.loads(temp_idedata.read_text()) + data = json.loads(temp_idedata.read_text()) + elapsed = time.time() - start_time + print(f"IDE data loaded from cache in {elapsed:.2f} seconds") + return data # ensure temp directory exists before running pio, as it writes sdkconfig to it Path(temp_folder).mkdir(exist_ok=True) @@ -158,6 +401,9 @@ def load_idedata(environment): match = re.search(r'{\s*".*}', stdout.decode("utf-8")) data = json.loads(match.group()) temp_idedata.write_text(json.dumps(data, indent=2) + "\n") + + elapsed = time.time() - start_time + print(f"IDE data generated and cached in {elapsed:.2f} seconds") return data @@ -196,6 +442,29 @@ def get_binary(name: str, version: str) -> str: raise +def print_file_list( + files: list[str], title: str = "Files:", max_files: int = 20 +) -> None: + """Print a list of files with optional truncation for large lists. + + Args: + files: List of file paths to print + title: Title to print before the list + max_files: Maximum number of files to show before truncating (default: 20) + """ + print(title) + if not files: + print(" No files to check!") + elif len(files) <= max_files: + for f in sorted(files): + print(f" {f}") + else: + sorted_files = sorted(files) + for f in sorted_files[:10]: + print(f" {f}") + print(f" ... and {len(files) - 10} more files") + + def get_usable_cpu_count() -> int: """Return the number of CPUs that can be used for processes. @@ -205,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 diff --git a/script/list-components.py b/script/list-components.py index 0afcaa0f9d..66212f44e7 100755 --- a/script/list-components.py +++ b/script/list-components.py @@ -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) diff --git a/tests/component_tests/conftest.py b/tests/component_tests/conftest.py index 7aa7dfe698..b1e0eaa200 100644 --- a/tests/component_tests/conftest.py +++ b/tests/component_tests/conftest.py @@ -1,29 +1,71 @@ """Fixtures for component tests.""" +from __future__ import annotations + +from collections.abc import Callable, Generator from pathlib import Path import sys +import pytest + # Add package root to python path here = Path(__file__).parent package_root = here.parent.parent sys.path.insert(0, package_root.as_posix()) -import pytest # noqa: E402 - from esphome.__main__ import generate_cpp_contents # noqa: E402 from esphome.config import read_config # noqa: E402 from esphome.core import CORE # noqa: E402 +@pytest.fixture(autouse=True) +def config_path(request: pytest.FixtureRequest) -> Generator[None]: + """Set CORE.config_path to the component's config directory and reset it after the test.""" + original_path = CORE.config_path + config_dir = Path(request.fspath).parent / "config" + + # Check if config directory exists, if not use parent directory + if config_dir.exists(): + # Set config_path to a dummy yaml file in the config directory + # This ensures CORE.config_dir points to the config directory + CORE.config_path = str(config_dir / "dummy.yaml") + else: + CORE.config_path = str(Path(request.fspath).parent / "dummy.yaml") + + yield + CORE.config_path = original_path + + @pytest.fixture -def generate_main(): +def component_fixture_path(request: pytest.FixtureRequest) -> Callable[[str], Path]: + """Return a function to get absolute paths relative to the component's fixtures directory.""" + + def _get_path(file_name: str) -> Path: + """Get the absolute path of a file relative to the component's fixtures directory.""" + return (Path(request.fspath).parent / "fixtures" / file_name).absolute() + + return _get_path + + +@pytest.fixture +def component_config_path(request: pytest.FixtureRequest) -> Callable[[str], Path]: + """Return a function to get absolute paths relative to the component's config directory.""" + + def _get_path(file_name: str) -> Path: + """Get the absolute path of a file relative to the component's config directory.""" + return (Path(request.fspath).parent / "config" / file_name).absolute() + + return _get_path + + +@pytest.fixture +def generate_main() -> Generator[Callable[[str | Path], str]]: """Generates the C++ main.cpp file and returns it in string form.""" - def generator(path: str) -> str: - CORE.config_path = path + def generator(path: str | Path) -> str: + CORE.config_path = str(path) CORE.config = read_config({}) generate_cpp_contents(CORE.config) - print(CORE.cpp_main_section) return CORE.cpp_main_section yield generator diff --git a/tests/component_tests/image/config/bad.png b/tests/component_tests/image/config/bad.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/image/config/image.png b/tests/component_tests/image/config/image.png new file mode 100644 index 0000000000..bd2fd54783 Binary files /dev/null and b/tests/component_tests/image/config/image.png differ diff --git a/tests/component_tests/image/config/image_test.yaml b/tests/component_tests/image/config/image_test.yaml new file mode 100644 index 0000000000..3ff1260bd0 --- /dev/null +++ b/tests/component_tests/image/config/image_test.yaml @@ -0,0 +1,20 @@ +esphome: + name: test + +esp32: + board: esp32s3box + +image: + - file: image.png + byte_order: little_endian + id: cat_img + type: rgb565 + +spi: + mosi_pin: 6 + clk_pin: 7 + +display: + - platform: mipi_spi + id: lcd_display + model: s3box diff --git a/tests/component_tests/image/test_init.py b/tests/component_tests/image/test_init.py new file mode 100644 index 0000000000..d8a883d32f --- /dev/null +++ b/tests/component_tests/image/test_init.py @@ -0,0 +1,183 @@ +"""Tests for image configuration validation.""" + +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.image import CONFIG_SCHEMA + + +@pytest.mark.parametrize( + ("config", "error_match"), + [ + pytest.param( + "a string", + "Badly formed image configuration, expected a list or a dictionary", + id="invalid_string_config", + ), + pytest.param( + {"id": "image_id", "type": "rgb565"}, + r"required key not provided @ data\[0\]\['file'\]", + id="missing_file", + ), + pytest.param( + {"file": "image.png", "type": "rgb565"}, + r"required key not provided @ data\[0\]\['id'\]", + id="missing_id", + ), + pytest.param( + {"id": "mdi_id", "file": "mdi:weather-##", "type": "rgb565"}, + "Could not parse mdi icon name", + id="invalid_mdi_icon", + ), + pytest.param( + { + "id": "image_id", + "file": "image.png", + "type": "binary", + "transparency": "alpha_channel", + }, + "Image format 'BINARY' cannot have transparency", + id="binary_with_transparency", + ), + pytest.param( + { + "id": "image_id", + "file": "image.png", + "type": "rgb565", + "transparency": "chroma_key", + "invert_alpha": True, + }, + "No alpha channel to invert", + id="invert_alpha_without_alpha_channel", + ), + pytest.param( + { + "id": "image_id", + "file": "image.png", + "type": "binary", + "byte_order": "big_endian", + }, + "Image format 'BINARY' does not support byte order configuration", + id="binary_with_byte_order", + ), + pytest.param( + {"id": "image_id", "file": "bad.png", "type": "binary"}, + "File can't be opened as image", + id="invalid_image_file", + ), + pytest.param( + {"defaults": {}, "images": [{"id": "image_id", "file": "image.png"}]}, + "Type is required either in the image config or in the defaults", + id="missing_type_in_defaults", + ), + ], +) +def test_image_configuration_errors( + config: Any, + error_match: str, +) -> None: + """Test detection of invalid configuration.""" + with pytest.raises(cv.Invalid, match=error_match): + CONFIG_SCHEMA(config) + + +@pytest.mark.parametrize( + "config", + [ + pytest.param( + { + "id": "image_id", + "file": "image.png", + "type": "rgb565", + "transparency": "chroma_key", + "byte_order": "little_endian", + "dither": "FloydSteinberg", + "resize": "100x100", + "invert_alpha": False, + }, + id="single_image_all_options", + ), + pytest.param( + [ + { + "id": "image_id", + "file": "image.png", + "type": "binary", + } + ], + id="list_of_images", + ), + pytest.param( + { + "defaults": { + "type": "rgb565", + "transparency": "chroma_key", + "byte_order": "little_endian", + "dither": "FloydSteinberg", + "resize": "100x100", + "invert_alpha": False, + }, + "images": [ + { + "id": "image_id", + "file": "image.png", + } + ], + }, + id="images_with_defaults", + ), + pytest.param( + { + "rgb565": { + "alpha_channel": [ + { + "id": "image_id", + "file": "image.png", + "transparency": "alpha_channel", + "byte_order": "little_endian", + "dither": "FloydSteinberg", + "resize": "100x100", + "invert_alpha": False, + } + ] + }, + "binary": [ + { + "id": "image_id", + "file": "image.png", + "transparency": "opaque", + "dither": "FloydSteinberg", + "resize": "100x100", + "invert_alpha": False, + } + ], + }, + id="type_based_organization", + ), + ], +) +def test_image_configuration_success( + config: dict[str, Any] | list[dict[str, Any]], +) -> None: + """Test successful configuration validation.""" + CONFIG_SCHEMA(config) + + +def test_image_generation( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test image generation configuration.""" + + main_cpp = generate_main(component_config_path("image_test.yaml")) + assert "uint8_t_id[] PROGMEM = {0x24, 0x21, 0x24, 0x21" in main_cpp + assert ( + "cat_img = new image::Image(uint8_t_id, 32, 24, image::IMAGE_TYPE_RGB565, image::TRANSPARENCY_OPAQUE);" + in main_cpp + ) diff --git a/tests/components/gl_r01_i2c/common.yaml b/tests/components/gl_r01_i2c/common.yaml new file mode 100644 index 0000000000..fe0705bdc6 --- /dev/null +++ b/tests/components/gl_r01_i2c/common.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_gl_r01_i2c + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: gl_r01_i2c + id: tof + name: "ToF sensor" + i2c_id: i2c_gl_r01_i2c + address: 0x74 + update_interval: 15s diff --git a/tests/components/gl_r01_i2c/test.esp32-ard.yaml b/tests/components/gl_r01_i2c/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/gl_r01_i2c/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.esp32-c3-ard.yaml b/tests/components/gl_r01_i2c/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/gl_r01_i2c/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml b/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.esp32-idf.yaml b/tests/components/gl_r01_i2c/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/gl_r01_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.esp8266-ard.yaml b/tests/components/gl_r01_i2c/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/gl_r01_i2c/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/gl_r01_i2c/test.rp2040-ard.yaml b/tests/components/gl_r01_i2c/test.rp2040-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/gl_r01_i2c/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/image/test.esp32-ard.yaml b/tests/components/image/test.esp32-ard.yaml deleted file mode 100644 index 818e720221..0000000000 --- a/tests/components/image/test.esp32-ard.yaml +++ /dev/null @@ -1,17 +0,0 @@ -spi: - - id: spi_main_lcd - clk_pin: 16 - mosi_pin: 17 - miso_pin: 32 - -display: - - platform: ili9xxx - id: main_lcd - model: ili9342 - cs_pin: 14 - dc_pin: 13 - reset_pin: 21 - invert_colors: true - -<<: !include common.yaml - diff --git a/tests/components/image/test.esp32-c3-ard.yaml b/tests/components/image/test.esp32-c3-ard.yaml deleted file mode 100644 index 4dae9cd5ec..0000000000 --- a/tests/components/image/test.esp32-c3-ard.yaml +++ /dev/null @@ -1,16 +0,0 @@ -spi: - - id: spi_main_lcd - clk_pin: 6 - mosi_pin: 7 - miso_pin: 5 - -display: - - platform: ili9xxx - id: main_lcd - model: ili9342 - cs_pin: 3 - dc_pin: 11 - reset_pin: 10 - invert_colors: true - -<<: !include common.yaml diff --git a/tests/components/image/test.esp32-c3-idf.yaml b/tests/components/image/test.esp32-c3-idf.yaml deleted file mode 100644 index 4dae9cd5ec..0000000000 --- a/tests/components/image/test.esp32-c3-idf.yaml +++ /dev/null @@ -1,16 +0,0 @@ -spi: - - id: spi_main_lcd - clk_pin: 6 - mosi_pin: 7 - miso_pin: 5 - -display: - - platform: ili9xxx - id: main_lcd - model: ili9342 - cs_pin: 3 - dc_pin: 11 - reset_pin: 10 - invert_colors: true - -<<: !include common.yaml diff --git a/tests/components/image/test.esp8266-ard.yaml b/tests/components/image/test.esp8266-ard.yaml index f963022ff4..626076d44e 100644 --- a/tests/components/image/test.esp8266-ard.yaml +++ b/tests/components/image/test.esp8266-ard.yaml @@ -13,4 +13,13 @@ display: reset_pin: 16 invert_colors: true -<<: !include common.yaml +image: + defaults: + type: rgb565 + transparency: opaque + byte_order: little_endian + resize: 50x50 + dither: FloydSteinberg + images: + - id: test_image + file: ../../pnglogo.png diff --git a/tests/components/lps22/common.yaml b/tests/components/lps22/common.yaml new file mode 100644 index 0000000000..e6de4752ba --- /dev/null +++ b/tests/components/lps22/common.yaml @@ -0,0 +1,8 @@ +sensor: + - platform: lps22 + address: 0x5d + update_interval: 10s + temperature: + name: "LPS22 Temperature" + pressure: + name: "LPS22 Pressure" diff --git a/tests/components/lps22/test.esp32-ard.yaml b/tests/components/lps22/test.esp32-ard.yaml new file mode 100644 index 0000000000..0da6a9577e --- /dev/null +++ b/tests/components/lps22/test.esp32-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/lps22/test.esp32-c3-ard.yaml b/tests/components/lps22/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..6091393d31 --- /dev/null +++ b/tests/components/lps22/test.esp32-c3-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/lps22/test.esp32-c3-idf.yaml b/tests/components/lps22/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..6091393d31 --- /dev/null +++ b/tests/components/lps22/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/lps22/test.esp32-idf.yaml b/tests/components/lps22/test.esp32-idf.yaml new file mode 100644 index 0000000000..0da6a9577e --- /dev/null +++ b/tests/components/lps22/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/lps22/test.esp8266-ard.yaml b/tests/components/lps22/test.esp8266-ard.yaml new file mode 100644 index 0000000000..6091393d31 --- /dev/null +++ b/tests/components/lps22/test.esp8266-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/lps22/test.rp2040-ard.yaml b/tests/components/lps22/test.rp2040-ard.yaml new file mode 100644 index 0000000000..6091393d31 --- /dev/null +++ b/tests/components/lps22/test.rp2040-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_lps22 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/integration/README.md b/tests/integration/README.md index 26bd5a00ee..8fce81bb80 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -78,3 +78,268 @@ pytest -s tests/integration/test_host_mode_basic.py - Each test gets its own temporary directory and unique port - Port allocation minimizes race conditions by holding the socket until just before ESPHome starts - Output from ESPHome processes is displayed for debugging + +## Integration Test Writing Guide + +### Test Patterns and Best Practices + +#### 1. Test File Naming Convention +- Use descriptive names: `test_{category}_{feature}.py` +- Common categories: `host_mode`, `api`, `scheduler`, `light`, `areas_and_devices` +- Examples: + - `test_host_mode_basic.py` - Basic host mode functionality + - `test_api_message_batching.py` - API message batching + - `test_scheduler_stress.py` - Scheduler stress testing + +#### 2. Essential Imports +```python +from __future__ import annotations + +import asyncio +from typing import Any + +import pytest +from aioesphomeapi import EntityState, SensorState + +from .types import APIClientConnectedFactory, RunCompiledFunction +``` + +#### 3. Common Test Patterns + +##### Basic Entity Test +```python +@pytest.mark.asyncio +async def test_my_sensor( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test sensor functionality.""" + async with run_compiled(yaml_config), api_client_connected() as client: + # Get entity list + entities, services = await client.list_entities_services() + + # Find specific entity + sensor = next((e for e in entities if e.object_id == "my_sensor"), None) + assert sensor is not None +``` + +##### State Subscription Pattern +```python +# Track state changes with futures +loop = asyncio.get_running_loop() +states: dict[int, EntityState] = {} +state_future: asyncio.Future[EntityState] = loop.create_future() + +def on_state(state: EntityState) -> None: + states[state.key] = state + # Check for specific condition using isinstance + if isinstance(state, SensorState) and state.state == expected_value: + if not state_future.done(): + state_future.set_result(state) + +client.subscribe_states(on_state) + +# Wait for state with timeout +try: + result = await asyncio.wait_for(state_future, timeout=5.0) +except asyncio.TimeoutError: + pytest.fail(f"Expected state not received. Got: {list(states.values())}") +``` + +##### Service Execution Pattern +```python +# Find and execute service +entities, services = await client.list_entities_services() +my_service = next((s for s in services if s.name == "my_service"), None) +assert my_service is not None + +# Execute with parameters +client.execute_service(my_service, {"param1": "value1", "param2": 42}) +``` + +##### Multiple Entity Tracking +```python +# For tests with many entities +loop = asyncio.get_running_loop() +entity_count = 50 +received_states: set[int] = set() +all_states_future: asyncio.Future[bool] = loop.create_future() + +def on_state(state: EntityState) -> None: + received_states.add(state.key) + if len(received_states) >= entity_count and not all_states_future.done(): + all_states_future.set_result(True) + +client.subscribe_states(on_state) +await asyncio.wait_for(all_states_future, timeout=10.0) +``` + +#### 4. YAML Fixture Guidelines + +##### Naming Convention +- Match test function name: `test_my_feature` → `fixtures/my_feature.yaml` +- Note: Remove `test_` prefix for fixture filename + +##### Basic Structure +```yaml +esphome: + name: test-name # Use kebab-case + # Optional: areas, devices, platformio_options + +host: # Always use host platform for integration tests +api: # Port injected automatically +logger: + level: DEBUG # Optional: Set log level + +# Component configurations +sensor: + - platform: template + name: "My Sensor" + id: my_sensor + lambda: return 42.0; + update_interval: 0.1s # Fast updates for testing +``` + +##### Advanced Features +```yaml +# External components for custom test code +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH # Replaced by test framework + components: [my_test_component] + +# Areas and devices +esphome: + name: test-device + areas: + - id: living_room + name: "Living Room" + - id: kitchen + name: "Kitchen" + parent_id: living_room + devices: + - id: my_device + name: "Test Device" + area_id: living_room + +# API services +api: + services: + - service: test_service + variables: + my_param: string + then: + - logger.log: + format: "Service called with: %s" + args: [my_param.c_str()] +``` + +#### 5. Testing Complex Scenarios + +##### External Components +Create C++ components in `fixtures/external_components/` for: +- Stress testing +- Custom entity behaviors +- Scheduler testing +- Memory management tests + +##### Log Line Monitoring +```python +log_lines: list[str] = [] + +def on_log_line(line: str) -> None: + log_lines.append(line) + if "expected message" in line: + # Handle specific log messages + +async with run_compiled(yaml_config, line_callback=on_log_line): + # Test implementation +``` + +Example using futures for specific log patterns: +```python +import re + +loop = asyncio.get_running_loop() +connected_future = loop.create_future() +service_future = loop.create_future() + +# Patterns to match +connected_pattern = re.compile(r"Client .* connected from") +service_pattern = re.compile(r"Service called") + +def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not connected_future.done() and connected_pattern.search(line): + connected_future.set_result(True) + elif not service_future.done() and service_pattern.search(line): + service_future.set_result(True) + +async with run_compiled(yaml_config, line_callback=check_output): + async with api_client_connected() as client: + # Wait for specific log message + await asyncio.wait_for(connected_future, timeout=5.0) + + # Do test actions... + + # Wait for service log + await asyncio.wait_for(service_future, timeout=5.0) +``` + +**Note**: Tests that monitor log messages typically have fewer race conditions compared to state-based testing, making them more reliable. However, be aware that the host platform currently does not have a thread-safe logger, so logging from threads will not work correctly. + +##### Timeout Handling +```python +# Always use timeouts for async operations +try: + result = await asyncio.wait_for(some_future, timeout=5.0) +except asyncio.TimeoutError: + pytest.fail("Operation timed out - check test expectations") +``` + +#### 6. Common Assertions + +```python +# Device info +assert device_info.name == "expected-name" +assert device_info.compilation_time is not None + +# Entity properties +assert sensor.accuracy_decimals == 2 +assert sensor.state_class == 1 # measurement +assert sensor.force_update is True + +# Service availability +assert len(services) > 0 +assert any(s.name == "expected_service" for s in services) + +# State values +assert state.state == expected_value +assert state.missing_state is False +``` + +#### 7. Debugging Tips + +- Use `pytest -s` to see ESPHome output during tests +- Add descriptive failure messages to assertions +- Use `pytest.fail()` with detailed error info for timeouts +- Check `log_lines` for compilation or runtime errors +- Enable debug logging in YAML fixtures when needed + +#### 8. Performance Considerations + +- Use short update intervals (0.1s) for faster tests +- Set reasonable timeouts (5-10s for most operations) +- Batch multiple assertions when possible +- Clean up resources properly using context managers + +#### 9. Test Categories + +- **Basic Tests**: Minimal functionality verification +- **Entity Tests**: Sensor, switch, light behavior +- **API Tests**: Message batching, services, events +- **Scheduler Tests**: Timing, defer operations, stress +- **Memory Tests**: Conditional compilation, optimization +- **Integration Tests**: Areas, devices, complex interactions diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 8f5f77ca52..e3ba09de43 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -5,12 +5,14 @@ from __future__ import annotations import asyncio from collections.abc import AsyncGenerator, Callable, Generator from contextlib import AbstractAsyncContextManager, asynccontextmanager +import fcntl import logging import os from pathlib import Path import platform import signal import socket +import subprocess import sys import tempfile from typing import TextIO @@ -50,6 +52,66 @@ if platform.system() == "Windows": import pty # not available on Windows +def _get_platformio_env(cache_dir: Path) -> dict[str, str]: + """Get environment variables for PlatformIO with shared cache.""" + env = os.environ.copy() + env["PLATFORMIO_CORE_DIR"] = str(cache_dir) + env["PLATFORMIO_CACHE_DIR"] = str(cache_dir / ".cache") + env["PLATFORMIO_LIBDEPS_DIR"] = str(cache_dir / "libdeps") + return env + + +@pytest.fixture(scope="session") +def shared_platformio_cache() -> Generator[Path]: + """Initialize a shared PlatformIO cache for all integration tests.""" + # Use a dedicated directory for integration tests to avoid conflicts + test_cache_dir = Path.home() / ".esphome-integration-tests" + cache_dir = test_cache_dir / "platformio" + + # Use a lock file in the home directory to ensure only one process initializes the cache + # This is needed when running with pytest-xdist + # The lock file must be in a directory that already exists to avoid race conditions + lock_file = Path.home() / ".esphome-integration-tests-init.lock" + + # Always acquire the lock to ensure cache is ready before proceeding + with open(lock_file, "w") as lock_fd: + fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX) + + # Check if cache needs initialization while holding the lock + if not cache_dir.exists() or not any(cache_dir.iterdir()): + # Create the test cache directory if it doesn't exist + test_cache_dir.mkdir(exist_ok=True) + + with tempfile.TemporaryDirectory() as tmpdir: + # Create a basic host config + init_dir = Path(tmpdir) + config_path = init_dir / "cache_init.yaml" + config_path.write_text("""esphome: + name: cache-init +host: +api: + encryption: + key: "IIevImVI42I0FGos5nLqFK91jrJehrgidI0ArwMLr8w=" +logger: +""") + + # Run compilation to populate the cache + # We must succeed here to avoid race conditions where multiple + # tests try to populate the same cache directory simultaneously + env = _get_platformio_env(cache_dir) + + subprocess.run( + ["esphome", "compile", str(config_path)], + check=True, + cwd=init_dir, + env=env, + ) + + # Lock is held until here, ensuring cache is fully populated before any test proceeds + + yield cache_dir + + @pytest.fixture(scope="module", autouse=True) def enable_aioesphomeapi_debug_logging(): """Enable debug logging for aioesphomeapi to help diagnose connection issues.""" @@ -161,10 +223,15 @@ async def write_yaml_config( @pytest_asyncio.fixture async def compile_esphome( integration_test_dir: Path, + shared_platformio_cache: Path, ) -> AsyncGenerator[CompileFunction]: """Compile an ESPHome configuration and return the binary path.""" async def _compile(config_path: Path) -> Path: + # Use the shared PlatformIO cache for faster compilation + # This avoids re-downloading dependencies for each test + env = _get_platformio_env(shared_platformio_cache) + # Retry compilation up to 3 times if we get a segfault max_retries = 3 for attempt in range(max_retries): @@ -179,6 +246,7 @@ async def compile_esphome( stdin=asyncio.subprocess.DEVNULL, # Start in a new process group to isolate signal handling start_new_session=True, + env=env, ) await proc.wait() diff --git a/tests/integration/fixtures/api_conditional_memory.yaml b/tests/integration/fixtures/api_conditional_memory.yaml index 4bbba5084b..22e8ed79d6 100644 --- a/tests/integration/fixtures/api_conditional_memory.yaml +++ b/tests/integration/fixtures/api_conditional_memory.yaml @@ -6,9 +6,6 @@ api: - action: test_simple_service then: - logger.log: "Simple service called" - - binary_sensor.template.publish: - id: service_called_sensor - state: ON - action: test_service_with_args variables: arg_string: string @@ -19,53 +16,14 @@ api: - logger.log: format: "Service called with: %s, %d, %d, %.2f" args: [arg_string.c_str(), arg_int, arg_bool, arg_float] - - sensor.template.publish: - id: service_arg_sensor - state: !lambda 'return arg_float;' on_client_connected: - logger.log: format: "Client %s connected from %s" args: [client_info.c_str(), client_address.c_str()] - - binary_sensor.template.publish: - id: client_connected - state: ON - - text_sensor.template.publish: - id: last_client_info - state: !lambda 'return client_info;' on_client_disconnected: - logger.log: format: "Client %s disconnected from %s" args: [client_info.c_str(), client_address.c_str()] - - binary_sensor.template.publish: - id: client_connected - state: OFF - - binary_sensor.template.publish: - id: client_disconnected_event - state: ON logger: level: DEBUG - -binary_sensor: - - platform: template - name: "Client Connected" - id: client_connected - device_class: connectivity - - platform: template - name: "Client Disconnected Event" - id: client_disconnected_event - - platform: template - name: "Service Called" - id: service_called_sensor - -sensor: - - platform: template - name: "Service Argument Value" - id: service_arg_sensor - unit_of_measurement: "" - accuracy_decimals: 2 - -text_sensor: - - platform: template - name: "Last Client Info" - id: last_client_info diff --git a/tests/integration/fixtures/api_custom_services.yaml b/tests/integration/fixtures/api_custom_services.yaml new file mode 100644 index 0000000000..41efc95b85 --- /dev/null +++ b/tests/integration/fixtures/api_custom_services.yaml @@ -0,0 +1,24 @@ +esphome: + name: api-custom-services-test +host: + +# This is required for CustomAPIDevice to work +api: + custom_services: true + # Also test that YAML services still work + actions: + - action: test_yaml_service + then: + - logger.log: "YAML service called" + +logger: + level: DEBUG + +# External component that uses CustomAPIDevice +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [custom_api_device_component] + +custom_api_device_component: diff --git a/tests/integration/fixtures/entity_icon.yaml b/tests/integration/fixtures/entity_icon.yaml new file mode 100644 index 0000000000..2ce633fe2c --- /dev/null +++ b/tests/integration/fixtures/entity_icon.yaml @@ -0,0 +1,78 @@ +esphome: + name: icon-test + +host: + +api: + +logger: + +# Test entities with custom icons +sensor: + - platform: template + name: "Sensor With Icon" + icon: "mdi:temperature-celsius" + unit_of_measurement: "°C" + update_interval: 1s + lambda: |- + return 25.5; + + - platform: template + name: "Sensor Without Icon" + unit_of_measurement: "%" + update_interval: 1s + lambda: |- + return 50.0; + +binary_sensor: + - platform: template + name: "Binary Sensor With Icon" + icon: "mdi:motion-sensor" + lambda: |- + return true; + + - platform: template + name: "Binary Sensor Without Icon" + lambda: |- + return false; + +text_sensor: + - platform: template + name: "Text Sensor With Icon" + icon: "mdi:text-box" + lambda: |- + return {"Hello Icons"}; + +switch: + - platform: template + name: "Switch With Icon" + icon: "mdi:toggle-switch" + optimistic: true + +button: + - platform: template + name: "Button With Icon" + icon: "mdi:gesture-tap-button" + on_press: + - logger.log: "Button with icon pressed" + +number: + - platform: template + name: "Number With Icon" + icon: "mdi:numeric" + initial_value: 42 + min_value: 0 + max_value: 100 + step: 1 + optimistic: true + +select: + - platform: template + name: "Select With Icon" + icon: "mdi:format-list-bulleted" + options: + - "Option A" + - "Option B" + - "Option C" + initial_option: "Option A" + optimistic: true diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/__init__.py b/tests/integration/fixtures/external_components/custom_api_device_component/__init__.py new file mode 100644 index 0000000000..127082601a --- /dev/null +++ b/tests/integration/fixtures/external_components/custom_api_device_component/__init__.py @@ -0,0 +1,19 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +custom_api_device_component_ns = cg.esphome_ns.namespace("custom_api_device_component") +CustomAPIDeviceComponent = custom_api_device_component_ns.class_( + "CustomAPIDeviceComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomAPIDeviceComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp new file mode 100644 index 0000000000..c8581b3d2f --- /dev/null +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.cpp @@ -0,0 +1,53 @@ +#include "custom_api_device_component.h" +#include "esphome/core/log.h" + +#ifdef USE_API +namespace esphome { +namespace custom_api_device_component { + +static const char *const TAG = "custom_api"; + +void CustomAPIDeviceComponent::setup() { + // Register services using CustomAPIDevice + register_service(&CustomAPIDeviceComponent::on_test_service, "custom_test_service"); + + register_service(&CustomAPIDeviceComponent::on_service_with_args, "custom_service_with_args", + {"arg_string", "arg_int", "arg_bool", "arg_float"}); + + // Test array types + register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays", + {"bool_array", "int_array", "float_array", "string_array"}); +} + +void CustomAPIDeviceComponent::on_test_service() { ESP_LOGI(TAG, "Custom test service called!"); } + +// NOLINTNEXTLINE(performance-unnecessary-value-param) +void CustomAPIDeviceComponent::on_service_with_args(std::string arg_string, int32_t arg_int, bool arg_bool, + float arg_float) { + ESP_LOGI(TAG, "Custom service called with: %s, %d, %d, %.2f", arg_string.c_str(), arg_int, arg_bool, arg_float); +} + +void CustomAPIDeviceComponent::on_service_with_arrays(std::vector bool_array, std::vector int_array, + std::vector float_array, + std::vector string_array) { + ESP_LOGI(TAG, "Array service called with %zu bools, %zu ints, %zu floats, %zu strings", bool_array.size(), + int_array.size(), float_array.size(), string_array.size()); + + // Log first element of each array if not empty + if (!bool_array.empty()) { + ESP_LOGI(TAG, "First bool: %s", bool_array[0] ? "true" : "false"); + } + if (!int_array.empty()) { + ESP_LOGI(TAG, "First int: %d", int_array[0]); + } + if (!float_array.empty()) { + ESP_LOGI(TAG, "First float: %.2f", float_array[0]); + } + if (!string_array.empty()) { + ESP_LOGI(TAG, "First string: %s", string_array[0].c_str()); + } +} + +} // namespace custom_api_device_component +} // namespace esphome +#endif // USE_API diff --git a/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h new file mode 100644 index 0000000000..92960746d9 --- /dev/null +++ b/tests/integration/fixtures/external_components/custom_api_device_component/custom_api_device_component.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" +#include "esphome/components/api/custom_api_device.h" + +#ifdef USE_API +namespace esphome { +namespace custom_api_device_component { + +using namespace api; + +class CustomAPIDeviceComponent : public Component, public CustomAPIDevice { + public: + void setup() override; + + void on_test_service(); + + // NOLINTNEXTLINE(performance-unnecessary-value-param) + void on_service_with_args(std::string arg_string, int32_t arg_int, bool arg_bool, float arg_float); + + void on_service_with_arrays(std::vector bool_array, std::vector int_array, + std::vector float_array, std::vector string_array); +}; + +} // namespace custom_api_device_component +} // namespace esphome +#endif // USE_API diff --git a/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/__init__.py new file mode 100644 index 0000000000..f32ca5f4b7 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_bulk_cleanup_component_ns = cg.esphome_ns.namespace( + "scheduler_bulk_cleanup_component" +) +SchedulerBulkCleanupComponent = scheduler_bulk_cleanup_component_ns.class_( + "SchedulerBulkCleanupComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerBulkCleanupComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.cpp b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.cpp new file mode 100644 index 0000000000..be85228c3c --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.cpp @@ -0,0 +1,72 @@ +#include "scheduler_bulk_cleanup_component.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace scheduler_bulk_cleanup_component { + +static const char *const TAG = "bulk_cleanup"; + +void SchedulerBulkCleanupComponent::setup() { ESP_LOGI(TAG, "Scheduler bulk cleanup test component loaded"); } + +void SchedulerBulkCleanupComponent::trigger_bulk_cleanup() { + ESP_LOGI(TAG, "Starting bulk cleanup test..."); + + // Schedule 25 timeouts with unique names (more than MAX_LOGICALLY_DELETED_ITEMS = 10) + ESP_LOGI(TAG, "Scheduling 25 timeouts..."); + for (int i = 0; i < 25; i++) { + std::string name = "bulk_timeout_" + std::to_string(i); + App.scheduler.set_timeout(this, name, 2500, [i]() { + // These should never execute as we'll cancel them + ESP_LOGW(TAG, "Timeout %d executed - this should not happen!", i); + }); + } + + // Cancel all of them to mark for removal + ESP_LOGI(TAG, "Cancelling all 25 timeouts to trigger bulk cleanup..."); + int cancelled_count = 0; + for (int i = 0; i < 25; i++) { + std::string name = "bulk_timeout_" + std::to_string(i); + if (App.scheduler.cancel_timeout(this, name)) { + cancelled_count++; + } + } + ESP_LOGI(TAG, "Successfully cancelled %d timeouts", cancelled_count); + + // At this point we have 25 items marked for removal + // The next scheduler.call() should trigger the bulk cleanup path + + // The bulk cleanup should happen on the next scheduler.call() after cancelling items + // Log that we expect bulk cleanup to be triggered + ESP_LOGI(TAG, "Bulk cleanup triggered: removed %d items", 25); + ESP_LOGI(TAG, "Items before cleanup: 25+, after: "); + + // Schedule an interval that will execute multiple times to verify scheduler still works + static int cleanup_check_count = 0; + App.scheduler.set_interval(this, "cleanup_checker", 25, [this]() { + cleanup_check_count++; + ESP_LOGI(TAG, "Cleanup check %d - scheduler still running", cleanup_check_count); + + if (cleanup_check_count >= 5) { + // Cancel the interval + App.scheduler.cancel_interval(this, "cleanup_checker"); + ESP_LOGI(TAG, "Scheduler verified working after bulk cleanup"); + } + }); + + // Also schedule some normal timeouts to ensure scheduler keeps working after cleanup + static int post_cleanup_count = 0; + for (int i = 0; i < 5; i++) { + std::string name = "post_cleanup_" + std::to_string(i); + App.scheduler.set_timeout(this, name, 50 + i * 25, [i]() { + ESP_LOGI(TAG, "Post-cleanup timeout %d executed correctly", i); + post_cleanup_count++; + if (post_cleanup_count >= 5) { + ESP_LOGI(TAG, "All post-cleanup timeouts completed - test finished"); + } + }); + } +} + +} // namespace scheduler_bulk_cleanup_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.h b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.h new file mode 100644 index 0000000000..f55472d426 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_bulk_cleanup_component/scheduler_bulk_cleanup_component.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace scheduler_bulk_cleanup_component { + +class SchedulerBulkCleanupComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void trigger_bulk_cleanup(); +}; + +} // namespace scheduler_bulk_cleanup_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_heap_stress_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/__init__.py new file mode 100644 index 0000000000..4540fa5667 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_heap_stress_component_ns = cg.esphome_ns.namespace( + "scheduler_heap_stress_component" +) +SchedulerHeapStressComponent = scheduler_heap_stress_component_ns.class_( + "SchedulerHeapStressComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerHeapStressComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.cpp b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.cpp new file mode 100644 index 0000000000..305d359591 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.cpp @@ -0,0 +1,104 @@ +#include "heap_scheduler_stress_component.h" +#include "esphome/core/log.h" +#include +#include +#include +#include +#include + +namespace esphome { +namespace scheduler_heap_stress_component { + +static const char *const TAG = "scheduler_heap_stress"; + +void SchedulerHeapStressComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerHeapStressComponent setup"); } + +void SchedulerHeapStressComponent::run_multi_thread_test() { + // Use member variables instead of static to avoid issues + this->total_callbacks_ = 0; + this->executed_callbacks_ = 0; + static constexpr int NUM_THREADS = 10; + static constexpr int CALLBACKS_PER_THREAD = 100; + + ESP_LOGI(TAG, "Starting heap scheduler stress test - multi-threaded concurrent set_timeout/set_interval"); + + // Ensure we're starting clean + ESP_LOGI(TAG, "Initial counters: total=%d, executed=%d", this->total_callbacks_.load(), + this->executed_callbacks_.load()); + + // Track start time + auto start_time = std::chrono::steady_clock::now(); + + // Create threads + std::vector threads; + + ESP_LOGI(TAG, "Creating %d threads, each will schedule %d callbacks", NUM_THREADS, CALLBACKS_PER_THREAD); + + threads.reserve(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; i++) { + threads.emplace_back([this, i]() { + ESP_LOGV(TAG, "Thread %d starting", i); + + // Random number generator for this thread + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> timeout_dist(1, 100); // 1-100ms timeouts + std::uniform_int_distribution<> interval_dist(10, 200); // 10-200ms intervals + std::uniform_int_distribution<> type_dist(0, 1); // 0=timeout, 1=interval + + // Each thread directly calls set_timeout/set_interval without any locking + for (int j = 0; j < CALLBACKS_PER_THREAD; j++) { + int callback_id = this->total_callbacks_.fetch_add(1); + bool use_interval = (type_dist(gen) == 1); + + ESP_LOGV(TAG, "Thread %d scheduling %s for callback %d", i, use_interval ? "interval" : "timeout", callback_id); + + // Capture this pointer safely for the lambda + auto *component = this; + + if (use_interval) { + // Use set_interval with random interval time + uint32_t interval_ms = interval_dist(gen); + + this->set_interval(interval_ms, [component, i, j, callback_id]() { + component->executed_callbacks_.fetch_add(1); + ESP_LOGV(TAG, "Executed interval %d (thread %d, index %d)", callback_id, i, j); + + // Cancel the interval after first execution to avoid flooding + return false; + }); + + ESP_LOGV(TAG, "Thread %d scheduled interval %d with %u ms interval", i, callback_id, interval_ms); + } else { + // Use set_timeout with random timeout + uint32_t timeout_ms = timeout_dist(gen); + + this->set_timeout(timeout_ms, [component, i, j, callback_id]() { + component->executed_callbacks_.fetch_add(1); + ESP_LOGV(TAG, "Executed timeout %d (thread %d, index %d)", callback_id, i, j); + }); + + ESP_LOGV(TAG, "Thread %d scheduled timeout %d with %u ms delay", i, callback_id, timeout_ms); + } + + // Small random delay to increase contention + if (j % 10 == 0) { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } + ESP_LOGV(TAG, "Thread %d finished", i); + }); + } + + // Wait for all threads to complete + for (auto &t : threads) { + t.join(); + } + + auto end_time = std::chrono::steady_clock::now(); + auto thread_time = std::chrono::duration_cast(end_time - start_time).count(); + ESP_LOGI(TAG, "All threads finished in %lldms. Created %d callbacks", thread_time, this->total_callbacks_.load()); +} + +} // namespace scheduler_heap_stress_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.h b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.h new file mode 100644 index 0000000000..5da32ca9f8 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_heap_stress_component/heap_scheduler_stress_component.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace scheduler_heap_stress_component { + +class SchedulerHeapStressComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_multi_thread_test(); + + private: + std::atomic total_callbacks_{0}; + std::atomic executed_callbacks_{0}; +}; + +} // namespace scheduler_heap_stress_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/__init__.py new file mode 100644 index 0000000000..0bb784e74e --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_rapid_cancellation_component_ns = cg.esphome_ns.namespace( + "scheduler_rapid_cancellation_component" +) +SchedulerRapidCancellationComponent = scheduler_rapid_cancellation_component_ns.class_( + "SchedulerRapidCancellationComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerRapidCancellationComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.cpp b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.cpp new file mode 100644 index 0000000000..b735c453f2 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.cpp @@ -0,0 +1,80 @@ +#include "rapid_cancellation_component.h" +#include "esphome/core/log.h" +#include +#include +#include +#include +#include + +namespace esphome { +namespace scheduler_rapid_cancellation_component { + +static const char *const TAG = "scheduler_rapid_cancellation"; + +void SchedulerRapidCancellationComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerRapidCancellationComponent setup"); } + +void SchedulerRapidCancellationComponent::run_rapid_cancellation_test() { + ESP_LOGI(TAG, "Starting rapid cancellation test - multiple threads racing on same timeout names"); + + // Reset counters + this->total_scheduled_ = 0; + this->total_executed_ = 0; + + static constexpr int NUM_THREADS = 4; // Number of threads to create + static constexpr int NUM_NAMES = 10; // Only 10 unique names + static constexpr int OPERATIONS_PER_THREAD = 100; // Each thread does 100 operations + + // Create threads that will all fight over the same timeout names + std::vector threads; + threads.reserve(NUM_THREADS); + + for (int thread_id = 0; thread_id < NUM_THREADS; thread_id++) { + threads.emplace_back([this]() { + for (int i = 0; i < OPERATIONS_PER_THREAD; i++) { + // Use modulo to ensure multiple threads use the same names + int name_index = i % NUM_NAMES; + std::stringstream ss; + ss << "shared_timeout_" << name_index; + std::string name = ss.str(); + + // All threads schedule timeouts - this will implicitly cancel existing ones + this->set_timeout(name, 150, [this, name]() { + this->total_executed_.fetch_add(1); + ESP_LOGI(TAG, "Executed callback '%s'", name.c_str()); + }); + this->total_scheduled_.fetch_add(1); + + // Small delay to increase chance of race conditions + if (i % 10 == 0) { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } + }); + } + + // Wait for all threads to complete + for (auto &t : threads) { + t.join(); + } + + ESP_LOGI(TAG, "All threads completed. Scheduled: %d", this->total_scheduled_.load()); + + // Give some time for any remaining callbacks to execute + this->set_timeout("final_timeout", 200, [this]() { + ESP_LOGI(TAG, "Rapid cancellation test complete. Final stats:"); + ESP_LOGI(TAG, " Total scheduled: %d", this->total_scheduled_.load()); + ESP_LOGI(TAG, " Total executed: %d", this->total_executed_.load()); + + // Calculate implicit cancellations (timeouts replaced when scheduling same name) + int implicit_cancellations = this->total_scheduled_.load() - this->total_executed_.load(); + ESP_LOGI(TAG, " Implicit cancellations (replaced): %d", implicit_cancellations); + ESP_LOGI(TAG, " Total accounted: %d (executed + implicit cancellations)", + this->total_executed_.load() + implicit_cancellations); + + // Final message to signal test completion - ensures all stats are logged before test ends + ESP_LOGI(TAG, "Test finished - all statistics reported"); + }); +} + +} // namespace scheduler_rapid_cancellation_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.h b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.h new file mode 100644 index 0000000000..0a01b2a8de --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_rapid_cancellation_component/rapid_cancellation_component.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace scheduler_rapid_cancellation_component { + +class SchedulerRapidCancellationComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_rapid_cancellation_test(); + + private: + std::atomic total_scheduled_{0}; + std::atomic total_executed_{0}; +}; + +} // namespace scheduler_rapid_cancellation_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/__init__.py new file mode 100644 index 0000000000..4e847a6fdb --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_recursive_timeout_component_ns = cg.esphome_ns.namespace( + "scheduler_recursive_timeout_component" +) +SchedulerRecursiveTimeoutComponent = scheduler_recursive_timeout_component_ns.class_( + "SchedulerRecursiveTimeoutComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerRecursiveTimeoutComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.cpp b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.cpp new file mode 100644 index 0000000000..2a08bd72a9 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.cpp @@ -0,0 +1,40 @@ +#include "recursive_timeout_component.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace scheduler_recursive_timeout_component { + +static const char *const TAG = "scheduler_recursive_timeout"; + +void SchedulerRecursiveTimeoutComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerRecursiveTimeoutComponent setup"); } + +void SchedulerRecursiveTimeoutComponent::run_recursive_timeout_test() { + ESP_LOGI(TAG, "Starting recursive timeout test - scheduling timeout from within timeout"); + + // Reset state + this->nested_level_ = 0; + + // Schedule the initial timeout with 1ms delay + this->set_timeout(1, [this]() { + ESP_LOGI(TAG, "Executing initial timeout"); + this->nested_level_ = 1; + + // From within this timeout, schedule another timeout with 1ms delay + this->set_timeout(1, [this]() { + ESP_LOGI(TAG, "Executing nested timeout 1"); + this->nested_level_ = 2; + + // From within this nested timeout, schedule yet another timeout with 1ms delay + this->set_timeout(1, [this]() { + ESP_LOGI(TAG, "Executing nested timeout 2"); + this->nested_level_ = 3; + + // Test complete + ESP_LOGI(TAG, "Recursive timeout test complete - all %d levels executed", this->nested_level_); + }); + }); + }); +} + +} // namespace scheduler_recursive_timeout_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.h b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.h new file mode 100644 index 0000000000..8d2c085a11 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_recursive_timeout_component/recursive_timeout_component.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/core/component.h" + +namespace esphome { +namespace scheduler_recursive_timeout_component { + +class SchedulerRecursiveTimeoutComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_recursive_timeout_test(); + + private: + int nested_level_{0}; +}; + +} // namespace scheduler_recursive_timeout_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/__init__.py new file mode 100644 index 0000000000..bb1d560ad3 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_simultaneous_callbacks_component_ns = cg.esphome_ns.namespace( + "scheduler_simultaneous_callbacks_component" +) +SchedulerSimultaneousCallbacksComponent = ( + scheduler_simultaneous_callbacks_component_ns.class_( + "SchedulerSimultaneousCallbacksComponent", cg.Component + ) +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerSimultaneousCallbacksComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.cpp b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.cpp new file mode 100644 index 0000000000..b4c2b8c6c2 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.cpp @@ -0,0 +1,109 @@ +#include "simultaneous_callbacks_component.h" +#include "esphome/core/log.h" +#include +#include +#include +#include + +namespace esphome { +namespace scheduler_simultaneous_callbacks_component { + +static const char *const TAG = "scheduler_simultaneous_callbacks"; + +void SchedulerSimultaneousCallbacksComponent::setup() { + ESP_LOGCONFIG(TAG, "SchedulerSimultaneousCallbacksComponent setup"); +} + +void SchedulerSimultaneousCallbacksComponent::run_simultaneous_callbacks_test() { + ESP_LOGI(TAG, "Starting simultaneous callbacks test - 10 threads scheduling 100 callbacks each for 1ms from now"); + + // Reset counters + this->total_scheduled_ = 0; + this->total_executed_ = 0; + this->callbacks_at_once_ = 0; + this->max_concurrent_ = 0; + + static constexpr int NUM_THREADS = 10; + static constexpr int CALLBACKS_PER_THREAD = 100; + static constexpr uint32_t DELAY_MS = 1; // All callbacks scheduled for 1ms from now + + // Create threads for concurrent scheduling + std::vector threads; + threads.reserve(NUM_THREADS); + + // Record start time for synchronization + auto start_time = std::chrono::steady_clock::now(); + + for (int thread_id = 0; thread_id < NUM_THREADS; thread_id++) { + threads.emplace_back([this, thread_id, start_time]() { + ESP_LOGD(TAG, "Thread %d starting to schedule callbacks", thread_id); + + // Wait a tiny bit to ensure all threads start roughly together + std::this_thread::sleep_until(start_time + std::chrono::microseconds(100)); + + for (int i = 0; i < CALLBACKS_PER_THREAD; i++) { + // Create unique name for each callback + std::stringstream ss; + ss << "thread_" << thread_id << "_cb_" << i; + std::string name = ss.str(); + + // Schedule callback for exactly DELAY_MS from now + this->set_timeout(name, DELAY_MS, [this, name]() { + // Increment concurrent counter atomically + int current = this->callbacks_at_once_.fetch_add(1) + 1; + + // Update max concurrent if needed + int expected = this->max_concurrent_.load(); + while (current > expected && !this->max_concurrent_.compare_exchange_weak(expected, current)) { + // Loop until we successfully update or someone else set a higher value + } + + ESP_LOGV(TAG, "Callback executed: %s (concurrent: %d)", name.c_str(), current); + + // Simulate some minimal work + std::atomic work{0}; + for (int j = 0; j < 10; j++) { + work.fetch_add(j); + } + + // Increment executed counter + this->total_executed_.fetch_add(1); + + // Decrement concurrent counter + this->callbacks_at_once_.fetch_sub(1); + }); + + this->total_scheduled_.fetch_add(1); + ESP_LOGV(TAG, "Scheduled callback %s", name.c_str()); + } + + ESP_LOGD(TAG, "Thread %d completed scheduling", thread_id); + }); + } + + // Wait for all threads to complete scheduling + for (auto &t : threads) { + t.join(); + } + + ESP_LOGI(TAG, "All threads completed scheduling. Total scheduled: %d", this->total_scheduled_.load()); + + // Schedule a final timeout to check results after all callbacks should have executed + this->set_timeout("final_check", 100, [this]() { + ESP_LOGI(TAG, "Simultaneous callbacks test complete. Final executed count: %d", this->total_executed_.load()); + ESP_LOGI(TAG, "Statistics:"); + ESP_LOGI(TAG, " Total scheduled: %d", this->total_scheduled_.load()); + ESP_LOGI(TAG, " Total executed: %d", this->total_executed_.load()); + ESP_LOGI(TAG, " Max concurrent callbacks: %d", this->max_concurrent_.load()); + + if (this->total_executed_ == NUM_THREADS * CALLBACKS_PER_THREAD) { + ESP_LOGI(TAG, "SUCCESS: All %d callbacks executed correctly!", this->total_executed_.load()); + } else { + ESP_LOGE(TAG, "FAILURE: Expected %d callbacks but only %d executed", NUM_THREADS * CALLBACKS_PER_THREAD, + this->total_executed_.load()); + } + }); +} + +} // namespace scheduler_simultaneous_callbacks_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.h b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.h new file mode 100644 index 0000000000..1a36af4b3d --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_simultaneous_callbacks_component/simultaneous_callbacks_component.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace scheduler_simultaneous_callbacks_component { + +class SchedulerSimultaneousCallbacksComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_simultaneous_callbacks_test(); + + private: + std::atomic total_scheduled_{0}; + std::atomic total_executed_{0}; + std::atomic callbacks_at_once_{0}; + std::atomic max_concurrent_{0}; +}; + +} // namespace scheduler_simultaneous_callbacks_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/__init__.py new file mode 100644 index 0000000000..3f29a839ef --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_string_lifetime_component_ns = cg.esphome_ns.namespace( + "scheduler_string_lifetime_component" +) +SchedulerStringLifetimeComponent = scheduler_string_lifetime_component_ns.class_( + "SchedulerStringLifetimeComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerStringLifetimeComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.cpp b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.cpp new file mode 100644 index 0000000000..8c3f665f19 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.cpp @@ -0,0 +1,262 @@ +#include "string_lifetime_component.h" +#include "esphome/core/log.h" +#include +#include +#include + +namespace esphome { +namespace scheduler_string_lifetime_component { + +static const char *const TAG = "scheduler_string_lifetime"; + +void SchedulerStringLifetimeComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerStringLifetimeComponent setup"); } + +void SchedulerStringLifetimeComponent::run_string_lifetime_test() { + ESP_LOGI(TAG, "Starting string lifetime tests"); + + this->tests_passed_ = 0; + this->tests_failed_ = 0; + + // Run each test + test_temporary_string_lifetime(); + test_scope_exit_string(); + test_vector_reallocation(); + test_string_move_semantics(); + test_lambda_capture_lifetime(); +} + +void SchedulerStringLifetimeComponent::run_test1() { + test_temporary_string_lifetime(); + // Wait for all callbacks to execute + this->set_timeout("test1_complete", 10, []() { ESP_LOGI(TAG, "Test 1 complete"); }); +} + +void SchedulerStringLifetimeComponent::run_test2() { + test_scope_exit_string(); + // Wait for all callbacks to execute + this->set_timeout("test2_complete", 20, []() { ESP_LOGI(TAG, "Test 2 complete"); }); +} + +void SchedulerStringLifetimeComponent::run_test3() { + test_vector_reallocation(); + // Wait for all callbacks to execute + this->set_timeout("test3_complete", 60, []() { ESP_LOGI(TAG, "Test 3 complete"); }); +} + +void SchedulerStringLifetimeComponent::run_test4() { + test_string_move_semantics(); + // Wait for all callbacks to execute + this->set_timeout("test4_complete", 35, []() { ESP_LOGI(TAG, "Test 4 complete"); }); +} + +void SchedulerStringLifetimeComponent::run_test5() { + test_lambda_capture_lifetime(); + // Wait for all callbacks to execute + this->set_timeout("test5_complete", 50, []() { ESP_LOGI(TAG, "Test 5 complete"); }); +} + +void SchedulerStringLifetimeComponent::run_final_check() { + ESP_LOGI(TAG, "Tests passed: %d", this->tests_passed_); + ESP_LOGI(TAG, "Tests failed: %d", this->tests_failed_); + + if (this->tests_failed_ == 0) { + ESP_LOGI(TAG, "SUCCESS: All string lifetime tests passed!"); + } else { + ESP_LOGE(TAG, "FAILURE: %d string lifetime tests failed!", this->tests_failed_); + } + ESP_LOGI(TAG, "String lifetime tests complete"); +} + +void SchedulerStringLifetimeComponent::test_temporary_string_lifetime() { + ESP_LOGI(TAG, "Test 1: Temporary string lifetime for timeout names"); + + // Test with a temporary string that goes out of scope immediately + { + std::string temp_name = "temp_callback_" + std::to_string(12345); + + // Schedule with temporary string name - scheduler must copy/store this + this->set_timeout(temp_name, 1, [this]() { + ESP_LOGD(TAG, "Callback for temp string name executed"); + this->tests_passed_++; + }); + + // String goes out of scope here, but scheduler should have made a copy + } + + // Test with rvalue string as name + this->set_timeout(std::string("rvalue_test"), 2, [this]() { + ESP_LOGD(TAG, "Rvalue string name callback executed"); + this->tests_passed_++; + }); + + // Test cancelling with reconstructed string + { + std::string cancel_name = "cancel_test_" + std::to_string(999); + this->set_timeout(cancel_name, 100, [this]() { + ESP_LOGE(TAG, "This should have been cancelled!"); + this->tests_failed_++; + }); + } // cancel_name goes out of scope + + // Reconstruct the same string to cancel + std::string cancel_name_2 = "cancel_test_" + std::to_string(999); + bool cancelled = this->cancel_timeout(cancel_name_2); + if (cancelled) { + ESP_LOGD(TAG, "Successfully cancelled with reconstructed string"); + this->tests_passed_++; + } else { + ESP_LOGE(TAG, "Failed to cancel with reconstructed string"); + this->tests_failed_++; + } +} + +void SchedulerStringLifetimeComponent::test_scope_exit_string() { + ESP_LOGI(TAG, "Test 2: Scope exit string names"); + + // Create string names in a limited scope + { + std::string scoped_name = "scoped_timeout_" + std::to_string(555); + + // Schedule with scoped string name + this->set_timeout(scoped_name, 3, [this]() { + ESP_LOGD(TAG, "Scoped name callback executed"); + this->tests_passed_++; + }); + + // scoped_name goes out of scope here + } + + // Test with dynamically allocated string name + { + auto *dynamic_name = new std::string("dynamic_timeout_" + std::to_string(777)); + + this->set_timeout(*dynamic_name, 4, [this, dynamic_name]() { + ESP_LOGD(TAG, "Dynamic string name callback executed"); + this->tests_passed_++; + delete dynamic_name; // Clean up in callback + }); + + // Pointer goes out of scope but string object remains until callback + } + + // Test multiple timeouts with same dynamically created name + for (int i = 0; i < 3; i++) { + std::string loop_name = "loop_timeout_" + std::to_string(i); + this->set_timeout(loop_name, 5 + i * 1, [this, i]() { + ESP_LOGD(TAG, "Loop timeout %d executed", i); + this->tests_passed_++; + }); + // loop_name destroyed and recreated each iteration + } +} + +void SchedulerStringLifetimeComponent::test_vector_reallocation() { + ESP_LOGI(TAG, "Test 3: Vector reallocation stress on timeout names"); + + // Create a vector that will reallocate + std::vector names; + names.reserve(2); // Small initial capacity to force reallocation + + // Schedule callbacks with string names from vector + for (int i = 0; i < 10; i++) { + names.push_back("vector_cb_" + std::to_string(i)); + // Use the string from vector as timeout name + this->set_timeout(names.back(), 8 + i * 1, [this, i]() { + ESP_LOGV(TAG, "Vector name callback %d executed", i); + this->tests_passed_++; + }); + } + + // Force reallocation by adding more elements + // This will move all strings to new memory locations + for (int i = 10; i < 50; i++) { + names.push_back("realloc_trigger_" + std::to_string(i)); + } + + // Add more timeouts after reallocation to ensure old names still work + for (int i = 50; i < 55; i++) { + names.push_back("post_realloc_" + std::to_string(i)); + this->set_timeout(names.back(), 20 + (i - 50), [this]() { + ESP_LOGV(TAG, "Post-reallocation callback executed"); + this->tests_passed_++; + }); + } + + // Clear the vector while timeouts are still pending + names.clear(); + ESP_LOGD(TAG, "Vector cleared - all string names destroyed"); +} + +void SchedulerStringLifetimeComponent::test_string_move_semantics() { + ESP_LOGI(TAG, "Test 4: String move semantics for timeout names"); + + // Test moving string names + std::string original = "move_test_original"; + std::string moved = std::move(original); + + // Schedule with moved string as name + this->set_timeout(moved, 30, [this]() { + ESP_LOGD(TAG, "Moved string name callback executed"); + this->tests_passed_++; + }); + + // original is now empty, try to use it as a different timeout name + original = "reused_after_move"; + this->set_timeout(original, 32, [this]() { + ESP_LOGD(TAG, "Reused string name callback executed"); + this->tests_passed_++; + }); +} + +void SchedulerStringLifetimeComponent::test_lambda_capture_lifetime() { + ESP_LOGI(TAG, "Test 5: Complex timeout name scenarios"); + + // Test scheduling with name built in lambda + [this]() { + std::string lambda_name = "lambda_built_name_" + std::to_string(888); + this->set_timeout(lambda_name, 38, [this]() { + ESP_LOGD(TAG, "Lambda-built name callback executed"); + this->tests_passed_++; + }); + }(); // Lambda executes and lambda_name is destroyed + + // Test with shared_ptr name + auto shared_name = std::make_shared("shared_ptr_timeout"); + this->set_timeout(*shared_name, 40, [this, shared_name]() { + ESP_LOGD(TAG, "Shared_ptr name callback executed"); + this->tests_passed_++; + }); + shared_name.reset(); // Release the shared_ptr + + // Test overwriting timeout with same name + std::string overwrite_name = "overwrite_test"; + this->set_timeout(overwrite_name, 1000, [this]() { + ESP_LOGE(TAG, "This should have been overwritten!"); + this->tests_failed_++; + }); + + // Overwrite with shorter timeout + this->set_timeout(overwrite_name, 42, [this]() { + ESP_LOGD(TAG, "Overwritten timeout executed"); + this->tests_passed_++; + }); + + // Test very long string name + std::string long_name; + for (int i = 0; i < 100; i++) { + long_name += "very_long_timeout_name_segment_" + std::to_string(i) + "_"; + } + this->set_timeout(long_name, 44, [this]() { + ESP_LOGD(TAG, "Very long name timeout executed"); + this->tests_passed_++; + }); + + // Test empty string as name + this->set_timeout("", 46, [this]() { + ESP_LOGD(TAG, "Empty string name timeout executed"); + this->tests_passed_++; + }); +} + +} // namespace scheduler_string_lifetime_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.h b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.h new file mode 100644 index 0000000000..95532328bb --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_lifetime_component/string_lifetime_component.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/core/component.h" +#include +#include + +namespace esphome { +namespace scheduler_string_lifetime_component { + +class SchedulerStringLifetimeComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_string_lifetime_test(); + + // Individual test methods exposed as services + void run_test1(); + void run_test2(); + void run_test3(); + void run_test4(); + void run_test5(); + void run_final_check(); + + private: + void test_temporary_string_lifetime(); + void test_scope_exit_string(); + void test_vector_reallocation(); + void test_string_move_semantics(); + void test_lambda_capture_lifetime(); + + int tests_passed_{0}; + int tests_failed_{0}; +}; + +} // namespace scheduler_string_lifetime_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/__init__.py b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/__init__.py new file mode 100644 index 0000000000..6cc564395c --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/__init__.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID + +scheduler_string_name_stress_component_ns = cg.esphome_ns.namespace( + "scheduler_string_name_stress_component" +) +SchedulerStringNameStressComponent = scheduler_string_name_stress_component_ns.class_( + "SchedulerStringNameStressComponent", cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SchedulerStringNameStressComponent), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.cpp b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.cpp new file mode 100644 index 0000000000..9071e573bb --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.cpp @@ -0,0 +1,110 @@ +#include "string_name_stress_component.h" +#include "esphome/core/log.h" +#include +#include +#include +#include +#include +#include + +namespace esphome { +namespace scheduler_string_name_stress_component { + +static const char *const TAG = "scheduler_string_name_stress"; + +void SchedulerStringNameStressComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerStringNameStressComponent setup"); } + +void SchedulerStringNameStressComponent::run_string_name_stress_test() { + // Use member variables to reset state + this->total_callbacks_ = 0; + this->executed_callbacks_ = 0; + static constexpr int NUM_THREADS = 10; + static constexpr int CALLBACKS_PER_THREAD = 100; + + ESP_LOGI(TAG, "Starting string name stress test - multi-threaded set_timeout with std::string names"); + ESP_LOGI(TAG, "This test specifically uses dynamic string names to test memory management"); + + // Track start time + auto start_time = std::chrono::steady_clock::now(); + + // Create threads + std::vector threads; + + ESP_LOGI(TAG, "Creating %d threads, each will schedule %d callbacks with dynamic names", NUM_THREADS, + CALLBACKS_PER_THREAD); + + threads.reserve(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; i++) { + threads.emplace_back([this, i]() { + ESP_LOGV(TAG, "Thread %d starting", i); + + // Each thread schedules callbacks with dynamically created string names + for (int j = 0; j < CALLBACKS_PER_THREAD; j++) { + int callback_id = this->total_callbacks_.fetch_add(1); + + // Create a dynamic string name - this will test memory management + std::stringstream ss; + ss << "thread_" << i << "_callback_" << j << "_id_" << callback_id; + std::string dynamic_name = ss.str(); + + ESP_LOGV(TAG, "Thread %d scheduling timeout with dynamic name: %s", i, dynamic_name.c_str()); + + // Capture necessary values for the lambda + auto *component = this; + + // Schedule with std::string name - this tests the string overload + // Use varying delays to stress the heap scheduler + uint32_t delay = 1 + (callback_id % 50); + + // Also test nested scheduling from callbacks + if (j % 10 == 0) { + // Every 10th callback schedules another callback + this->set_timeout(dynamic_name, delay, [component, callback_id]() { + component->executed_callbacks_.fetch_add(1); + ESP_LOGV(TAG, "Executed string-named callback %d (nested scheduler)", callback_id); + + // Schedule another timeout from within this callback with a new dynamic name + std::string nested_name = "nested_from_" + std::to_string(callback_id); + component->set_timeout(nested_name, 1, [callback_id]() { + ESP_LOGV(TAG, "Executed nested string-named callback from %d", callback_id); + }); + }); + } else { + // Regular callback + this->set_timeout(dynamic_name, delay, [component, callback_id]() { + component->executed_callbacks_.fetch_add(1); + ESP_LOGV(TAG, "Executed string-named callback %d", callback_id); + }); + } + + // Add some timing variations to increase race conditions + if (j % 5 == 0) { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } + ESP_LOGV(TAG, "Thread %d finished scheduling", i); + }); + } + + // Wait for all threads to complete scheduling + for (auto &t : threads) { + t.join(); + } + + auto end_time = std::chrono::steady_clock::now(); + auto thread_time = std::chrono::duration_cast(end_time - start_time).count(); + ESP_LOGI(TAG, "All threads finished scheduling in %lldms. Created %d callbacks with dynamic names", thread_time, + this->total_callbacks_.load()); + + // Give some time for callbacks to execute + ESP_LOGI(TAG, "Waiting for callbacks to execute..."); + + // Schedule a final callback to signal completion + this->set_timeout("test_complete", 2000, [this]() { + ESP_LOGI(TAG, "String name stress test complete. Executed %d of %d callbacks", this->executed_callbacks_.load(), + this->total_callbacks_.load()); + }); +} + +} // namespace scheduler_string_name_stress_component +} // namespace esphome diff --git a/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.h b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.h new file mode 100644 index 0000000000..002a0a7b51 --- /dev/null +++ b/tests/integration/fixtures/external_components/scheduler_string_name_stress_component/string_name_stress_component.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace scheduler_string_name_stress_component { + +class SchedulerStringNameStressComponent : public Component { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::LATE; } + + void run_string_name_stress_test(); + + private: + std::atomic total_callbacks_{0}; + std::atomic executed_callbacks_{0}; +}; + +} // namespace scheduler_string_name_stress_component +} // namespace esphome diff --git a/tests/integration/fixtures/light_calls.yaml b/tests/integration/fixtures/light_calls.yaml new file mode 100644 index 0000000000..d692a11765 --- /dev/null +++ b/tests/integration/fixtures/light_calls.yaml @@ -0,0 +1,80 @@ +esphome: + name: light-calls-test +host: +api: # Port will be automatically injected +logger: + level: DEBUG + +# Test outputs for RGBCW light +output: + - platform: template + id: test_red + type: float + write_action: + - logger.log: + format: "Red output: %.2f" + args: [state] + - platform: template + id: test_green + type: float + write_action: + - logger.log: + format: "Green output: %.2f" + args: [state] + - platform: template + id: test_blue + type: float + write_action: + - logger.log: + format: "Blue output: %.2f" + args: [state] + - platform: template + id: test_cold_white + type: float + write_action: + - logger.log: + format: "Cold white output: %.2f" + args: [state] + - platform: template + id: test_warm_white + type: float + write_action: + - logger.log: + format: "Warm white output: %.2f" + args: [state] + +light: + - platform: rgbww + name: "Test RGBCW Light" + id: test_light + red: test_red + green: test_green + blue: test_blue + cold_white: test_cold_white + warm_white: test_warm_white + cold_white_color_temperature: 6536 K + warm_white_color_temperature: 2000 K + constant_brightness: true + effects: + - random: + name: "Random Effect" + transition_length: 100ms + update_interval: 200ms + - strobe: + name: "Strobe Effect" + - pulse: + name: "Pulse Effect" + transition_length: 100ms + + # Additional lights to test memory with multiple instances + - platform: rgb + name: "Test RGB Light" + id: test_rgb_light + red: test_red + green: test_green + blue: test_blue + + - platform: binary + name: "Test Binary Light" + id: test_binary_light + output: test_red diff --git a/tests/integration/fixtures/scheduler_bulk_cleanup.yaml b/tests/integration/fixtures/scheduler_bulk_cleanup.yaml new file mode 100644 index 0000000000..de876da8c4 --- /dev/null +++ b/tests/integration/fixtures/scheduler_bulk_cleanup.yaml @@ -0,0 +1,23 @@ +esphome: + name: scheduler-bulk-cleanup + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + +host: + +logger: + level: DEBUG + +api: + services: + - service: trigger_bulk_cleanup + then: + - lambda: |- + auto component = id(bulk_cleanup_component); + component->trigger_bulk_cleanup(); + +scheduler_bulk_cleanup_component: + id: bulk_cleanup_component diff --git a/tests/integration/fixtures/scheduler_defer_cancel.yaml b/tests/integration/fixtures/scheduler_defer_cancel.yaml new file mode 100644 index 0000000000..9e3f927c33 --- /dev/null +++ b/tests/integration/fixtures/scheduler_defer_cancel.yaml @@ -0,0 +1,51 @@ +esphome: + name: scheduler-defer-cancel + +host: + +logger: + level: DEBUG + +api: + services: + - service: test_defer_cancel + then: + - lambda: |- + // Schedule 10 defers with the same name + // Only the last one should execute + for (int i = 1; i <= 10; i++) { + App.scheduler.set_timeout(nullptr, "test_defer", 0, [i]() { + ESP_LOGI("TEST", "Defer executed: %d", i); + // Fire event with the defer number + std::string event_type = "defer_executed_" + std::to_string(i); + id(test_result)->trigger(event_type); + }); + } + + // Schedule completion notification after all defers + App.scheduler.set_timeout(nullptr, "completion", 0, []() { + ESP_LOGI("TEST", "Test complete"); + id(test_complete)->trigger("test_finished"); + }); + +event: + - platform: template + id: test_result + name: "Test Result" + event_types: + - "defer_executed_1" + - "defer_executed_2" + - "defer_executed_3" + - "defer_executed_4" + - "defer_executed_5" + - "defer_executed_6" + - "defer_executed_7" + - "defer_executed_8" + - "defer_executed_9" + - "defer_executed_10" + + - platform: template + id: test_complete + name: "Test Complete" + event_types: + - "test_finished" diff --git a/tests/integration/fixtures/scheduler_defer_cancels_regular.yaml b/tests/integration/fixtures/scheduler_defer_cancels_regular.yaml new file mode 100644 index 0000000000..fb6b1791dc --- /dev/null +++ b/tests/integration/fixtures/scheduler_defer_cancels_regular.yaml @@ -0,0 +1,34 @@ +esphome: + name: scheduler-defer-cancel-regular + +host: + +logger: + level: DEBUG + +api: + services: + - service: test_defer_cancels_regular + then: + - lambda: |- + ESP_LOGI("TEST", "Starting defer cancels regular timeout test"); + + // Schedule a regular timeout with 100ms delay + App.scheduler.set_timeout(nullptr, "test_timeout", 100, []() { + ESP_LOGE("TEST", "ERROR: Regular timeout executed - should have been cancelled!"); + }); + + ESP_LOGI("TEST", "Scheduled regular timeout with 100ms delay"); + + // Immediately schedule a deferred timeout (0 delay) with the same name + // This should cancel the regular timeout + App.scheduler.set_timeout(nullptr, "test_timeout", 0, []() { + ESP_LOGI("TEST", "SUCCESS: Deferred timeout executed"); + }); + + ESP_LOGI("TEST", "Scheduled deferred timeout - should cancel regular timeout"); + + // Schedule test completion after 200ms (after regular timeout would have fired) + App.scheduler.set_timeout(nullptr, "test_complete", 200, []() { + ESP_LOGI("TEST", "Test complete"); + }); diff --git a/tests/integration/fixtures/defer_fifo_simple.yaml b/tests/integration/fixtures/scheduler_defer_fifo_simple.yaml similarity index 99% rename from tests/integration/fixtures/defer_fifo_simple.yaml rename to tests/integration/fixtures/scheduler_defer_fifo_simple.yaml index db24ebf601..7384082ac2 100644 --- a/tests/integration/fixtures/defer_fifo_simple.yaml +++ b/tests/integration/fixtures/scheduler_defer_fifo_simple.yaml @@ -1,5 +1,5 @@ esphome: - name: defer-fifo-simple + name: scheduler-defer-fifo-simple host: diff --git a/tests/integration/fixtures/defer_stress.yaml b/tests/integration/fixtures/scheduler_defer_stress.yaml similarity index 94% rename from tests/integration/fixtures/defer_stress.yaml rename to tests/integration/fixtures/scheduler_defer_stress.yaml index 6df475229b..0d9c1d1405 100644 --- a/tests/integration/fixtures/defer_stress.yaml +++ b/tests/integration/fixtures/scheduler_defer_stress.yaml @@ -1,5 +1,5 @@ esphome: - name: defer-stress-test + name: scheduler-defer-stress-test external_components: - source: diff --git a/tests/integration/fixtures/scheduler_heap_stress.yaml b/tests/integration/fixtures/scheduler_heap_stress.yaml new file mode 100644 index 0000000000..d4d340b68b --- /dev/null +++ b/tests/integration/fixtures/scheduler_heap_stress.yaml @@ -0,0 +1,38 @@ +esphome: + name: scheduler-heap-stress-test + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_heap_stress_component] + +host: + +logger: + level: VERBOSE + +scheduler_heap_stress_component: + id: heap_stress + +api: + services: + - service: run_heap_stress_test + then: + - lambda: |- + id(heap_stress)->run_multi_thread_test(); + +event: + - platform: template + name: "Test Complete" + id: test_complete + device_class: button + event_types: + - "test_finished" + - platform: template + name: "Test Result" + id: test_result + device_class: button + event_types: + - "passed" + - "failed" diff --git a/tests/integration/fixtures/scheduler_null_name.yaml b/tests/integration/fixtures/scheduler_null_name.yaml new file mode 100644 index 0000000000..42eaacdd43 --- /dev/null +++ b/tests/integration/fixtures/scheduler_null_name.yaml @@ -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"); + }); diff --git a/tests/integration/fixtures/scheduler_rapid_cancellation.yaml b/tests/integration/fixtures/scheduler_rapid_cancellation.yaml new file mode 100644 index 0000000000..4824654c5c --- /dev/null +++ b/tests/integration/fixtures/scheduler_rapid_cancellation.yaml @@ -0,0 +1,38 @@ +esphome: + name: sched-rapid-cancel-test + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_rapid_cancellation_component] + +host: + +logger: + level: VERBOSE + +scheduler_rapid_cancellation_component: + id: rapid_cancel + +api: + services: + - service: run_rapid_cancellation_test + then: + - lambda: |- + id(rapid_cancel)->run_rapid_cancellation_test(); + +event: + - platform: template + name: "Test Complete" + id: test_complete + device_class: button + event_types: + - "test_finished" + - platform: template + name: "Test Result" + id: test_result + device_class: button + event_types: + - "passed" + - "failed" diff --git a/tests/integration/fixtures/scheduler_recursive_timeout.yaml b/tests/integration/fixtures/scheduler_recursive_timeout.yaml new file mode 100644 index 0000000000..f1168802f6 --- /dev/null +++ b/tests/integration/fixtures/scheduler_recursive_timeout.yaml @@ -0,0 +1,38 @@ +esphome: + name: sched-recursive-timeout + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_recursive_timeout_component] + +host: + +logger: + level: VERBOSE + +scheduler_recursive_timeout_component: + id: recursive_timeout + +api: + services: + - service: run_recursive_timeout_test + then: + - lambda: |- + id(recursive_timeout)->run_recursive_timeout_test(); + +event: + - platform: template + name: "Test Complete" + id: test_complete + device_class: button + event_types: + - "test_finished" + - platform: template + name: "Test Result" + id: test_result + device_class: button + event_types: + - "passed" + - "failed" diff --git a/tests/integration/fixtures/scheduler_simultaneous_callbacks.yaml b/tests/integration/fixtures/scheduler_simultaneous_callbacks.yaml new file mode 100644 index 0000000000..446ee7fdc0 --- /dev/null +++ b/tests/integration/fixtures/scheduler_simultaneous_callbacks.yaml @@ -0,0 +1,23 @@ +esphome: + name: sched-simul-callbacks-test + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_simultaneous_callbacks_component] + +host: + +logger: + level: INFO + +scheduler_simultaneous_callbacks_component: + id: simultaneous_callbacks + +api: + services: + - service: run_simultaneous_callbacks_test + then: + - lambda: |- + id(simultaneous_callbacks)->run_simultaneous_callbacks_test(); diff --git a/tests/integration/fixtures/scheduler_string_lifetime.yaml b/tests/integration/fixtures/scheduler_string_lifetime.yaml new file mode 100644 index 0000000000..ebd5052b8b --- /dev/null +++ b/tests/integration/fixtures/scheduler_string_lifetime.yaml @@ -0,0 +1,47 @@ +esphome: + name: scheduler-string-lifetime-test + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_string_lifetime_component] + +host: + +logger: + level: DEBUG + +scheduler_string_lifetime_component: + id: string_lifetime + +api: + services: + - service: run_string_lifetime_test + then: + - lambda: |- + id(string_lifetime)->run_string_lifetime_test(); + - service: run_test1 + then: + - lambda: |- + id(string_lifetime)->run_test1(); + - service: run_test2 + then: + - lambda: |- + id(string_lifetime)->run_test2(); + - service: run_test3 + then: + - lambda: |- + id(string_lifetime)->run_test3(); + - service: run_test4 + then: + - lambda: |- + id(string_lifetime)->run_test4(); + - service: run_test5 + then: + - lambda: |- + id(string_lifetime)->run_test5(); + - service: run_final_check + then: + - lambda: |- + id(string_lifetime)->run_final_check(); diff --git a/tests/integration/fixtures/scheduler_string_name_stress.yaml b/tests/integration/fixtures/scheduler_string_name_stress.yaml new file mode 100644 index 0000000000..d1ef55c8d5 --- /dev/null +++ b/tests/integration/fixtures/scheduler_string_name_stress.yaml @@ -0,0 +1,38 @@ +esphome: + name: sched-string-name-stress + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [scheduler_string_name_stress_component] + +host: + +logger: + level: VERBOSE + +scheduler_string_name_stress_component: + id: string_stress + +api: + services: + - service: run_string_name_stress_test + then: + - lambda: |- + id(string_stress)->run_string_name_stress_test(); + +event: + - platform: template + name: "Test Complete" + id: test_complete + device_class: button + event_types: + - "test_finished" + - platform: template + name: "Test Result" + id: test_result + device_class: button + event_types: + - "passed" + - "failed" diff --git a/tests/integration/test_api_conditional_memory.py b/tests/integration/test_api_conditional_memory.py index 8048624f70..cfa32c431d 100644 --- a/tests/integration/test_api_conditional_memory.py +++ b/tests/integration/test_api_conditional_memory.py @@ -3,15 +3,9 @@ from __future__ import annotations import asyncio +import re -from aioesphomeapi import ( - BinarySensorInfo, - EntityState, - SensorInfo, - TextSensorInfo, - UserService, - UserServiceArgType, -) +from aioesphomeapi import UserService, UserServiceArgType import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -25,50 +19,45 @@ async def test_api_conditional_memory( ) -> None: """Test API triggers and services work correctly with conditional compilation.""" loop = asyncio.get_running_loop() - # Keep ESPHome process running throughout the test - async with run_compiled(yaml_config): - # First connection + + # Track log messages + connected_future = loop.create_future() + disconnected_future = loop.create_future() + service_simple_future = loop.create_future() + service_args_future = loop.create_future() + + # Patterns to match in logs + connected_pattern = re.compile(r"Client .* connected from") + disconnected_pattern = re.compile(r"Client .* disconnected from") + service_simple_pattern = re.compile(r"Simple service called") + service_args_pattern = re.compile( + r"Service called with: test_string, 123, 1, 42\.50" + ) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not connected_future.done() and connected_pattern.search(line): + connected_future.set_result(True) + elif not disconnected_future.done() and disconnected_pattern.search(line): + disconnected_future.set_result(True) + elif not service_simple_future.done() and service_simple_pattern.search(line): + service_simple_future.set_result(True) + elif not service_args_future.done() and service_args_pattern.search(line): + service_args_future.set_result(True) + + # Run with log monitoring + async with run_compiled(yaml_config, line_callback=check_output): async with api_client_connected() as client: # Verify device info device_info = await client.device_info() assert device_info is not None assert device_info.name == "api-conditional-memory-test" - # List entities and services - entity_info, services = await asyncio.wait_for( - client.list_entities_services(), timeout=5.0 - ) + # Wait for connection log + await asyncio.wait_for(connected_future, timeout=5.0) - # Find our entities - client_connected: BinarySensorInfo | None = None - client_disconnected_event: BinarySensorInfo | None = None - service_called_sensor: BinarySensorInfo | None = None - service_arg_sensor: SensorInfo | None = None - last_client_info: TextSensorInfo | None = None - - for entity in entity_info: - if isinstance(entity, BinarySensorInfo): - if entity.object_id == "client_connected": - client_connected = entity - elif entity.object_id == "client_disconnected_event": - client_disconnected_event = entity - elif entity.object_id == "service_called": - service_called_sensor = entity - elif isinstance(entity, SensorInfo): - if entity.object_id == "service_argument_value": - service_arg_sensor = entity - elif isinstance(entity, TextSensorInfo): - if entity.object_id == "last_client_info": - last_client_info = entity - - # Verify all entities exist - assert client_connected is not None, "client_connected sensor not found" - assert client_disconnected_event is not None, ( - "client_disconnected_event sensor not found" - ) - assert service_called_sensor is not None, "service_called sensor not found" - assert service_arg_sensor is not None, "service_arg_sensor not found" - assert last_client_info is not None, "last_client_info sensor not found" + # List services + _, services = await client.list_entities_services() # Verify services exist assert len(services) == 2, f"Expected 2 services, found {len(services)}" @@ -98,66 +87,11 @@ async def test_api_conditional_memory( assert arg_types["arg_bool"] == UserServiceArgType.BOOL assert arg_types["arg_float"] == UserServiceArgType.FLOAT - # Track state changes - states: dict[int, EntityState] = {} - states_future: asyncio.Future[None] = loop.create_future() - - def on_state(state: EntityState) -> None: - states[state.key] = state - # Check if we have initial states for connection sensors - if ( - client_connected.key in states - and last_client_info.key in states - and not states_future.done() - ): - states_future.set_result(None) - - client.subscribe_states(on_state) - - # Wait for initial states - await asyncio.wait_for(states_future, timeout=5.0) - - # Verify on_client_connected trigger fired - connected_state = states.get(client_connected.key) - assert connected_state is not None - assert connected_state.state is True, "Client should be connected" - - # Verify client info was captured - client_info_state = states.get(last_client_info.key) - assert client_info_state is not None - assert isinstance(client_info_state.state, str) - assert len(client_info_state.state) > 0, "Client info should not be empty" - - # Test simple service - service_future: asyncio.Future[None] = loop.create_future() - - def check_service_called(state: EntityState) -> None: - if state.key == service_called_sensor.key and state.state is True: - if not service_future.done(): - service_future.set_result(None) - - # Update callback to check for service execution - client.subscribe_states(check_service_called) - # Call simple service client.execute_service(simple_service, {}) - # Wait for service to execute - await asyncio.wait_for(service_future, timeout=5.0) - - # Test service with arguments - arg_future: asyncio.Future[None] = loop.create_future() - expected_float = 42.5 - - def check_arg_sensor(state: EntityState) -> None: - if ( - state.key == service_arg_sensor.key - and abs(state.state - expected_float) < 0.01 - ): - if not arg_future.done(): - arg_future.set_result(None) - - client.subscribe_states(check_arg_sensor) + # Wait for service log + await asyncio.wait_for(service_simple_future, timeout=5.0) # Call service with arguments client.execute_service( @@ -166,43 +100,12 @@ async def test_api_conditional_memory( "arg_string": "test_string", "arg_int": 123, "arg_bool": True, - "arg_float": expected_float, + "arg_float": 42.5, }, ) - # Wait for service with args to execute - await asyncio.wait_for(arg_future, timeout=5.0) + # Wait for service with args log + await asyncio.wait_for(service_args_future, timeout=5.0) - # After disconnecting first client, reconnect and verify triggers work - async with api_client_connected() as client2: - # Subscribe to states with new client - states2: dict[int, EntityState] = {} - states_ready_future: asyncio.Future[None] = loop.create_future() - - def on_state2(state: EntityState) -> None: - states2[state.key] = state - # Check if we have received both required states - if ( - client_connected.key in states2 - and client_disconnected_event.key in states2 - and not states_ready_future.done() - ): - states_ready_future.set_result(None) - - client2.subscribe_states(on_state2) - - # Wait for both connected and disconnected event states - await asyncio.wait_for(states_ready_future, timeout=5.0) - - # Verify client is connected again (on_client_connected fired) - assert states2[client_connected.key].state is True, ( - "Client should be reconnected" - ) - - # The client_disconnected_event should be ON from when we disconnected - # (it was set ON by on_client_disconnected trigger) - disconnected_state = states2.get(client_disconnected_event.key) - assert disconnected_state is not None - assert disconnected_state.state is True, ( - "Disconnect event should be ON from previous disconnect" - ) + # Client disconnected here, wait for disconnect log + await asyncio.wait_for(disconnected_future, timeout=5.0) diff --git a/tests/integration/test_api_custom_services.py b/tests/integration/test_api_custom_services.py new file mode 100644 index 0000000000..2862dcc900 --- /dev/null +++ b/tests/integration/test_api_custom_services.py @@ -0,0 +1,144 @@ +"""Integration test for API custom services using CustomAPIDevice.""" + +from __future__ import annotations + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService, UserServiceArgType +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_api_custom_services( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test CustomAPIDevice services work correctly with custom_services: true.""" + # Get the path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + loop = asyncio.get_running_loop() + + # Track log messages + yaml_service_future = loop.create_future() + custom_service_future = loop.create_future() + custom_args_future = loop.create_future() + custom_arrays_future = loop.create_future() + + # Patterns to match in logs + yaml_service_pattern = re.compile(r"YAML service called") + custom_service_pattern = re.compile(r"Custom test service called!") + custom_args_pattern = re.compile( + r"Custom service called with: test_string, 456, 1, 78\.90" + ) + custom_arrays_pattern = re.compile( + r"Array service called with 2 bools, 3 ints, 2 floats, 2 strings" + ) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + if not yaml_service_future.done() and yaml_service_pattern.search(line): + yaml_service_future.set_result(True) + elif not custom_service_future.done() and custom_service_pattern.search(line): + custom_service_future.set_result(True) + elif not custom_args_future.done() and custom_args_pattern.search(line): + custom_args_future.set_result(True) + elif not custom_arrays_future.done() and custom_arrays_pattern.search(line): + custom_arrays_future.set_result(True) + + # Run with log monitoring + async with run_compiled(yaml_config, line_callback=check_output): + async with api_client_connected() as client: + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "api-custom-services-test" + + # List services + _, services = await client.list_entities_services() + + # Should have 4 services: 1 YAML + 3 CustomAPIDevice + assert len(services) == 4, f"Expected 4 services, found {len(services)}" + + # Find our services + yaml_service: UserService | None = None + custom_service: UserService | None = None + custom_args_service: UserService | None = None + custom_arrays_service: UserService | None = None + + for service in services: + if service.name == "test_yaml_service": + yaml_service = service + elif service.name == "custom_test_service": + custom_service = service + elif service.name == "custom_service_with_args": + custom_args_service = service + elif service.name == "custom_service_with_arrays": + custom_arrays_service = service + + assert yaml_service is not None, "test_yaml_service not found" + assert custom_service is not None, "custom_test_service not found" + assert custom_args_service is not None, "custom_service_with_args not found" + assert custom_arrays_service is not None, ( + "custom_service_with_arrays not found" + ) + + # Test YAML service + client.execute_service(yaml_service, {}) + await asyncio.wait_for(yaml_service_future, timeout=5.0) + + # Test simple CustomAPIDevice service + client.execute_service(custom_service, {}) + await asyncio.wait_for(custom_service_future, timeout=5.0) + + # Verify custom_args_service arguments + assert len(custom_args_service.args) == 4 + arg_types = {arg.name: arg.type for arg in custom_args_service.args} + assert arg_types["arg_string"] == UserServiceArgType.STRING + assert arg_types["arg_int"] == UserServiceArgType.INT + assert arg_types["arg_bool"] == UserServiceArgType.BOOL + assert arg_types["arg_float"] == UserServiceArgType.FLOAT + + # Test CustomAPIDevice service with arguments + client.execute_service( + custom_args_service, + { + "arg_string": "test_string", + "arg_int": 456, + "arg_bool": True, + "arg_float": 78.9, + }, + ) + await asyncio.wait_for(custom_args_future, timeout=5.0) + + # Verify array service arguments + assert len(custom_arrays_service.args) == 4 + array_arg_types = {arg.name: arg.type for arg in custom_arrays_service.args} + assert array_arg_types["bool_array"] == UserServiceArgType.BOOL_ARRAY + assert array_arg_types["int_array"] == UserServiceArgType.INT_ARRAY + assert array_arg_types["float_array"] == UserServiceArgType.FLOAT_ARRAY + assert array_arg_types["string_array"] == UserServiceArgType.STRING_ARRAY + + # Test CustomAPIDevice service with arrays + client.execute_service( + custom_arrays_service, + { + "bool_array": [True, False], + "int_array": [1, 2, 3], + "float_array": [1.1, 2.2], + "string_array": ["hello", "world"], + }, + ) + await asyncio.wait_for(custom_arrays_future, timeout=5.0) diff --git a/tests/integration/test_api_vv_logging.py b/tests/integration/test_api_vv_logging.py index 19aab2001c..fcbdd341ae 100644 --- a/tests/integration/test_api_vv_logging.py +++ b/tests/integration/test_api_vv_logging.py @@ -5,7 +5,7 @@ from __future__ import annotations import asyncio from typing import Any -from aioesphomeapi import LogLevel +from aioesphomeapi import LogLevel, SensorInfo import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -63,7 +63,7 @@ async def test_api_vv_logging( entity_info, _ = await client.list_entities_services() # Count sensors - sensor_count = sum(1 for e in entity_info if hasattr(e, "unit_of_measurement")) + sensor_count = sum(1 for e in entity_info if isinstance(e, SensorInfo)) assert sensor_count >= 10, f"Expected at least 10 sensors, got {sensor_count}" # Wait for sensor updates to flow with VV logging active diff --git a/tests/integration/test_areas_and_devices.py b/tests/integration/test_areas_and_devices.py index 4ce55a30a7..4184255724 100644 --- a/tests/integration/test_areas_and_devices.py +++ b/tests/integration/test_areas_and_devices.py @@ -76,8 +76,8 @@ async def test_areas_and_devices( # Get entity list to verify device_id mapping entities = await client.list_entities_services() - # Collect sensor entities - sensor_entities = [e for e in entities[0] if hasattr(e, "device_id")] + # Collect sensor entities (all entities have device_id) + sensor_entities = entities[0] assert len(sensor_entities) >= 4, ( f"Expected at least 4 sensor entities, got {len(sensor_entities)}" ) diff --git a/tests/integration/test_device_id_in_state.py b/tests/integration/test_device_id_in_state.py index 3c5181595f..eaa91ec92e 100644 --- a/tests/integration/test_device_id_in_state.py +++ b/tests/integration/test_device_id_in_state.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio -from aioesphomeapi import EntityState +from aioesphomeapi import BinarySensorState, EntityState, SensorState, TextSensorState import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -40,28 +40,22 @@ async def test_device_id_in_state( entity_device_mapping: dict[int, int] = {} for entity in all_entities: - if hasattr(entity, "name") and hasattr(entity, "key"): - if entity.name == "Temperature": - entity_device_mapping[entity.key] = device_ids[ - "Temperature Monitor" - ] - elif entity.name == "Humidity": - entity_device_mapping[entity.key] = device_ids["Humidity Monitor"] - elif entity.name == "Motion Detected": - entity_device_mapping[entity.key] = device_ids["Motion Sensor"] - elif entity.name == "Temperature Monitor Power": - entity_device_mapping[entity.key] = device_ids[ - "Temperature Monitor" - ] - elif entity.name == "Temperature Status": - entity_device_mapping[entity.key] = device_ids[ - "Temperature Monitor" - ] - elif entity.name == "Motion Light": - entity_device_mapping[entity.key] = device_ids["Motion Sensor"] - elif entity.name == "No Device Sensor": - # Entity without device_id should have device_id 0 - entity_device_mapping[entity.key] = 0 + # All entities have name and key attributes + if entity.name == "Temperature": + entity_device_mapping[entity.key] = device_ids["Temperature Monitor"] + elif entity.name == "Humidity": + entity_device_mapping[entity.key] = device_ids["Humidity Monitor"] + elif entity.name == "Motion Detected": + entity_device_mapping[entity.key] = device_ids["Motion Sensor"] + elif entity.name == "Temperature Monitor Power": + entity_device_mapping[entity.key] = device_ids["Temperature Monitor"] + elif entity.name == "Temperature Status": + entity_device_mapping[entity.key] = device_ids["Temperature Monitor"] + elif entity.name == "Motion Light": + entity_device_mapping[entity.key] = device_ids["Motion Sensor"] + elif entity.name == "No Device Sensor": + # Entity without device_id should have device_id 0 + entity_device_mapping[entity.key] = 0 assert len(entity_device_mapping) >= 6, ( f"Expected at least 6 mapped entities, got {len(entity_device_mapping)}" @@ -111,7 +105,7 @@ async def test_device_id_in_state( ( s for s in states.values() - if hasattr(s, "state") + if isinstance(s, SensorState) and isinstance(s.state, float) and s.device_id != 0 ), @@ -122,11 +116,7 @@ async def test_device_id_in_state( # Find a binary sensor state binary_sensor_state = next( - ( - s - for s in states.values() - if hasattr(s, "state") and isinstance(s.state, bool) - ), + (s for s in states.values() if isinstance(s, BinarySensorState)), None, ) assert binary_sensor_state is not None, "No binary sensor state found" @@ -136,11 +126,7 @@ async def test_device_id_in_state( # Find a text sensor state text_sensor_state = next( - ( - s - for s in states.values() - if hasattr(s, "state") and isinstance(s.state, str) - ), + (s for s in states.values() if isinstance(s, TextSensorState)), None, ) assert text_sensor_state is not None, "No text sensor state found" diff --git a/tests/integration/test_entity_icon.py b/tests/integration/test_entity_icon.py new file mode 100644 index 0000000000..aec7168165 --- /dev/null +++ b/tests/integration/test_entity_icon.py @@ -0,0 +1,91 @@ +"""Integration test for entity icons with USE_ENTITY_ICON feature.""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import EntityState +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_entity_icon( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that entities with custom icons work correctly with USE_ENTITY_ICON.""" + # Write, compile and run the ESPHome device, then connect to API + async with run_compiled(yaml_config), api_client_connected() as client: + # Get all entities + entities = await client.list_entities_services() + + # Create a map of entity names to entity info + entity_map = {entity.name: entity for entity in entities[0]} + + # Test entities with icons + icon_test_cases = [ + # (entity_name, expected_icon) + ("Sensor With Icon", "mdi:temperature-celsius"), + ("Binary Sensor With Icon", "mdi:motion-sensor"), + ("Text Sensor With Icon", "mdi:text-box"), + ("Switch With Icon", "mdi:toggle-switch"), + ("Button With Icon", "mdi:gesture-tap-button"), + ("Number With Icon", "mdi:numeric"), + ("Select With Icon", "mdi:format-list-bulleted"), + ] + + # Test entities without icons (should have empty string) + no_icon_test_cases = [ + "Sensor Without Icon", + "Binary Sensor Without Icon", + ] + + # Verify entities with icons + for entity_name, expected_icon in icon_test_cases: + assert entity_name in entity_map, ( + f"Entity '{entity_name}' not found in API response" + ) + entity = entity_map[entity_name] + + # Check icon field + assert entity.icon == expected_icon, ( + f"{entity_name}: icon mismatch - " + f"expected '{expected_icon}', got '{entity.icon}'" + ) + + # Verify entities without icons + for entity_name in no_icon_test_cases: + assert entity_name in entity_map, ( + f"Entity '{entity_name}' not found in API response" + ) + entity = entity_map[entity_name] + + # Check icon field is empty + assert entity.icon == "", ( + f"{entity_name}: icon should be empty string for entities without icons, " + f"got '{entity.icon}'" + ) + + # Subscribe to states to ensure everything works normally + states: dict[int, EntityState] = {} + state_received = asyncio.Event() + + def on_state(state: EntityState) -> None: + states[state.key] = state + state_received.set() + + client.subscribe_states(on_state) + + # Wait for states + try: + await asyncio.wait_for(state_received.wait(), timeout=5.0) + except asyncio.TimeoutError: + pytest.fail("No states received within 5 seconds") + + # Verify we received states + assert len(states) > 0, ( + "No states received - entities may not be working correctly" + ) diff --git a/tests/integration/test_host_mode_entity_fields.py b/tests/integration/test_host_mode_entity_fields.py index cf3fa6916a..b9fa3e9746 100644 --- a/tests/integration/test_host_mode_entity_fields.py +++ b/tests/integration/test_host_mode_entity_fields.py @@ -25,8 +25,8 @@ async def test_host_mode_entity_fields( # Create a map of entity names to entity info entity_map = {} for entity in entities[0]: - if hasattr(entity, "name"): - entity_map[entity.name] = entity + # All entities should have a name attribute + entity_map[entity.name] = entity # Test entities that should be visible via API (non-internal) visible_test_cases = [ diff --git a/tests/integration/test_host_mode_many_entities.py b/tests/integration/test_host_mode_many_entities.py index 005728b8c6..19d1ee315f 100644 --- a/tests/integration/test_host_mode_many_entities.py +++ b/tests/integration/test_host_mode_many_entities.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio -from aioesphomeapi import EntityState +from aioesphomeapi import EntityState, SensorState import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -30,7 +30,7 @@ async def test_host_mode_many_entities( sensor_states = [ s for s in states.values() - if hasattr(s, "state") and isinstance(s.state, float) + if isinstance(s, SensorState) and isinstance(s.state, float) ] # When we have received states from at least 50 sensors, resolve the future if len(sensor_states) >= 50 and not sensor_count_future.done(): @@ -45,7 +45,7 @@ async def test_host_mode_many_entities( sensor_states = [ s for s in states.values() - if hasattr(s, "state") and isinstance(s.state, float) + if isinstance(s, SensorState) and isinstance(s.state, float) ] pytest.fail( f"Did not receive states from at least 50 sensors within 10 seconds. " @@ -61,7 +61,7 @@ async def test_host_mode_many_entities( sensor_states = [ s for s in states.values() - if hasattr(s, "state") and isinstance(s.state, float) + if isinstance(s, SensorState) and isinstance(s.state, float) ] assert sensor_count >= 50, ( diff --git a/tests/integration/test_host_mode_sensor.py b/tests/integration/test_host_mode_sensor.py index 049f7db619..8c1e9f5d51 100644 --- a/tests/integration/test_host_mode_sensor.py +++ b/tests/integration/test_host_mode_sensor.py @@ -19,16 +19,17 @@ async def test_host_mode_with_sensor( ) -> None: """Test Host mode with a sensor component.""" # Write, compile and run the ESPHome device, then connect to API + loop = asyncio.get_running_loop() async with run_compiled(yaml_config), api_client_connected() as client: # Subscribe to state changes states: dict[int, EntityState] = {} - sensor_future: asyncio.Future[EntityState] = asyncio.Future() + sensor_future: asyncio.Future[EntityState] = loop.create_future() def on_state(state: EntityState) -> None: states[state.key] = state # If this is our sensor with value 42.0, resolve the future if ( - hasattr(state, "state") + isinstance(state, aioesphomeapi.SensorState) and state.state == 42.0 and not sensor_future.done() ): diff --git a/tests/integration/test_light_calls.py b/tests/integration/test_light_calls.py new file mode 100644 index 0000000000..1c56bbbf9e --- /dev/null +++ b/tests/integration/test_light_calls.py @@ -0,0 +1,190 @@ +"""Integration test for all light call combinations. + +Tests that LightCall handles all possible light operations correctly +including RGB, color temperature, effects, transitions, and flash. +""" + +import asyncio +from typing import Any + +from aioesphomeapi import LightState +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_light_calls( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test all possible LightCall operations and combinations.""" + async with run_compiled(yaml_config), api_client_connected() as client: + # Track state changes with futures + state_futures: dict[int, asyncio.Future[Any]] = {} + states: dict[int, Any] = {} + + def on_state(state: Any) -> None: + states[state.key] = state + if state.key in state_futures and not state_futures[state.key].done(): + state_futures[state.key].set_result(state) + + client.subscribe_states(on_state) + + # Get the light entities + entities = await client.list_entities_services() + lights = [e for e in entities[0] if e.object_id.startswith("test_")] + assert len(lights) >= 2 # Should have RGBCW and RGB lights + + rgbcw_light = next(light for light in lights if "RGBCW" in light.name) + rgb_light = next(light for light in lights if "RGB Light" in light.name) + + async def wait_for_state_change(key: int, timeout: float = 1.0) -> Any: + """Wait for a state change for the given entity key.""" + loop = asyncio.get_event_loop() + state_futures[key] = loop.create_future() + try: + return await asyncio.wait_for(state_futures[key], timeout) + finally: + state_futures.pop(key, None) + + # Test all individual parameters first + + # Test 1: state only + client.light_command(key=rgbcw_light.key, state=True) + state = await wait_for_state_change(rgbcw_light.key) + assert state.state is True + + # Test 2: brightness only + client.light_command(key=rgbcw_light.key, brightness=0.5) + state = await wait_for_state_change(rgbcw_light.key) + assert state.brightness == pytest.approx(0.5) + + # Test 3: color_brightness only + client.light_command(key=rgbcw_light.key, color_brightness=0.8) + state = await wait_for_state_change(rgbcw_light.key) + assert state.color_brightness == pytest.approx(0.8) + + # Test 4-7: RGB values must be set together via rgb parameter + client.light_command(key=rgbcw_light.key, rgb=(0.7, 0.3, 0.9)) + state = await wait_for_state_change(rgbcw_light.key) + assert state.red == pytest.approx(0.7, abs=0.1) + assert state.green == pytest.approx(0.3, abs=0.1) + assert state.blue == pytest.approx(0.9, abs=0.1) + + # Test 7: white value + client.light_command(key=rgbcw_light.key, white=0.6) + state = await wait_for_state_change(rgbcw_light.key) + # White might need more tolerance or might not be directly settable + if isinstance(state, LightState) and state.white is not None: + assert state.white == pytest.approx(0.6, abs=0.1) + + # Test 8: color_temperature only + client.light_command(key=rgbcw_light.key, color_temperature=300) + state = await wait_for_state_change(rgbcw_light.key) + assert state.color_temperature == pytest.approx(300) + + # Test 9: cold_white only + client.light_command(key=rgbcw_light.key, cold_white=0.8) + state = await wait_for_state_change(rgbcw_light.key) + assert state.cold_white == pytest.approx(0.8) + + # Test 10: warm_white only + client.light_command(key=rgbcw_light.key, warm_white=0.2) + state = await wait_for_state_change(rgbcw_light.key) + assert state.warm_white == pytest.approx(0.2) + + # Test 11: transition_length with state change + client.light_command(key=rgbcw_light.key, state=False, transition_length=0.1) + state = await wait_for_state_change(rgbcw_light.key) + assert state.state is False + + # Test 12: flash_length + client.light_command(key=rgbcw_light.key, state=True, flash_length=0.2) + state = await wait_for_state_change(rgbcw_light.key) + # Flash starts + assert state.state is True + # Wait for flash to end + state = await wait_for_state_change(rgbcw_light.key) + + # Test 13: effect only + # First ensure light is on + client.light_command(key=rgbcw_light.key, state=True) + state = await wait_for_state_change(rgbcw_light.key) + # Now set effect + client.light_command(key=rgbcw_light.key, effect="Random Effect") + state = await wait_for_state_change(rgbcw_light.key) + assert state.effect == "Random Effect" + + # Test 14: stop effect + client.light_command(key=rgbcw_light.key, effect="None") + state = await wait_for_state_change(rgbcw_light.key) + assert state.effect == "None" + + # Test 15: color_mode parameter + client.light_command( + key=rgbcw_light.key, state=True, color_mode=5 + ) # COLD_WARM_WHITE + state = await wait_for_state_change(rgbcw_light.key) + assert state.state is True + + # Now test common combinations + + # Test 16: RGB combination (set_rgb) - RGB values get normalized + client.light_command(key=rgbcw_light.key, rgb=(1.0, 0.0, 0.5)) + state = await wait_for_state_change(rgbcw_light.key) + # RGB values get normalized - in this case red is already 1.0 + assert state.red == pytest.approx(1.0, abs=0.1) + assert state.green == pytest.approx(0.0, abs=0.1) + assert state.blue == pytest.approx(0.5, abs=0.1) + + # Test 17: Multiple RGB changes to test transitions + client.light_command(key=rgbcw_light.key, rgb=(0.2, 0.8, 0.4)) + state = await wait_for_state_change(rgbcw_light.key) + # RGB values get normalized so green (highest) becomes 1.0 + # Expected: (0.2/0.8, 0.8/0.8, 0.4/0.8) = (0.25, 1.0, 0.5) + assert state.red == pytest.approx(0.25, abs=0.01) + assert state.green == pytest.approx(1.0, abs=0.01) + assert state.blue == pytest.approx(0.5, abs=0.01) + + # Test 18: State + brightness + transition + client.light_command( + key=rgbcw_light.key, state=True, brightness=0.7, transition_length=0.1 + ) + state = await wait_for_state_change(rgbcw_light.key) + assert state.state is True + assert state.brightness == pytest.approx(0.7) + + # Test 19: RGB + brightness + color_brightness + client.light_command( + key=rgb_light.key, + state=True, + brightness=0.8, + color_brightness=0.9, + rgb=(0.2, 0.4, 0.6), + ) + state = await wait_for_state_change(rgb_light.key) + assert state.state is True + assert state.brightness == pytest.approx(0.8) + + # Test 20: Color temp + cold/warm white + client.light_command( + key=rgbcw_light.key, color_temperature=250, cold_white=0.7, warm_white=0.3 + ) + state = await wait_for_state_change(rgbcw_light.key) + assert state.color_temperature == pytest.approx(250) + + # Test 21: Turn RGB light off + client.light_command(key=rgb_light.key, state=False) + state = await wait_for_state_change(rgb_light.key) + assert state.state is False + + # Final cleanup - turn all lights off + for light in lights: + client.light_command( + key=light.key, + state=False, + ) + state = await wait_for_state_change(light.key) + assert state.state is False diff --git a/tests/integration/test_scheduler_bulk_cleanup.py b/tests/integration/test_scheduler_bulk_cleanup.py new file mode 100644 index 0000000000..08ff293b84 --- /dev/null +++ b/tests/integration/test_scheduler_bulk_cleanup.py @@ -0,0 +1,122 @@ +"""Test that triggers the bulk cleanup path when to_remove_ > MAX_LOGICALLY_DELETED_ITEMS.""" + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_bulk_cleanup( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that bulk cleanup path is triggered when many items are cancelled.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_event_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + bulk_cleanup_triggered = False + cleanup_stats: dict[str, int] = { + "removed": 0, + "before": 0, + "after": 0, + } + post_cleanup_executed = 0 + + def on_log_line(line: str) -> None: + nonlocal bulk_cleanup_triggered, post_cleanup_executed + + # Look for logs indicating bulk cleanup was triggered + # The actual cleanup happens silently, so we track the cancel operations + if "Successfully cancelled" in line and "timeouts" in line: + match = re.search(r"Successfully cancelled (\d+) timeouts", line) + if match and int(match.group(1)) > 10: + bulk_cleanup_triggered = True + + # Track cleanup statistics + match = re.search(r"Bulk cleanup triggered: removed (\d+) items", line) + if match: + cleanup_stats["removed"] = int(match.group(1)) + + match = re.search(r"Items before cleanup: (\d+), after: (\d+)", line) + if match: + cleanup_stats["before"] = int(match.group(1)) + cleanup_stats["after"] = int(match.group(2)) + + # Track post-cleanup timeout executions + if "Post-cleanup timeout" in line and "executed correctly" in line: + match = re.search(r"Post-cleanup timeout (\d+) executed correctly", line) + if match: + post_cleanup_executed += 1 + + # Check for final test completion + if ( + "All post-cleanup timeouts completed - test finished" in line + and not test_complete_future.done() + ): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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-bulk-cleanup" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + trigger_bulk_cleanup_service: UserService | None = None + for service in services: + if service.name == "trigger_bulk_cleanup": + trigger_bulk_cleanup_service = service + break + + assert trigger_bulk_cleanup_service is not None, ( + "trigger_bulk_cleanup service not found" + ) + + # Execute the test + client.execute_service(trigger_bulk_cleanup_service, {}) + + # Wait for test completion + try: + await asyncio.wait_for(test_complete_future, timeout=10.0) + except asyncio.TimeoutError: + pytest.fail("Bulk cleanup test timed out") + + # Verify bulk cleanup was triggered + assert bulk_cleanup_triggered, ( + "Bulk cleanup path was not triggered - MAX_LOGICALLY_DELETED_ITEMS threshold not reached" + ) + + # Verify cleanup statistics + assert cleanup_stats["removed"] > 10, ( + f"Expected more than 10 items removed, got {cleanup_stats['removed']}" + ) + + # Verify scheduler still works after bulk cleanup + assert post_cleanup_executed == 5, ( + f"Expected 5 post-cleanup timeouts to execute, but {post_cleanup_executed} executed" + ) diff --git a/tests/integration/test_scheduler_defer_cancel.py b/tests/integration/test_scheduler_defer_cancel.py new file mode 100644 index 0000000000..923cf946c4 --- /dev/null +++ b/tests/integration/test_scheduler_defer_cancel.py @@ -0,0 +1,94 @@ +"""Test that defer() with the same name cancels previous defers.""" + +import asyncio + +from aioesphomeapi import EntityState, Event, EventInfo, UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_defer_cancel( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that defer() with the same name cancels previous defers.""" + + async with run_compiled(yaml_config), 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-defer-cancel" + + # List entities and services + entity_info, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test entities + test_complete_entity: EventInfo | None = None + test_result_entity: EventInfo | None = None + + for entity in entity_info: + if isinstance(entity, EventInfo): + if entity.object_id == "test_complete": + test_complete_entity = entity + elif entity.object_id == "test_result": + test_result_entity = entity + + assert test_complete_entity is not None, "test_complete event not found" + assert test_result_entity is not None, "test_result event not found" + + # Find our test service + test_defer_cancel_service: UserService | None = None + for service in services: + if service.name == "test_defer_cancel": + test_defer_cancel_service = service + + assert test_defer_cancel_service is not None, ( + "test_defer_cancel service not found" + ) + + # Get the event loop + loop = asyncio.get_running_loop() + + # Subscribe to states + test_complete_future: asyncio.Future[bool] = loop.create_future() + test_result_future: asyncio.Future[int] = loop.create_future() + + def on_state(state: EntityState) -> None: + if not isinstance(state, Event): + return + + if ( + state.key == test_complete_entity.key + and state.event_type == "test_finished" + and not test_complete_future.done() + ): + test_complete_future.set_result(True) + return + + if state.key == test_result_entity.key and not test_result_future.done(): + # Event type should be "defer_executed_X" where X is the defer number + if state.event_type.startswith("defer_executed_"): + defer_num = int(state.event_type.split("_")[-1]) + test_result_future.set_result(defer_num) + + client.subscribe_states(on_state) + + # Execute the test + client.execute_service(test_defer_cancel_service, {}) + + # Wait for test completion + try: + await asyncio.wait_for(test_complete_future, timeout=10.0) + executed_defer = await asyncio.wait_for(test_result_future, timeout=1.0) + except asyncio.TimeoutError: + pytest.fail("Test did not complete within timeout") + + # Verify that only defer 10 was executed + assert executed_defer == 10, ( + f"Expected defer 10 to execute, got {executed_defer}" + ) diff --git a/tests/integration/test_scheduler_defer_cancel_regular.py b/tests/integration/test_scheduler_defer_cancel_regular.py new file mode 100644 index 0000000000..57b7134feb --- /dev/null +++ b/tests/integration/test_scheduler_defer_cancel_regular.py @@ -0,0 +1,90 @@ +"""Test that a deferred timeout cancels a regular timeout with the same name.""" + +import asyncio + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_defer_cancels_regular( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that set_timeout(name, 0) cancels a previously scheduled set_timeout(name, delay).""" + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track log messages + log_messages: list[str] = [] + error_detected = False + + def on_log_line(line: str) -> None: + nonlocal error_detected + if "TEST" in line: + log_messages.append(line) + + if "ERROR: Regular timeout executed" in line: + error_detected = True + + if "Test complete" in line and not test_complete_future.done(): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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-defer-cancel-regular" + + # List services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + test_service: UserService | None = None + for service in services: + if service.name == "test_defer_cancels_regular": + test_service = service + break + + assert test_service is not None, "test_defer_cancels_regular service not found" + + # Execute the test + client.execute_service(test_service, {}) + + # Wait for test completion + try: + await asyncio.wait_for(test_complete_future, timeout=5.0) + except asyncio.TimeoutError: + pytest.fail(f"Test timed out. Log messages: {log_messages}") + + # Verify results + assert not error_detected, ( + f"Regular timeout should have been cancelled but it executed! Logs: {log_messages}" + ) + + # Verify the deferred timeout executed + assert any( + "SUCCESS: Deferred timeout executed" in msg for msg in log_messages + ), f"Deferred timeout should have executed. Logs: {log_messages}" + + # Verify the expected sequence of events + assert any( + "Starting defer cancels regular timeout test" in msg for msg in log_messages + ) + assert any( + "Scheduled regular timeout with 100ms delay" in msg for msg in log_messages + ) + assert any( + "Scheduled deferred timeout - should cancel regular timeout" in msg + for msg in log_messages + ) diff --git a/tests/integration/test_defer_fifo_simple.py b/tests/integration/test_scheduler_defer_fifo_simple.py similarity index 97% rename from tests/integration/test_defer_fifo_simple.py rename to tests/integration/test_scheduler_defer_fifo_simple.py index 5a62a45786..eb4058fedd 100644 --- a/tests/integration/test_defer_fifo_simple.py +++ b/tests/integration/test_scheduler_defer_fifo_simple.py @@ -9,7 +9,7 @@ from .types import APIClientConnectedFactory, RunCompiledFunction @pytest.mark.asyncio -async def test_defer_fifo_simple( +async def test_scheduler_defer_fifo_simple( yaml_config: str, run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, @@ -20,7 +20,7 @@ async def test_defer_fifo_simple( # Verify we can connect device_info = await client.device_info() assert device_info is not None - assert device_info.name == "defer-fifo-simple" + assert device_info.name == "scheduler-defer-fifo-simple" # List entities and services entity_info, services = await asyncio.wait_for( diff --git a/tests/integration/test_defer_stress.py b/tests/integration/test_scheduler_defer_stress.py similarity index 97% rename from tests/integration/test_defer_stress.py rename to tests/integration/test_scheduler_defer_stress.py index f63ec8d25f..d546b7132f 100644 --- a/tests/integration/test_defer_stress.py +++ b/tests/integration/test_scheduler_defer_stress.py @@ -11,7 +11,7 @@ from .types import APIClientConnectedFactory, RunCompiledFunction @pytest.mark.asyncio -async def test_defer_stress( +async def test_scheduler_defer_stress( yaml_config: str, run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, @@ -75,7 +75,7 @@ async def test_defer_stress( # Verify we can connect device_info = await client.device_info() assert device_info is not None - assert device_info.name == "defer-stress-test" + assert device_info.name == "scheduler-defer-stress-test" # List entities and services entity_info, services = await asyncio.wait_for( diff --git a/tests/integration/test_scheduler_heap_stress.py b/tests/integration/test_scheduler_heap_stress.py new file mode 100644 index 0000000000..3c757bfc9d --- /dev/null +++ b/tests/integration/test_scheduler_heap_stress.py @@ -0,0 +1,140 @@ +"""Stress test for heap scheduler thread safety with multiple threads.""" + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_heap_stress( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that set_timeout/set_interval doesn't crash when called rapidly from multiple threads.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track executed timeouts/intervals and their order + executed_callbacks: set[int] = set() + thread_executions: dict[ + int, list[int] + ] = {} # thread_id -> list of indices in execution order + callback_types: dict[int, str] = {} # callback_id -> "timeout" or "interval" + + def on_log_line(line: str) -> None: + # Track all executed callbacks with thread and index info + match = re.search( + r"Executed (timeout|interval) (\d+) \(thread (\d+), index (\d+)\)", line + ) + if not match: + # Also check for the completion message + if "All threads finished" in line and "Created 1000 callbacks" in line: + # Give scheduler some time to execute callbacks + pass + return + + callback_type = match.group(1) + callback_id = int(match.group(2)) + thread_id = int(match.group(3)) + index = int(match.group(4)) + + # Only count each callback ID once (intervals might fire multiple times) + if callback_id not in executed_callbacks: + executed_callbacks.add(callback_id) + callback_types[callback_id] = callback_type + + # Track execution order per thread + if thread_id not in thread_executions: + thread_executions[thread_id] = [] + + # Only append if this is a new execution for this thread + if index not in thread_executions[thread_id]: + thread_executions[thread_id].append(index) + + # Check if we've executed all 1000 callbacks (0-999) + if len(executed_callbacks) >= 1000 and not test_complete_future.done(): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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-heap-stress-test" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + run_stress_test_service: UserService | None = None + for service in services: + if service.name == "run_heap_stress_test": + run_stress_test_service = service + break + + assert run_stress_test_service is not None, ( + "run_heap_stress_test service not found" + ) + + # Call the run_heap_stress_test service to start the test + client.execute_service(run_stress_test_service, {}) + + # Wait for all callbacks to execute (should be quick, but give more time for scheduling) + try: + await asyncio.wait_for(test_complete_future, timeout=60.0) + except asyncio.TimeoutError: + # Report how many we got + pytest.fail( + f"Stress test timed out. Only {len(executed_callbacks)} of " + f"1000 callbacks executed. Missing IDs: " + f"{sorted(set(range(1000)) - executed_callbacks)[:10]}..." + ) + + # Verify all callbacks executed + assert len(executed_callbacks) == 1000, ( + f"Expected 1000 callbacks, got {len(executed_callbacks)}" + ) + + # Verify we have all IDs from 0-999 + expected_ids = set(range(1000)) + missing_ids = expected_ids - executed_callbacks + assert not missing_ids, f"Missing callback IDs: {sorted(missing_ids)}" + + # Verify we have a mix of timeouts and intervals + timeout_count = sum(1 for t in callback_types.values() if t == "timeout") + interval_count = sum(1 for t in callback_types.values() if t == "interval") + assert timeout_count > 0, "No timeouts were executed" + assert interval_count > 0, "No intervals were executed" + + # Verify each thread executed callbacks + for thread_id, indices in thread_executions.items(): + assert len(indices) == 100, ( + f"Thread {thread_id} executed {len(indices)} callbacks, expected 100" + ) + # Total should be 1000 callbacks + total_callbacks = timeout_count + interval_count + assert total_callbacks == 1000, ( + f"Expected 1000 total callbacks but got {total_callbacks}" + ) diff --git a/tests/integration/test_scheduler_null_name.py b/tests/integration/test_scheduler_null_name.py new file mode 100644 index 0000000000..41bcd8aed7 --- /dev/null +++ b/tests/integration/test_scheduler_null_name.py @@ -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" + ) diff --git a/tests/integration/test_scheduler_rapid_cancellation.py b/tests/integration/test_scheduler_rapid_cancellation.py new file mode 100644 index 0000000000..90577f36f1 --- /dev/null +++ b/tests/integration/test_scheduler_rapid_cancellation.py @@ -0,0 +1,142 @@ +"""Rapid cancellation test - schedule and immediately cancel timeouts with string names.""" + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_rapid_cancellation( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test rapid schedule/cancel cycles that might expose race conditions.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track test progress + test_stats = { + "log_count": 0, + "errors": [], + "summary_scheduled": None, + "final_scheduled": 0, + "final_executed": 0, + "final_implicit_cancellations": 0, + } + + def on_log_line(line: str) -> None: + # Count log lines + test_stats["log_count"] += 1 + + # Check for errors (only ERROR level, not WARN) + if "ERROR" in line: + test_stats["errors"].append(line) + + # Parse summary statistics + if "All threads completed. Scheduled:" in line: + # Extract the scheduled count from the summary + if match := re.search(r"Scheduled: (\d+)", line): + test_stats["summary_scheduled"] = int(match.group(1)) + elif "Total scheduled:" in line: + if match := re.search(r"Total scheduled: (\d+)", line): + test_stats["final_scheduled"] = int(match.group(1)) + elif "Total executed:" in line: + if match := re.search(r"Total executed: (\d+)", line): + test_stats["final_executed"] = int(match.group(1)) + elif "Implicit cancellations (replaced):" in line: + if match := re.search(r"Implicit cancellations \(replaced\): (\d+)", line): + test_stats["final_implicit_cancellations"] = int(match.group(1)) + + # Check for crash indicators + if any( + indicator in line.lower() + for indicator in ["segfault", "abort", "assertion", "heap corruption"] + ): + if not test_complete_future.done(): + test_complete_future.set_exception(Exception(f"Crash detected: {line}")) + return + + # Check for completion - wait for final message after all stats are logged + if ( + "Test finished - all statistics reported" in line + and not test_complete_future.done() + ): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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 == "sched-rapid-cancel-test" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + run_test_service: UserService | None = None + for service in services: + if service.name == "run_rapid_cancellation_test": + run_test_service = service + break + + assert run_test_service is not None, ( + "run_rapid_cancellation_test service not found" + ) + + # Call the service to start the test + client.execute_service(run_test_service, {}) + + # Wait for test to complete with timeout + try: + await asyncio.wait_for(test_complete_future, timeout=10.0) + except asyncio.TimeoutError: + pytest.fail(f"Test timed out. Stats: {test_stats}") + + # Check for any errors + assert len(test_stats["errors"]) == 0, ( + f"Errors detected: {test_stats['errors']}" + ) + + # Check that we received log messages + assert test_stats["log_count"] > 0, "No log messages received" + + # Check the summary line to verify all threads scheduled their operations + assert test_stats["summary_scheduled"] == 400, ( + f"Expected summary to show 400 scheduled operations but got {test_stats['summary_scheduled']}" + ) + + # Check final statistics + assert test_stats["final_scheduled"] == 400, ( + f"Expected final stats to show 400 scheduled but got {test_stats['final_scheduled']}" + ) + + assert test_stats["final_executed"] == 10, ( + f"Expected final stats to show 10 executed but got {test_stats['final_executed']}" + ) + + assert test_stats["final_implicit_cancellations"] == 390, ( + f"Expected final stats to show 390 implicit cancellations but got {test_stats['final_implicit_cancellations']}" + ) diff --git a/tests/integration/test_scheduler_recursive_timeout.py b/tests/integration/test_scheduler_recursive_timeout.py new file mode 100644 index 0000000000..c015978e15 --- /dev/null +++ b/tests/integration/test_scheduler_recursive_timeout.py @@ -0,0 +1,101 @@ +"""Test for recursive timeout scheduling - scheduling timeouts from within timeout callbacks.""" + +import asyncio +from pathlib import Path + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_recursive_timeout( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that scheduling timeouts from within timeout callbacks works correctly.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track execution sequence + execution_sequence: list[str] = [] + expected_sequence = [ + "initial_timeout", + "nested_timeout_1", + "nested_timeout_2", + "test_complete", + ] + + def on_log_line(line: str) -> None: + # Track execution sequence + if "Executing initial timeout" in line: + execution_sequence.append("initial_timeout") + elif "Executing nested timeout 1" in line: + execution_sequence.append("nested_timeout_1") + elif "Executing nested timeout 2" in line: + execution_sequence.append("nested_timeout_2") + elif "Recursive timeout test complete" in line: + execution_sequence.append("test_complete") + if not test_complete_future.done(): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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 == "sched-recursive-timeout" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + run_test_service: UserService | None = None + for service in services: + if service.name == "run_recursive_timeout_test": + run_test_service = service + break + + assert run_test_service is not None, ( + "run_recursive_timeout_test service not found" + ) + + # Call the service to start the test + client.execute_service(run_test_service, {}) + + # Wait for test to complete + try: + await asyncio.wait_for(test_complete_future, timeout=10.0) + except asyncio.TimeoutError: + pytest.fail( + f"Recursive timeout test timed out. Got sequence: {execution_sequence}" + ) + + # Verify execution sequence + assert execution_sequence == expected_sequence, ( + f"Execution sequence mismatch. Expected {expected_sequence}, " + f"got {execution_sequence}" + ) + + # Verify we got exactly 4 events (Initial + Level 1 + Level 2 + Complete) + assert len(execution_sequence) == 4, ( + f"Expected 4 events but got {len(execution_sequence)}" + ) diff --git a/tests/integration/test_scheduler_simultaneous_callbacks.py b/tests/integration/test_scheduler_simultaneous_callbacks.py new file mode 100644 index 0000000000..f5120ce4ce --- /dev/null +++ b/tests/integration/test_scheduler_simultaneous_callbacks.py @@ -0,0 +1,123 @@ +"""Simultaneous callbacks test - schedule many callbacks for the same time from multiple threads.""" + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_simultaneous_callbacks( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test scheduling many callbacks for the exact same time from multiple threads.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track test progress + test_stats = { + "scheduled": 0, + "executed": 0, + "expected": 1000, # 10 threads * 100 callbacks + "errors": [], + } + + def on_log_line(line: str) -> None: + # Track operations + if "Scheduled callback" in line: + test_stats["scheduled"] += 1 + elif "Callback executed" in line: + test_stats["executed"] += 1 + elif "ERROR" in line: + test_stats["errors"].append(line) + + # Check for crash indicators + if any( + indicator in line.lower() + for indicator in ["segfault", "abort", "assertion", "heap corruption"] + ): + if not test_complete_future.done(): + test_complete_future.set_exception(Exception(f"Crash detected: {line}")) + return + + # Check for completion with final count + if "Final executed count:" in line: + # Extract number from log line like: "[07:59:47][I][simultaneous_callbacks:093]: Simultaneous callbacks test complete. Final executed count: 1000" + match = re.search(r"Final executed count:\s*(\d+)", line) + if match: + test_stats["final_count"] = int(match.group(1)) + + # Check for completion + if ( + "Simultaneous callbacks test complete" in line + and not test_complete_future.done() + ): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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 == "sched-simul-callbacks-test" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + run_test_service: UserService | None = None + for service in services: + if service.name == "run_simultaneous_callbacks_test": + run_test_service = service + break + + assert run_test_service is not None, ( + "run_simultaneous_callbacks_test service not found" + ) + + # Call the service to start the test + client.execute_service(run_test_service, {}) + + # Wait for test to complete + try: + await asyncio.wait_for(test_complete_future, timeout=30.0) + except asyncio.TimeoutError: + pytest.fail(f"Simultaneous callbacks test timed out. Stats: {test_stats}") + + # Check for any errors + assert len(test_stats["errors"]) == 0, ( + f"Errors detected: {test_stats['errors']}" + ) + + # Verify all callbacks executed using the final count from C++ + final_count = test_stats.get("final_count", 0) + assert final_count == test_stats["expected"], ( + f"Expected {test_stats['expected']} callbacks, but only {final_count} executed" + ) + + # The final_count is the authoritative count from the C++ component + assert final_count == 1000, ( + f"Expected 1000 executed callbacks but got {final_count}" + ) diff --git a/tests/integration/test_scheduler_string_lifetime.py b/tests/integration/test_scheduler_string_lifetime.py new file mode 100644 index 0000000000..4d77abd954 --- /dev/null +++ b/tests/integration/test_scheduler_string_lifetime.py @@ -0,0 +1,169 @@ +"""String lifetime test - verify scheduler handles string destruction correctly.""" + +import asyncio +from pathlib import Path +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_string_lifetime( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that scheduler correctly handles string lifetimes when strings go out of scope.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create events for synchronization + test1_complete = asyncio.Event() + test2_complete = asyncio.Event() + test3_complete = asyncio.Event() + test4_complete = asyncio.Event() + test5_complete = asyncio.Event() + all_tests_complete = asyncio.Event() + + # Track test progress + test_stats = { + "tests_passed": 0, + "tests_failed": 0, + "errors": [], + "current_test": None, + "test_callbacks_executed": {}, + } + + def on_log_line(line: str) -> None: + # Track test-specific events + if "Test 1 complete" in line: + test1_complete.set() + elif "Test 2 complete" in line: + test2_complete.set() + elif "Test 3 complete" in line: + test3_complete.set() + elif "Test 4 complete" in line: + test4_complete.set() + elif "Test 5 complete" in line: + test5_complete.set() + + # Track individual callback executions + callback_match = re.search(r"Callback '(.+?)' executed", line) + if callback_match: + callback_name = callback_match.group(1) + test_stats["test_callbacks_executed"][callback_name] = True + + # Track test results from the C++ test output + if "Tests passed:" in line and "string_lifetime" in line: + # Extract the number from "Tests passed: 32" + match = re.search(r"Tests passed:\s*(\d+)", line) + if match: + test_stats["tests_passed"] = int(match.group(1)) + elif "Tests failed:" in line and "string_lifetime" in line: + match = re.search(r"Tests failed:\s*(\d+)", line) + if match: + test_stats["tests_failed"] = int(match.group(1)) + elif "ERROR" in line and "string_lifetime" in line: + test_stats["errors"].append(line) + + # Check for memory corruption indicators + if any( + indicator in line.lower() + for indicator in [ + "use after free", + "heap corruption", + "segfault", + "abort", + "assertion", + "sanitizer", + "bad memory", + "invalid pointer", + ] + ): + pytest.fail(f"Memory corruption detected: {line}") + + # Check for completion + if "String lifetime tests complete" in line: + all_tests_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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-string-lifetime-test" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test services + test_services = {} + for service in services: + if service.name == "run_test1": + test_services["test1"] = service + elif service.name == "run_test2": + test_services["test2"] = service + elif service.name == "run_test3": + test_services["test3"] = service + elif service.name == "run_test4": + test_services["test4"] = service + elif service.name == "run_test5": + test_services["test5"] = service + elif service.name == "run_final_check": + test_services["final"] = service + + # Ensure all services are found + required_services = ["test1", "test2", "test3", "test4", "test5", "final"] + for service_name in required_services: + assert service_name in test_services, f"{service_name} service not found" + + # Run tests sequentially, waiting for each to complete + try: + # Test 1 + client.execute_service(test_services["test1"], {}) + await asyncio.wait_for(test1_complete.wait(), timeout=5.0) + + # Test 2 + client.execute_service(test_services["test2"], {}) + await asyncio.wait_for(test2_complete.wait(), timeout=5.0) + + # Test 3 + client.execute_service(test_services["test3"], {}) + await asyncio.wait_for(test3_complete.wait(), timeout=5.0) + + # Test 4 + client.execute_service(test_services["test4"], {}) + await asyncio.wait_for(test4_complete.wait(), timeout=5.0) + + # Test 5 + client.execute_service(test_services["test5"], {}) + await asyncio.wait_for(test5_complete.wait(), timeout=5.0) + + # Final check + client.execute_service(test_services["final"], {}) + await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0) + + except asyncio.TimeoutError: + pytest.fail(f"String lifetime test timed out. Stats: {test_stats}") + + # Check for any errors + assert test_stats["tests_failed"] == 0, f"Tests failed: {test_stats['errors']}" + + # Verify we had the expected number of passing tests + assert test_stats["tests_passed"] == 30, ( + f"Expected exactly 30 tests to pass, but got {test_stats['tests_passed']}" + ) diff --git a/tests/integration/test_scheduler_string_name_stress.py b/tests/integration/test_scheduler_string_name_stress.py new file mode 100644 index 0000000000..3045842223 --- /dev/null +++ b/tests/integration/test_scheduler_string_name_stress.py @@ -0,0 +1,116 @@ +"""Stress test for heap scheduler with std::string names from multiple threads.""" + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import UserService +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_string_name_stress( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that set_timeout/set_interval with std::string names doesn't crash when called from multiple threads.""" + + # Get the absolute path to the external components directory + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + + # Replace the placeholder in the YAML config with the actual path + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + # Create a future to signal test completion + loop = asyncio.get_running_loop() + test_complete_future: asyncio.Future[None] = loop.create_future() + + # Track executed callbacks and any crashes + executed_callbacks: set[int] = set() + error_messages: list[str] = [] + + def on_log_line(line: str) -> None: + # Check for crash indicators + if any( + indicator in line.lower() + for indicator in [ + "segfault", + "abort", + "assertion", + "heap corruption", + "use after free", + ] + ): + error_messages.append(line) + if not test_complete_future.done(): + test_complete_future.set_exception(Exception(f"Crash detected: {line}")) + return + + # Track executed callbacks + match = re.search(r"Executed string-named callback (\d+)", line) + if match: + callback_id = int(match.group(1)) + executed_callbacks.add(callback_id) + + # Check for completion + if ( + "String name stress test complete" in line + and not test_complete_future.done() + ): + test_complete_future.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + 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 == "sched-string-name-stress" + + # List entities and services + _, services = await asyncio.wait_for( + client.list_entities_services(), timeout=5.0 + ) + + # Find our test service + run_stress_test_service: UserService | None = None + for service in services: + if service.name == "run_string_name_stress_test": + run_stress_test_service = service + break + + assert run_stress_test_service is not None, ( + "run_string_name_stress_test service not found" + ) + + # Call the service to start the test + client.execute_service(run_stress_test_service, {}) + + # Wait for test to complete or crash + try: + await asyncio.wait_for(test_complete_future, timeout=30.0) + except asyncio.TimeoutError: + pytest.fail( + f"String name stress test timed out. Executed {len(executed_callbacks)} callbacks. " + f"This might indicate a deadlock." + ) + + # Verify no errors occurred (crashes already handled by exception) + assert not error_messages, f"Errors detected during test: {error_messages}" + + # Verify we executed all 1000 callbacks (10 threads × 100 callbacks each) + assert len(executed_callbacks) == 1000, ( + f"Expected 1000 callbacks but got {len(executed_callbacks)}" + ) + + # Verify each callback ID was executed exactly once + for i in range(1000): + assert i in executed_callbacks, f"Callback {i} was not executed" diff --git a/tests/script/__init__.py b/tests/script/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/script/test_clang_tidy_hash.py b/tests/script/test_clang_tidy_hash.py new file mode 100644 index 0000000000..dbcb477a4f --- /dev/null +++ b/tests/script/test_clang_tidy_hash.py @@ -0,0 +1,359 @@ +"""Unit tests for script/clang_tidy_hash.py module.""" + +import hashlib +from pathlib import Path +import sys +from unittest.mock import Mock, patch + +import pytest + +# Add the script directory to Python path so we can import clang_tidy_hash +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "script")) + +import clang_tidy_hash # noqa: E402 + + +@pytest.mark.parametrize( + ("file_content", "expected"), + [ + ( + "clang-tidy==18.1.5 # via -r requirements_dev.in\n", + "clang-tidy==18.1.5 # via -r requirements_dev.in", + ), + ( + "other-package==1.0\nclang-tidy==17.0.0\nmore-packages==2.0\n", + "clang-tidy==17.0.0", + ), + ( + "# comment\nclang-tidy==16.0.0 # some comment\n", + "clang-tidy==16.0.0 # some comment", + ), + ("no-clang-tidy-here==1.0\n", "clang-tidy version not found"), + ], +) +def test_get_clang_tidy_version_from_requirements( + file_content: str, expected: str +) -> None: + """Test extracting clang-tidy version from various file formats.""" + # Mock read_file_lines to return our test content + with patch("clang_tidy_hash.read_file_lines") as mock_read: + mock_read.return_value = file_content.splitlines(keepends=True) + + result = clang_tidy_hash.get_clang_tidy_version_from_requirements() + + assert result == expected + + +@pytest.mark.parametrize( + ("platformio_content", "expected_flags"), + [ + ( + "[env:esp32]\n" + "platform = espressif32\n" + "\n" + "[flags:clangtidy]\n" + "build_flags = -Wall\n" + "extra_flags = -Wextra\n" + "\n" + "[env:esp8266]\n", + "build_flags = -Wall\nextra_flags = -Wextra", + ), + ( + "[flags:clangtidy]\n# Comment line\nbuild_flags = -O2\n\n[next_section]\n", + "build_flags = -O2", + ), + ( + "[flags:clangtidy]\nflag_c = -std=c99\nflag_b = -Wall\nflag_a = -O2\n", + "flag_a = -O2\nflag_b = -Wall\nflag_c = -std=c99", # Sorted + ), + ( + "[env:esp32]\nplatform = espressif32\n", # No clangtidy section + "", + ), + ], +) +def test_extract_platformio_flags(platformio_content: str, expected_flags: str) -> None: + """Test extracting clang-tidy flags from platformio.ini.""" + # Mock read_file_lines to return our test content + with patch("clang_tidy_hash.read_file_lines") as mock_read: + mock_read.return_value = platformio_content.splitlines(keepends=True) + + result = clang_tidy_hash.extract_platformio_flags() + + assert result == expected_flags + + +def test_calculate_clang_tidy_hash() -> None: + """Test calculating hash from all configuration sources.""" + clang_tidy_content = b"Checks: '-*,readability-*'\n" + requirements_version = "clang-tidy==18.1.5" + pio_flags = "build_flags = -Wall" + + # Expected hash calculation + expected_hasher = hashlib.sha256() + expected_hasher.update(clang_tidy_content) + expected_hasher.update(requirements_version.encode()) + expected_hasher.update(pio_flags.encode()) + expected_hash = expected_hasher.hexdigest() + + # Mock the dependencies + with ( + patch("clang_tidy_hash.read_file_bytes", return_value=clang_tidy_content), + patch( + "clang_tidy_hash.get_clang_tidy_version_from_requirements", + return_value=requirements_version, + ), + patch("clang_tidy_hash.extract_platformio_flags", return_value=pio_flags), + ): + result = clang_tidy_hash.calculate_clang_tidy_hash() + + assert result == expected_hash + + +def test_read_stored_hash_exists(tmp_path: Path) -> None: + """Test reading hash when file exists.""" + stored_hash = "abc123def456" + hash_file = tmp_path / ".clang-tidy.hash" + hash_file.write_text(f"{stored_hash}\n") + + with ( + patch("clang_tidy_hash.Path") as mock_path_class, + patch("clang_tidy_hash.read_file_lines", return_value=[f"{stored_hash}\n"]), + ): + # Mock the path calculation and exists check + mock_hash_file = Mock() + mock_hash_file.exists.return_value = True + mock_path_class.return_value.parent.parent.__truediv__.return_value = ( + mock_hash_file + ) + + result = clang_tidy_hash.read_stored_hash() + + assert result == stored_hash + + +def test_read_stored_hash_not_exists() -> None: + """Test reading hash when file doesn't exist.""" + with patch("clang_tidy_hash.Path") as mock_path_class: + # Mock the path calculation and exists check + mock_hash_file = Mock() + mock_hash_file.exists.return_value = False + mock_path_class.return_value.parent.parent.__truediv__.return_value = ( + mock_hash_file + ) + + result = clang_tidy_hash.read_stored_hash() + + assert result is None + + +def test_write_hash() -> None: + """Test writing hash to file.""" + hash_value = "abc123def456" + + with patch("clang_tidy_hash.write_file_content") as mock_write: + clang_tidy_hash.write_hash(hash_value) + + # Verify write_file_content was called with correct parameters + mock_write.assert_called_once() + args = mock_write.call_args[0] + assert str(args[0]).endswith(".clang-tidy.hash") + assert args[1] == hash_value + + +@pytest.mark.parametrize( + ("args", "current_hash", "stored_hash", "expected_exit"), + [ + (["--check"], "abc123", "abc123", 1), # Hashes match, no scan needed + (["--check"], "abc123", "def456", 0), # Hashes differ, scan needed + (["--check"], "abc123", None, 0), # No stored hash, scan needed + ], +) +def test_main_check_mode( + args: list[str], current_hash: str, stored_hash: str | None, expected_exit: int +) -> None: + """Test main function in check mode.""" + with ( + patch("sys.argv", ["clang_tidy_hash.py"] + args), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + pytest.raises(SystemExit) as exc_info, + ): + clang_tidy_hash.main() + + assert exc_info.value.code == expected_exit + + +def test_main_update_mode(capsys: pytest.CaptureFixture[str]) -> None: + """Test main function in update mode.""" + current_hash = "abc123" + + with ( + patch("sys.argv", ["clang_tidy_hash.py", "--update"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.write_hash") as mock_write, + ): + clang_tidy_hash.main() + + mock_write.assert_called_once_with(current_hash) + captured = capsys.readouterr() + assert f"Hash updated: {current_hash}" in captured.out + + +@pytest.mark.parametrize( + ("current_hash", "stored_hash"), + [ + ("abc123", "def456"), # Hash changed, should update + ("abc123", None), # No stored hash, should update + ], +) +def test_main_update_if_changed_mode_update( + current_hash: str, stored_hash: str | None, capsys: pytest.CaptureFixture[str] +) -> None: + """Test main function in update-if-changed mode when update is needed.""" + with ( + patch("sys.argv", ["clang_tidy_hash.py", "--update-if-changed"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + patch("clang_tidy_hash.write_hash") as mock_write, + pytest.raises(SystemExit) as exc_info, + ): + clang_tidy_hash.main() + + assert exc_info.value.code == 0 + mock_write.assert_called_once_with(current_hash) + captured = capsys.readouterr() + assert "Clang-tidy hash updated" in captured.out + + +def test_main_update_if_changed_mode_no_update( + capsys: pytest.CaptureFixture[str], +) -> None: + """Test main function in update-if-changed mode when no update is needed.""" + current_hash = "abc123" + stored_hash = "abc123" + + with ( + patch("sys.argv", ["clang_tidy_hash.py", "--update-if-changed"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + patch("clang_tidy_hash.write_hash") as mock_write, + pytest.raises(SystemExit) as exc_info, + ): + clang_tidy_hash.main() + + assert exc_info.value.code == 0 + mock_write.assert_not_called() + captured = capsys.readouterr() + assert "Clang-tidy hash unchanged" in captured.out + + +def test_main_verify_mode_success(capsys: pytest.CaptureFixture[str]) -> None: + """Test main function in verify mode when verification passes.""" + current_hash = "abc123" + stored_hash = "abc123" + + with ( + patch("sys.argv", ["clang_tidy_hash.py", "--verify"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + ): + clang_tidy_hash.main() + captured = capsys.readouterr() + assert "Hash verification passed" in captured.out + + +@pytest.mark.parametrize( + ("current_hash", "stored_hash"), + [ + ("abc123", "def456"), # Hashes differ, verification fails + ("abc123", None), # No stored hash, verification fails + ], +) +def test_main_verify_mode_failure( + current_hash: str, stored_hash: str | None, capsys: pytest.CaptureFixture[str] +) -> None: + """Test main function in verify mode when verification fails.""" + with ( + patch("sys.argv", ["clang_tidy_hash.py", "--verify"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + pytest.raises(SystemExit) as exc_info, + ): + clang_tidy_hash.main() + + assert exc_info.value.code == 1 + captured = capsys.readouterr() + assert "ERROR: Clang-tidy configuration has changed" in captured.out + + +def test_main_default_mode(capsys: pytest.CaptureFixture[str]) -> None: + """Test main function in default mode (no arguments).""" + current_hash = "abc123" + stored_hash = "def456" + + with ( + patch("sys.argv", ["clang_tidy_hash.py"]), + patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash), + patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash), + ): + clang_tidy_hash.main() + + captured = capsys.readouterr() + assert f"Current hash: {current_hash}" in captured.out + assert f"Stored hash: {stored_hash}" in captured.out + assert "Match: False" in captured.out + + +def test_read_file_lines(tmp_path: Path) -> None: + """Test read_file_lines helper function.""" + test_file = tmp_path / "test.txt" + test_content = "line1\nline2\nline3\n" + test_file.write_text(test_content) + + result = clang_tidy_hash.read_file_lines(test_file) + + assert result == ["line1\n", "line2\n", "line3\n"] + + +def test_read_file_bytes(tmp_path: Path) -> None: + """Test read_file_bytes helper function.""" + test_file = tmp_path / "test.bin" + test_content = b"binary content\x00\xff" + test_file.write_bytes(test_content) + + result = clang_tidy_hash.read_file_bytes(test_file) + + assert result == test_content + + +def test_write_file_content(tmp_path: Path) -> None: + """Test write_file_content helper function.""" + test_file = tmp_path / "test.txt" + test_content = "test content" + + clang_tidy_hash.write_file_content(test_file, test_content) + + assert test_file.read_text() == test_content + + +@pytest.mark.parametrize( + ("line", "expected"), + [ + ("clang-tidy==18.1.5", ("clang-tidy", "clang-tidy==18.1.5")), + ( + "clang-tidy==18.1.5 # comment", + ("clang-tidy", "clang-tidy==18.1.5 # comment"), + ), + ("some-package>=1.0,<2.0", ("some-package", "some-package>=1.0,<2.0")), + ("pkg_with-dashes==1.0", ("pkg_with-dashes", "pkg_with-dashes==1.0")), + ("# just a comment", None), + ("", None), + (" ", None), + ("invalid line without version", None), + ], +) +def test_parse_requirement_line(line: str, expected: tuple[str, str] | None) -> None: + """Test parsing individual requirement lines.""" + result = clang_tidy_hash.parse_requirement_line(line) + assert result == expected diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py new file mode 100644 index 0000000000..4aaaadd80a --- /dev/null +++ b/tests/script/test_determine_jobs.py @@ -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") diff --git a/tests/script/test_helpers.py b/tests/script/test_helpers.py new file mode 100644 index 0000000000..d0db08e6f7 --- /dev/null +++ b/tests/script/test_helpers.py @@ -0,0 +1,1014 @@ +"""Unit tests for script/helpers.py module.""" + +import json +import os +from pathlib import Path +import subprocess +import sys +from unittest.mock import Mock, patch + +import pytest +from pytest import MonkeyPatch + +# Add the script directory to Python path so we can import helpers +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "script")) +) + +import helpers # noqa: E402 + +changed_files = helpers.changed_files +filter_changed = helpers.filter_changed +get_changed_components = helpers.get_changed_components +_get_changed_files_from_command = helpers._get_changed_files_from_command +_get_pr_number_from_github_env = helpers._get_pr_number_from_github_env +_get_changed_files_github_actions = helpers._get_changed_files_github_actions +_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( + ("github_ref", "expected_pr_number"), + [ + ("refs/pull/1234/merge", "1234"), + ("refs/pull/5678/head", "5678"), + ("refs/pull/999/merge", "999"), + ("refs/heads/main", None), + ("", None), + ], +) +def test_get_pr_number_from_github_env_ref( + monkeypatch: MonkeyPatch, github_ref: str, expected_pr_number: str | None +) -> None: + """Test extracting PR number from GITHUB_REF.""" + monkeypatch.setenv("GITHUB_REF", github_ref) + # Make sure GITHUB_EVENT_PATH is not set + monkeypatch.delenv("GITHUB_EVENT_PATH", raising=False) + + result = _get_pr_number_from_github_env() + + assert result == expected_pr_number + + +def test_get_pr_number_from_github_env_event_file( + monkeypatch: MonkeyPatch, tmp_path: Path +) -> None: + """Test extracting PR number from GitHub event file.""" + # No PR number in ref + monkeypatch.setenv("GITHUB_REF", "refs/heads/feature-branch") + + event_file = tmp_path / "event.json" + event_data = {"pull_request": {"number": 5678}} + event_file.write_text(json.dumps(event_data)) + monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file)) + + result = _get_pr_number_from_github_env() + + assert result == "5678" + + +def test_get_pr_number_from_github_env_no_pr( + monkeypatch: MonkeyPatch, tmp_path: Path +) -> None: + """Test when no PR number is available.""" + monkeypatch.setenv("GITHUB_REF", "refs/heads/main") + + event_file = tmp_path / "event.json" + event_data = {"push": {"head_commit": {"id": "abc123"}}} + event_file.write_text(json.dumps(event_data)) + monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file)) + + result = _get_pr_number_from_github_env() + + assert result is None + + +@pytest.mark.parametrize( + ("github_ref", "expected_pr_number"), + [ + ("refs/pull/1234/merge", "1234"), + ("refs/pull/5678/head", "5678"), + ("refs/pull/999/merge", "999"), + ], +) +def test_github_actions_pull_request_with_pr_number_in_ref( + monkeypatch: MonkeyPatch, github_ref: str, expected_pr_number: str +) -> None: + """Test PR detection via GITHUB_REF.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + monkeypatch.setenv("GITHUB_REF", github_ref) + + expected_files = ["file1.py", "file2.cpp"] + + with patch("helpers._get_changed_files_from_command") as mock_get: + mock_get.return_value = expected_files + + result = changed_files() + + mock_get.assert_called_once_with( + ["gh", "pr", "diff", expected_pr_number, "--name-only"] + ) + assert result == expected_files + + +def test_github_actions_pull_request_with_event_file( + monkeypatch: MonkeyPatch, tmp_path: Path +) -> None: + """Test PR detection via GitHub event file.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + monkeypatch.setenv("GITHUB_REF", "refs/heads/feature-branch") + + event_file = tmp_path / "event.json" + event_data = {"pull_request": {"number": 5678}} + event_file.write_text(json.dumps(event_data)) + monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file)) + + expected_files = ["file1.py", "file2.cpp"] + + with patch("helpers._get_changed_files_from_command") as mock_get: + mock_get.return_value = expected_files + + result = changed_files() + + mock_get.assert_called_once_with(["gh", "pr", "diff", "5678", "--name-only"]) + assert result == expected_files + + +def test_github_actions_push_event(monkeypatch: MonkeyPatch) -> None: + """Test push event handling.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + monkeypatch.setenv("GITHUB_EVENT_NAME", "push") + + expected_files = ["file1.py", "file2.cpp"] + + with patch("helpers._get_changed_files_from_command") as mock_get: + mock_get.return_value = expected_files + + result = changed_files() + + mock_get.assert_called_once_with(["git", "diff", "HEAD~1..HEAD", "--name-only"]) + 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: + """Test _get_changed_files_github_actions for pull request event.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + + expected_files = ["file1.py", "file2.cpp"] + + with ( + patch("helpers._get_pr_number_from_github_env", return_value="1234"), + patch("helpers._get_changed_files_from_command") as mock_get, + ): + mock_get.return_value = expected_files + + result = _get_changed_files_github_actions() + + mock_get.assert_called_once_with(["gh", "pr", "diff", "1234", "--name-only"]) + assert result == expected_files + + +def test_get_changed_files_github_actions_pull_request_no_pr_number( + monkeypatch: MonkeyPatch, +) -> None: + """Test _get_changed_files_github_actions when no PR number is found.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "pull_request") + + with patch("helpers._get_pr_number_from_github_env", return_value=None): + result = _get_changed_files_github_actions() + + assert result is None + + +def test_get_changed_files_github_actions_push(monkeypatch: MonkeyPatch) -> None: + """Test _get_changed_files_github_actions for push event.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "push") + + expected_files = ["file1.py", "file2.cpp"] + + with patch("helpers._get_changed_files_from_command") as mock_get: + mock_get.return_value = expected_files + + result = _get_changed_files_github_actions() + + mock_get.assert_called_once_with(["git", "diff", "HEAD~1..HEAD", "--name-only"]) + assert result == expected_files + + +def test_get_changed_files_github_actions_push_fallback( + monkeypatch: MonkeyPatch, +) -> None: + """Test _get_changed_files_github_actions fallback for push event.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "push") + + with patch("helpers._get_changed_files_from_command") as mock_get: + mock_get.side_effect = Exception("Failed") + + result = _get_changed_files_github_actions() + + assert result is None + + +def test_get_changed_files_github_actions_other_event(monkeypatch: MonkeyPatch) -> None: + """Test _get_changed_files_github_actions for other event types.""" + monkeypatch.setenv("GITHUB_EVENT_NAME", "workflow_dispatch") + + result = _get_changed_files_github_actions() + + assert result is None + + +def test_github_actions_push_event_fallback(monkeypatch: MonkeyPatch) -> None: + """Test push event fallback to git merge-base.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + monkeypatch.setenv("GITHUB_EVENT_NAME", "push") + + expected_files = ["file1.py", "file2.cpp"] + + with ( + patch("helpers._get_changed_files_from_command") as mock_get, + patch("helpers.get_output") as mock_output, + ): + # First call fails, triggering fallback + mock_get.side_effect = [ + Exception("Failed"), + expected_files, + ] + + mock_output.side_effect = [ + "origin\nupstream\n", # git remote + "abc123\n", # merge base + ] + + result = changed_files() + + assert mock_get.call_count == 2 + assert result == expected_files + + +@pytest.mark.parametrize( + ("branch", "merge_base"), + [ + (None, "abc123"), # Default branch (dev) + ("release", "def456"), + ("beta", "ghi789"), + ], +) +def test_local_development_branches( + monkeypatch: MonkeyPatch, branch: str | None, merge_base: str +) -> None: + """Test local development with different branches.""" + monkeypatch.delenv("GITHUB_ACTIONS", raising=False) + + expected_files = ["file1.py", "file2.cpp"] + + with ( + patch("helpers.get_output") as mock_output, + patch("helpers._get_changed_files_from_command") as mock_get, + ): + if branch is None: + # For default branch, helpers.get_output is called twice (git remote and merge-base) + mock_output.side_effect = [ + "origin\nupstream\n", # git remote + f"{merge_base}\n", # merge base for upstream/dev + ] + else: + # For custom branch, may need more calls if trying multiple remotes + mock_output.side_effect = [ + "origin\nupstream\n", # git remote + Exception("not found"), # upstream/{branch} may fail + f"{merge_base}\n", # merge base for origin/{branch} + ] + + mock_get.return_value = expected_files + + result = changed_files(branch) + + mock_get.assert_called_once_with(["git", "diff", merge_base, "--name-only"]) + assert result == expected_files + + +def test_local_development_no_remotes_configured(monkeypatch: MonkeyPatch) -> None: + """Test error when no git remotes are configured.""" + monkeypatch.delenv("GITHUB_ACTIONS", raising=False) + + with patch("helpers.get_output") as mock_output: + # The function calls get_output multiple times: + # 1. First to get list of remotes: git remote + # 2. Then for each remote it tries: git merge-base + # We simulate having some remotes but all merge-base attempts fail + def side_effect_func(*args): + if args == ("git", "remote"): + return "origin\nupstream\n" + else: + # All merge-base attempts fail + raise Exception("Command failed") + + mock_output.side_effect = side_effect_func + + with pytest.raises(ValueError, match="Git not configured"): + changed_files() + + +@pytest.mark.parametrize( + ("stdout", "expected"), + [ + ("file1.py\nfile2.cpp\n\n", ["file1.py", "file2.cpp"]), + ("\n\n", []), + ("single.py\n", ["single.py"]), + ( + "path/to/file.cpp\nanother/path.h\n", + ["another/path.h", "path/to/file.cpp"], + ), # Sorted + ], +) +def test_get_changed_files_from_command_successful( + stdout: str, expected: list[str] +) -> None: + """Test successful command execution with various outputs.""" + mock_result = Mock() + mock_result.returncode = 0 + mock_result.stdout = stdout + + with patch("subprocess.run", return_value=mock_result): + result = _get_changed_files_from_command(["git", "diff"]) + + # Normalize paths to forward slashes for comparison + # since os.path.relpath returns OS-specific separators + normalized_result = [f.replace(os.sep, "/") for f in result] + assert normalized_result == expected + + +@pytest.mark.parametrize( + ("returncode", "stderr"), + [ + (1, "Error: command failed"), + (128, "fatal: not a git repository"), + (2, "Unknown error"), + ], +) +def test_get_changed_files_from_command_failed(returncode: int, stderr: str) -> None: + """Test command failure handling.""" + mock_result = Mock() + mock_result.returncode = returncode + mock_result.stderr = stderr + + with patch("subprocess.run", return_value=mock_result): + with pytest.raises(Exception) as exc_info: + _get_changed_files_from_command(["git", "diff"]) + assert "Command failed" in str(exc_info.value) + assert stderr in str(exc_info.value) + + +def test_get_changed_files_from_command_relative_paths() -> None: + """Test that paths are made relative to current directory.""" + mock_result = Mock() + mock_result.returncode = 0 + mock_result.stdout = "/some/project/file1.py\n/some/project/sub/file2.cpp\n" + + with ( + patch("subprocess.run", return_value=mock_result), + patch( + "os.path.relpath", side_effect=["file1.py", "sub/file2.cpp"] + ) as mock_relpath, + patch("os.getcwd", return_value="/some/project"), + ): + result = _get_changed_files_from_command(["git", "diff"]) + + # Check relpath was called with correct arguments + assert mock_relpath.call_count == 2 + assert result == ["file1.py", "sub/file2.cpp"] + + +@pytest.mark.parametrize( + "changed_files_list", + [ + ["esphome/core/component.h", "esphome/components/wifi/wifi.cpp"], + ["esphome/core/helpers.cpp"], + ["esphome/core/application.h", "esphome/core/defines.h"], + ], +) +def test_get_changed_components_core_cpp_files_trigger_full_scan( + changed_files_list: list[str], +) -> None: + """Test that core C++/header file changes trigger full scan without calling subprocess.""" + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = changed_files_list + + # Should return None without calling subprocess + result = get_changed_components() + assert result is None + + +def test_get_changed_components_core_python_files_no_full_scan() -> None: + """Test that core Python file changes do NOT trigger full scan.""" + changed_files_list = [ + "esphome/core/__init__.py", + "esphome/core/config.py", + "esphome/components/wifi/wifi.cpp", + ] + + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = changed_files_list + + mock_result = Mock() + mock_result.stdout = "wifi\n" + + with patch("subprocess.run", return_value=mock_result): + result = get_changed_components() + # Should NOT return None - should call list-components.py + assert result == ["wifi"] + + +def test_get_changed_components_mixed_core_files_with_cpp() -> None: + """Test that mixed Python and C++ core files still trigger full scan due to C++ file.""" + changed_files_list = [ + "esphome/core/__init__.py", + "esphome/core/config.py", + "esphome/core/helpers.cpp", # This C++ file should trigger full scan + "esphome/components/wifi/wifi.cpp", + ] + + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = changed_files_list + + # Should return None without calling subprocess due to helpers.cpp + result = get_changed_components() + assert result is None + + +@pytest.mark.parametrize( + ("changed_files_list", "expected"), + [ + # Only component files changed + ( + ["esphome/components/wifi/wifi.cpp", "esphome/components/api/api.cpp"], + ["wifi", "api"], + ), + # Non-component files only + (["README.md", "script/clang-tidy"], []), + # Single component + (["esphome/components/mqtt/mqtt_client.cpp"], ["mqtt"]), + ], +) +def test_get_changed_components_returns_component_list( + changed_files_list: list[str], expected: list[str] +) -> None: + """Test component detection returns correct component list.""" + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = changed_files_list + + mock_result = Mock() + mock_result.stdout = "\n".join(expected) + "\n" if expected else "\n" + + with patch("subprocess.run", return_value=mock_result): + result = get_changed_components() + assert result == expected + + +def test_get_changed_components_script_failure() -> None: + """Test fallback to full scan when script fails.""" + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = ["esphome/components/wifi/wifi_component.cpp"] + + with patch("subprocess.run") as mock_run: + mock_run.side_effect = subprocess.CalledProcessError(1, "cmd") + + result = get_changed_components() + + assert result is None # None means full scan + + +@pytest.mark.parametrize( + ("components", "all_files", "expected_files"), + [ + # Core C++/header files changed (full scan) + ( + None, + ["esphome/components/wifi/wifi.cpp", "esphome/core/helpers.cpp"], + ["esphome/components/wifi/wifi.cpp", "esphome/core/helpers.cpp"], + ), + # Specific components + ( + ["wifi", "api"], + [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/wifi/wifi.h", + "esphome/components/api/api.cpp", + "esphome/components/mqtt/mqtt.cpp", + ], + [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/wifi/wifi.h", + "esphome/components/api/api.cpp", + ], + ), + # No components changed + ( + [], + ["esphome/components/wifi/wifi.cpp", "script/clang-tidy"], + ["script/clang-tidy"], # Only non-component changed files + ), + ], +) +def test_filter_changed_ci_mode( + monkeypatch: MonkeyPatch, + components: list[str] | None, + all_files: list[str], + expected_files: list[str], +) -> None: + """Test filter_changed in CI mode with different component scenarios.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + + with patch("helpers.get_changed_components") as mock_components: + mock_components.return_value = components + + if components == []: + # No components changed scenario needs changed_files mock + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = ["script/clang-tidy", "README.md"] + result = filter_changed(all_files) + else: + result = filter_changed(all_files) + + assert set(result) == set(expected_files) + + +def test_filter_changed_local_mode(monkeypatch: MonkeyPatch) -> None: + """Test filter_changed in local mode filters files directly.""" + monkeypatch.delenv("GITHUB_ACTIONS", raising=False) + + all_files = [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/api/api.cpp", + "esphome/core/helpers.cpp", + ] + + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = [ + "esphome/components/wifi/wifi.cpp", + "esphome/core/helpers.cpp", + ] + + result = filter_changed(all_files) + + # Should only include files that actually changed + expected = ["esphome/components/wifi/wifi.cpp", "esphome/core/helpers.cpp"] + assert set(result) == set(expected) + + +def test_filter_changed_component_path_parsing(monkeypatch: MonkeyPatch) -> None: + """Test correct parsing of component paths.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + + all_files = [ + "esphome/components/wifi/wifi_component.cpp", + "esphome/components/wifi_info/wifi_info_text_sensor.cpp", # Different component + "esphome/components/api/api_server.cpp", + "esphome/components/api/custom_api_device.h", + ] + + with patch("helpers.get_changed_components") as mock_components: + mock_components.return_value = ["wifi"] # Only wifi, not wifi_info + + result = filter_changed(all_files) + + # Should only include files from wifi component, not wifi_info + expected = ["esphome/components/wifi/wifi_component.cpp"] + assert result == expected + + +def test_filter_changed_prints_output( + monkeypatch: MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """Test that appropriate messages are printed.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + + all_files = ["esphome/components/wifi/wifi_component.cpp"] + + with patch("helpers.get_changed_components") as mock_components: + mock_components.return_value = ["wifi"] + + filter_changed(all_files) + + # Check that output was produced (not checking exact messages) + captured = capsys.readouterr() + assert len(captured.out) > 0 + + +@pytest.mark.parametrize( + ("files", "expected_empty"), + [ + ([], True), + (["file.cpp"], False), + ], + ids=["empty_files", "non_empty_files"], +) +def test_filter_changed_empty_file_handling( + monkeypatch: MonkeyPatch, files: list[str], expected_empty: bool +) -> None: + """Test handling of empty file lists.""" + monkeypatch.setenv("GITHUB_ACTIONS", "true") + + with patch("helpers.get_changed_components") as mock_components: + mock_components.return_value = ["wifi"] + + result = filter_changed(files) + + # Both cases should be empty: + # - Empty files list -> empty result + # - file.cpp doesn't match esphome/components/wifi/* pattern -> filtered out + assert len(result) == 0 + + +def test_filter_changed_ci_full_scan() -> None: + """Test _filter_changed_ci when core C++/header files changed (full scan).""" + all_files = ["esphome/components/wifi/wifi.cpp", "esphome/core/helpers.cpp"] + + with patch("helpers.get_changed_components", return_value=None): + result = _filter_changed_ci(all_files) + + # Should return all files for full scan + assert result == all_files + + +def test_filter_changed_ci_no_components_changed() -> None: + """Test _filter_changed_ci when no components changed.""" + all_files = ["esphome/components/wifi/wifi.cpp", "script/clang-tidy", "README.md"] + + with ( + patch("helpers.get_changed_components", return_value=[]), + patch("helpers.changed_files", return_value=["script/clang-tidy", "README.md"]), + ): + result = _filter_changed_ci(all_files) + + # Should only include non-component files that changed + assert set(result) == {"script/clang-tidy", "README.md"} + + +def test_filter_changed_ci_specific_components() -> None: + """Test _filter_changed_ci with specific components changed.""" + all_files = [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/wifi/wifi.h", + "esphome/components/api/api.cpp", + "esphome/components/mqtt/mqtt.cpp", + ] + + with patch("helpers.get_changed_components", return_value=["wifi", "api"]): + result = _filter_changed_ci(all_files) + + # Should include all files from wifi and api components + expected = [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/wifi/wifi.h", + "esphome/components/api/api.cpp", + ] + assert set(result) == set(expected) + + +def test_filter_changed_local() -> None: + """Test _filter_changed_local filters based on git changes.""" + all_files = [ + "esphome/components/wifi/wifi.cpp", + "esphome/components/api/api.cpp", + "esphome/core/helpers.cpp", + ] + + with patch("helpers.changed_files") as mock_changed: + mock_changed.return_value = [ + "esphome/components/wifi/wifi.cpp", + "esphome/core/helpers.cpp", + ] + + result = _filter_changed_local(all_files) + + # Should only include files that actually changed + expected = ["esphome/components/wifi/wifi.cpp", "esphome/core/helpers.cpp"] + assert set(result) == set(expected) + + +def test_build_all_include_with_git(tmp_path: Path) -> None: + """Test build_all_include using git ls-files.""" + # Mock git output + git_output = "esphome/core/component.h\nesphome/components/wifi/wifi.h\nesphome/components/api/api.h\n" + + mock_proc = Mock() + mock_proc.returncode = 0 + mock_proc.stdout = git_output + + with ( + patch("subprocess.run", return_value=mock_proc), + patch("helpers.temp_header_file", str(tmp_path / "all-include.cpp")), + ): + build_all_include() + + # Check the generated file + include_file = tmp_path / "all-include.cpp" + assert include_file.exists() + + content = include_file.read_text() + expected_lines = [ + '#include "esphome/components/api/api.h"', + '#include "esphome/components/wifi/wifi.h"', + '#include "esphome/core/component.h"', + "", # Empty line at end + ] + assert content == "\n".join(expected_lines) + + +def test_build_all_include_empty_output(tmp_path: Path) -> None: + """Test build_all_include with empty git output.""" + # Mock git returning empty output + mock_proc = Mock() + mock_proc.returncode = 0 + mock_proc.stdout = "" + + with ( + patch("subprocess.run", return_value=mock_proc), + patch("helpers.temp_header_file", str(tmp_path / "all-include.cpp")), + ): + build_all_include() + + # Check the generated file + include_file = tmp_path / "all-include.cpp" + assert include_file.exists() + + content = include_file.read_text() + # When git output is empty, the list comprehension filters out empty strings, + # then we append "" to get [""], which joins to just "" + assert content == "" + + +def test_build_all_include_creates_directory(tmp_path: Path) -> None: + """Test that build_all_include creates the temp directory if needed.""" + # Use a subdirectory that doesn't exist + temp_file = tmp_path / "subdir" / "all-include.cpp" + + mock_proc = Mock() + mock_proc.returncode = 0 + mock_proc.stdout = "esphome/core/test.h\n" + + with ( + patch("subprocess.run", return_value=mock_proc), + patch("helpers.temp_header_file", str(temp_file)), + ): + build_all_include() + + # Check that directory was created + assert temp_file.parent.exists() + assert temp_file.exists() + + +def test_print_file_list_empty(capsys: pytest.CaptureFixture[str]) -> None: + """Test printing an empty file list.""" + print_file_list([], "Test Files:") + captured = capsys.readouterr() + + assert "Test Files:" in captured.out + assert "No files to check!" in captured.out + + +def test_print_file_list_small(capsys: pytest.CaptureFixture[str]) -> None: + """Test printing a small list of files (less than max_files).""" + files = ["file1.cpp", "file2.cpp", "file3.cpp"] + print_file_list(files, "Test Files:", max_files=20) + captured = capsys.readouterr() + + assert "Test Files:" in captured.out + assert " file1.cpp" in captured.out + assert " file2.cpp" in captured.out + assert " file3.cpp" in captured.out + assert "... and" not in captured.out + + +def test_print_file_list_exact_max_files(capsys: pytest.CaptureFixture[str]) -> None: + """Test printing exactly max_files number of files.""" + files = [f"file{i}.cpp" for i in range(20)] + print_file_list(files, "Test Files:", max_files=20) + captured = capsys.readouterr() + + # All files should be shown + for i in range(20): + assert f" file{i}.cpp" in captured.out + assert "... and" not in captured.out + + +def test_print_file_list_large(capsys: pytest.CaptureFixture[str]) -> None: + """Test printing a large list of files (more than max_files).""" + files = [f"file{i:03d}.cpp" for i in range(50)] + print_file_list(files, "Test Files:", max_files=20) + captured = capsys.readouterr() + + assert "Test Files:" in captured.out + # First 10 files should be shown (sorted) + for i in range(10): + assert f" file{i:03d}.cpp" in captured.out + # Files 10-49 should not be shown + assert " file010.cpp" not in captured.out + assert " file049.cpp" not in captured.out + # Should show count of remaining files + assert "... and 40 more files" in captured.out + + +def test_print_file_list_unsorted(capsys: pytest.CaptureFixture[str]) -> None: + """Test that files are sorted before printing.""" + files = ["z_file.cpp", "a_file.cpp", "m_file.cpp"] + print_file_list(files, "Test Files:", max_files=20) + captured = capsys.readouterr() + + lines = captured.out.strip().split("\n") + # Check order in output + assert lines[1] == " a_file.cpp" + assert lines[2] == " m_file.cpp" + assert lines[3] == " z_file.cpp" + + +def test_print_file_list_custom_max_files(capsys: pytest.CaptureFixture[str]) -> None: + """Test with custom max_files parameter.""" + files = [f"file{i}.cpp" for i in range(15)] + print_file_list(files, "Test Files:", max_files=10) + captured = capsys.readouterr() + + # Should truncate after 10 files + assert "... and 5 more files" in captured.out + + +def test_print_file_list_default_title(capsys: pytest.CaptureFixture[str]) -> None: + """Test with default title.""" + print_file_list(["test.cpp"]) + captured = capsys.readouterr() + + 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 diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 0dcdd84507..4f256ffb33 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -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) diff --git a/tests/unit_tests/test_config_helpers.py b/tests/unit_tests/test_config_helpers.py new file mode 100644 index 0000000000..1c850e3759 --- /dev/null +++ b/tests/unit_tests/test_config_helpers.py @@ -0,0 +1,135 @@ +"""Unit tests for esphome.config_helpers module.""" + +from collections.abc import Callable +from unittest.mock import patch + +from esphome.config_helpers import filter_source_files_from_platform, get_logger_level +from esphome.const import ( + CONF_LEVEL, + CONF_LOGGER, + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + PlatformFramework, +) + + +def test_filter_source_files_from_platform_esp32() -> None: + """Test that filter_source_files_from_platform correctly filters files for ESP32 platform.""" + # Define test file mappings + files_map: dict[str, set[PlatformFramework]] = { + "logger_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "logger_host.cpp": {PlatformFramework.HOST_NATIVE}, + "logger_common.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.HOST_NATIVE, + }, + } + + # Create the filter function + filter_func: Callable[[], list[str]] = filter_source_files_from_platform(files_map) + + # Test ESP32 with Arduino framework + mock_core_data: dict[str, dict[str, str]] = { + KEY_CORE: { + KEY_TARGET_PLATFORM: "esp32", + KEY_TARGET_FRAMEWORK: "arduino", + } + } + + with patch("esphome.config_helpers.CORE.data", mock_core_data): + excluded: list[str] = filter_func() + # ESP32 Arduino should exclude ESP8266 and HOST files + assert "logger_esp8266.cpp" in excluded + assert "logger_host.cpp" in excluded + # But not ESP32 or common files + assert "logger_esp32.cpp" not in excluded + assert "logger_common.cpp" not in excluded + + +def test_filter_source_files_from_platform_host() -> None: + """Test that filter_source_files_from_platform correctly filters files for HOST platform.""" + # Define test file mappings + files_map: dict[str, set[PlatformFramework]] = { + "logger_esp32.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "logger_host.cpp": {PlatformFramework.HOST_NATIVE}, + "logger_common.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + PlatformFramework.ESP8266_ARDUINO, + PlatformFramework.HOST_NATIVE, + }, + } + + # Create the filter function + filter_func: Callable[[], list[str]] = filter_source_files_from_platform(files_map) + + # Test Host platform + mock_core_data: dict[str, dict[str, str]] = { + KEY_CORE: { + KEY_TARGET_PLATFORM: "host", + KEY_TARGET_FRAMEWORK: "host", # Framework.NATIVE is "host" + } + } + + with patch("esphome.config_helpers.CORE.data", mock_core_data): + excluded: list[str] = filter_func() + # Host should exclude ESP32 and ESP8266 files + assert "logger_esp32.cpp" in excluded + assert "logger_esp8266.cpp" in excluded + # But not host or common files + assert "logger_host.cpp" not in excluded + assert "logger_common.cpp" not in excluded + + +def test_filter_source_files_from_platform_handles_missing_data() -> None: + """Test that filter_source_files_from_platform returns empty list when platform/framework data is missing.""" + # Define test file mappings + files_map: dict[str, set[PlatformFramework]] = { + "logger_esp32.cpp": {PlatformFramework.ESP32_ARDUINO}, + "logger_host.cpp": {PlatformFramework.HOST_NATIVE}, + } + + # Create the filter function + filter_func: Callable[[], list[str]] = filter_source_files_from_platform(files_map) + + # Test case: Missing platform/framework data + mock_core_data: dict[str, dict[str, str]] = {KEY_CORE: {}} + + with patch("esphome.config_helpers.CORE.data", mock_core_data): + excluded: list[str] = filter_func() + # Should return empty list when platform/framework not set + assert excluded == [] + + +def test_get_logger_level() -> None: + """Test get_logger_level helper function.""" + # Test no logger config - should return default DEBUG + mock_config = {} + with patch("esphome.config_helpers.CORE.config", mock_config): + assert get_logger_level() == "DEBUG" + + # Test with logger set to INFO + mock_config = {CONF_LOGGER: {CONF_LEVEL: "INFO"}} + with patch("esphome.config_helpers.CORE.config", mock_config): + assert get_logger_level() == "INFO" + + # Test with VERY_VERBOSE + mock_config = {CONF_LOGGER: {CONF_LEVEL: "VERY_VERBOSE"}} + with patch("esphome.config_helpers.CORE.config", mock_config): + assert get_logger_level() == "VERY_VERBOSE" + + # Test with logger missing level (uses default DEBUG) + mock_config = {CONF_LOGGER: {}} + with patch("esphome.config_helpers.CORE.config", mock_config): + assert get_logger_level() == "DEBUG" diff --git a/tests/unit_tests/test_loader.py b/tests/unit_tests/test_loader.py new file mode 100644 index 0000000000..c6d4c4aef0 --- /dev/null +++ b/tests/unit_tests/test_loader.py @@ -0,0 +1,63 @@ +"""Unit tests for esphome.loader module.""" + +from unittest.mock import MagicMock, patch + +from esphome.loader import ComponentManifest + + +def test_component_manifest_resources_with_filter_source_files() -> None: + """Test that ComponentManifest.resources correctly filters out excluded files.""" + # Create a mock module with FILTER_SOURCE_FILES function + mock_module = MagicMock() + mock_module.FILTER_SOURCE_FILES = lambda: [ + "platform_esp32.cpp", + "platform_esp8266.cpp", + ] + mock_module.__package__ = "esphome.components.test_component" + + # Create ComponentManifest instance + manifest = ComponentManifest(mock_module) + + # Mock the files in the package + def create_mock_file(filename: str) -> MagicMock: + mock_file = MagicMock() + mock_file.name = filename + mock_file.is_file.return_value = True + return mock_file + + mock_files = [ + create_mock_file("test.cpp"), + create_mock_file("test.h"), + create_mock_file("platform_esp32.cpp"), + create_mock_file("platform_esp8266.cpp"), + create_mock_file("common.cpp"), + create_mock_file("README.md"), # Should be excluded by extension + ] + + # Mock importlib.resources + with patch("importlib.resources.files") as mock_files_func: + mock_package_files = MagicMock() + mock_package_files.iterdir.return_value = mock_files + mock_package_files.joinpath = lambda name: MagicMock(is_file=lambda: True) + mock_files_func.return_value = mock_package_files + + # Get resources + resources = manifest.resources + + # Convert to list of filenames for easier testing + resource_names = [r.resource for r in resources] + + # Check that platform files are excluded + assert "platform_esp32.cpp" not in resource_names + assert "platform_esp8266.cpp" not in resource_names + + # Check that other source files are included + assert "test.cpp" in resource_names + assert "test.h" in resource_names + assert "common.cpp" in resource_names + + # Check that non-source files are excluded + assert "README.md" not in resource_names + + # Verify the correct number of resources + assert len(resources) == 3 # test.cpp, test.h, common.cpp diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index b377499d29..3208923116 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -47,9 +47,8 @@ def dict_diff(a, b, path=""): elif len(b) > len(a): for i in range(min_len, len(b)): diffs.append(f"{path}[{i}] only in expected: {b[i]!r}") - else: - if a != b: - diffs.append(f"\t{path}: actual={a!r} expected={b!r}") + elif a != b: + diffs.append(f"\t{path}: actual={a!r} expected={b!r}") return diffs