Skip to content

Inherit a base's sub-blocks in the id walk so split platforms offer their nested sensor ids - #2826

Merged
bdraco merged 3 commits into
mainfrom
split-platform-nested-ids
Sep 23, 2026
Merged

bdraco merged 3 commits into
mainfrom
split-platform-nested-ids

Conversation

@bdraco

@bdraco bdraco commented Sep 22, 2026

Copy link
Copy Markdown
Member

What does this implement/fix?

The id walk in script/sync_components.py pulled only an inherited id from an extends base, so a platform whose sub-sensor blocks live on a shared base (sensor.bme280_i2c and the other split _i2c / _spi platforms, sensor.ina3221 one level down, sensor.airthings_wave_plus) never got them into provides_id_paths; since #2782 filtered their hub id out of sensor pickers they offered nothing.

A block now inherits its base's id and sub-blocks at every level; flat inherited fields still never merge, so a sibling mqtt_id is not recorded. The inherited automations and filters carry no schema node, so the walk never descends them and the whole bundle walks in under a second. The nested leaf rule in _record_id_classes counts a class only when some platform of that class's own domain declares it at its root, read once from the bundle in _root_entity_classes, so display::DisplayPage (pages[].id) never becomes a display provider path. The walk carries the extends refs expanded up the path, as _convert_config_vars does, which stops lvgl's self referential widget schemas.

Catalog regenerated against 2026.9.0, sync wall time 86 s against an 83 s baseline. The 92 changed files: 16 split platforms plus sensor.ina3221 and sensor.airthings_wave_mini gain nested provides_id_paths and drop id_classes; sensor.airthings_wave_plus, ade7880, atm90e32, growatt_solar, havells_solar, ld6002b, rd03d and sdm_meter gain the sub-sensor paths they inherit; the eight wk2xxx_* hubs provide uart through uart[].id; lvgl reaches its widget ids and provides key_provider; every remaining sensor declarer now carries sensor::Sensor, so references_class drops from 99 sensor references across 51 component bodies and two automation docs. No display body gains a pages path.

Checked in the dashboard with the paired frontend: a sensor.copy source_id picker offers the bme280 temperature, pressure and humidity ids and not the hub, and a touchscreen display picker offers the display only.

Related issue or feature (if applicable):

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Frontend coordination

  • No frontend change needed
  • Companion frontend PR: esphome/device-builder-frontend#

Checklist

  • The code change is tested and works locally.
  • Pre-commit hooks pass (ruff, codespell, yaml/json/python checks).
  • Tests have been added or updated under tests/ where applicable.
  • components.index.json / definitions/components/*.json have not been hand-edited (regenerate via script/sync_components.py if a sync is needed).
  • Architecture-level changes are reflected in docs/ARCHITECTURE.md and/or docs/API.md.

…heir nested sensor ids

A block inherits its extends base's id and sub-blocks at every level; the nested leaf rule counts a class only when a platform of its domain declares it at a root, and the walk carries the refs expanded up the path so a self referential base stops. Catalog regenerated against 2026.9.0.
@bdraco bdraco added the bugfix Bug fix label Sep 22, 2026
@bdraco

bdraco commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@codspeed

codspeed Bot commented Sep 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 28 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing split-platform-nested-ids (b61d659) with main (509c7f3)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@codecov

codecov Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.75%. Comparing base (509c7f3) to head (b61d659).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #2826   +/-   ##
=======================================
  Coverage   99.75%   99.75%           
=======================================
  Files         294      294           
  Lines       25421    25421           
=======================================
  Hits        25359    25359           
  Misses         62       62           
Flag Coverage Δ
py3.12 99.72% <ø> (ø)
py3.14 99.60% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@esphbot

esphbot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco

bdraco commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Thanks, all four addressed.

  1. Frontend descent: drove an lvgl config in the dashboard with the paired frontend. A binary_sensor.lvgl widget picker offers top_button, my_matrix, key_1 (the 5 segment widgets.buttonmatrix.rows.buttons.id path), my_meter, my_scale, my_arc (the 6 segment widgets.meter.scales.indicators.arc.id path) and my_page, with no page errors; collectIdsAtPath recurses per segment, so depth is generic. It does not offer a button nested inside the page's own widgets, which is item 3. The key_collector source_id field renders as a list of plain text inputs in the current frontend, so there is no picker to drive there; that is how the field renders on main too.

  2. The display test now walks every body and every namespace, asserting no shipped id path descends through pages. Small correction to the reasoning: display::DisplayPage is a use_id_type target in the bundle (display.json, page.json, touchscreen.json, six sites), so without the entity class rule the sub-block inheritance would have shipped pages[].id as a display path on the 20 platforms that inherit pages; the unit test pins the rule and the catalog test pins the invariant.

  3. Recorded in docs/API.md next to the inherited base clause: paths are enumerated, so a self referential schema is listed one level deep.

  4. _iter_bundle_sections now logs a warning for a skipped file.

Also re-checked the whole regeneration mechanically: every leaf level difference across the 89 catalog files is one of references_class: sensor::Sensor removed, provides gained, a provides_id_paths path added, or id_classes removed, and every added path outside lvgl resolves to an id entry already present in that component's config_entries on main.

@bdraco

bdraco commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

@bdraco: Thanks, all four addressed.

Verified all four, plus your correction.

Warning lands at sync_components.py:5005. Pages test walks every body, every namespace — confirmed non-vacuous: 30 shipped bodies carry a pages key (29 display.* plus binary_sensor.touchscreen), and "pages" in id_path does not collide with lvgl's widgets.page.id. Docs note enumerated depth at API.md:288.

Your display::DisplayPage correction holds. Catalog re-checks clean: index provides_id_paths 290 → 316, provides 378 → 404, +26 both, no losses, no display.* body touched.

One gap left. Nothing pins lvgl's shipped widget paths or key_provider. Filed as a suggestion — non-blocking.

@esphbot

esphbot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco marked this pull request as ready for review September 22, 2026 23:23
Copilot AI lite review requested due to automatic review settings September 22, 2026 23:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical recursion and test-fixture schema-resolution blockers remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
What changed in this PR

This bugfix extends catalog ID discovery through inherited sub-blocks so split platforms expose nested sensor/UART IDs while filtering non-entity paths.

Changes:

  • Adds inherited ID traversal and entity-class filtering.
  • Regenerates affected component and automation metadata.
  • Adds regression tests and API documentation.
File Summary
tests/​test_sync_components_reference_classes.py Adds coverage for root entity-class filtering.
tests/​test_sync_components_provides.py Tests inherited provider paths; Critical (2 votes): the fixture wraps the sensor schema twice, preventing schema resolution.
script/​sync_components.py Implements inherited ID traversal; Critical (1 vote): recursive ancestry resolution does not propagate seen, allowing self-referential recursion.
esphome_device_builder/​definitions/​components/​wk2212_spi.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2212_i2c.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2204_spi.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2204_i2c.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2168_spi.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2168_i2c.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2132_spi.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​wk2132_i2c.json Regenerated inherited UART provider metadata.
esphome_device_builder/​definitions/​components/​statsd.json Regenerated component metadata.
esphome_device_builder/​definitions/​components/​packet_transport.udp.json Regenerated packet transport metadata.
esphome_device_builder/​definitions/​components/​packet_transport.uart.json Regenerated packet transport metadata.
esphome_device_builder/​definitions/​components/​packet_transport.sx127x.json Regenerated packet transport metadata.
esphome_device_builder/​definitions/​components/​packet_transport.sx126x.json Regenerated packet transport metadata.
esphome_device_builder/​definitions/​components/​packet_transport.espnow.json Regenerated packet transport metadata.
esphome_device_builder/​definitions/​components/​opentherm.json Regenerated component metadata.
esphome_device_builder/​definitions/​components/​lvgl.json Regenerated widget ID and provider metadata.
esphome_device_builder/​definitions/​components/​graph.json Regenerated component metadata.
esphome_device_builder/​definitions/​components/​binary_sensor.analog_threshold.json Regenerated sensor metadata.
esphome_device_builder/​definitions/​automations/​conditions/​sensor.in_range.json Regenerated sensor reference metadata.
esphome_device_builder/​definitions/​automations/​actions/​sensor.template.publish.json Regenerated sensor reference metadata.
docs/​API.md Documents inherited provider paths.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread script/sync_components.py
Comment thread tests/test_sync_components_provides.py
@esphbot

esphbot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Code Review

⚠️ Partial review — 73 file(s) omitted due to diff size and NOT reviewed: esphome_device_builder/definitions/components/wk2168_i2c.json, esphome_device_builder/definitions/components/sensor.ade7880.json, esphome_device_builder/definitions/components/sensor.ade7953_i2c.json, esphome_device_builder/definitions/components/sensor.ade7953_spi.json, esphome_device_builder/definitions/components/sensor.airthings_wave_mini.json, esphome_device_builder/definitions/components/sensor.airthings_wave_plus.json, esphome_device_builder/definitions/components/sensor.atm90e32.json, esphome_device_builder/definitions/components/sensor.bme280_i2c.json, esphome_device_builder/definitions/components/sensor.bme280_spi.json, esphome_device_builder/definitions/components/sensor.bmp280_i2c.json, esphome_device_builder/definitions/components/sensor.bmp280_spi.json, esphome_device_builder/definitions/components/sensor.bmp3xx_i2c.json, esphome_device_builder/definitions/components/sensor.bmp3xx_spi.json, esphome_device_builder/definitions/components/sensor.bmp581_i2c.json, esphome_device_builder/definitions/components/sensor.bmp581_spi.json, esphome_device_builder/definitions/components/sensor.ccs811.json, esphome_device_builder/definitions/components/sensor.combination.json, esphome_device_builder/definitions/components/sensor.dew_point.json, esphome_device_builder/definitions/components/sensor.ens160_i2c.json, esphome_device_builder/definitions/components/sensor.ens160_spi.json, esphome_device_builder/definitions/components/sensor.growatt_solar.json, esphome_device_builder/definitions/components/sensor.havells_solar.json, esphome_device_builder/definitions/components/sensor.ina2xx_i2c.json, esphome_device_builder/definitions/components/sensor.ina2xx_spi.json, esphome_device_builder/definitions/components/sensor.ina3221.json, esphome_device_builder/definitions/components/sensor.integration.json, esphome_device_builder/definitions/components/sensor.ld6002b.json, esphome_device_builder/definitions/components/sensor.ntc.json, esphome_device_builder/definitions/components/sensor.rd03d.json, esphome_device_builder/definitions/components/sensor.resistance.json, esphome_device_builder/definitions/components/sensor.scd4x.json, esphome_device_builder/definitions/components/sensor.sdm_meter.json, esphome_device_builder/definitions/components/sensor.sgp30.json, esphome_device_builder/definitions/components/sensor.sgp4x.json, esphome_device_builder/definitions/components/sensor.spa06_i2c.json, esphome_device_builder/definitions/components/sensor.spa06_spi.json, esphome_device_builder/definitions/components/sensor.total_daily_energy.json, esphome_device_builder/definitions/components/sensor.ufire_ec.json, esphome_device_builder/definitions/components/sensor.ufire_ise.json, esphome_device_builder/definitions/components/wk2132_spi.json, esphome_device_builder/definitions/components/wk2168_spi.json, esphome_device_builder/definitions/components/wk2204_i2c.json, esphome_device_builder/definitions/components/wk2204_spi.json, esphome_device_builder/definitions/components/wk2212_i2c.json, esphome_device_builder/definitions/components/wk2212_spi.json, script/sync_components.py, tests/test_sync_components_provides.py, tests/test_sync_components_reference_classes.py, esphome_device_builder/definitions/components/climate.noblex.json, esphome_device_builder/definitions/components/climate.tcl112.json, esphome_device_builder/definitions/components/climate.zhlt01.json, esphome_device_builder/definitions/components/climate.delonghi.json, esphome_device_builder/definitions/components/climate.daikin_arc.json, esphome_device_builder/definitions/components/climate.hitachi_ac344.json, esphome_device_builder/definitions/components/climate.hitachi_ac424.json, esphome_device_builder/definitions/components/climate.fujitsu_general.json, esphome_device_builder/definitions/components/climate.whynter.json, esphome_device_builder/definitions/components/climate.midea_ir.json, esphome_device_builder/definitions/components/climate.daikin_brc.json, esphome_device_builder/definitions/components/sensor.copy.json, esphome_device_builder/definitions/components/climate.whirlpool.json, esphome_device_builder/definitions/components/climate.toshiba.json, esphome_device_builder/definitions/components/climate.gree.json, esphome_device_builder/definitions/components/climate.climate_ir_lg.json, esphome_device_builder/definitions/components/sensor.absolute_humidity.json, esphome_device_builder/definitions/components/climate.mitsubishi.json, esphome_device_builder/definitions/components/sensor.aqi.json, esphome_device_builder/definitions/components/climate.bang_bang.json, esphome_device_builder/definitions/components/climate.heatpumpir.json, esphome_device_builder/definitions/components/climate.pid.json, esphome_device_builder/definitions/components/climate.template.json, esphome_device_builder/definitions/components/climate.thermostat.json, esphome_device_builder/definitions/components.index.json (partial)

PR Review — Inherit a base's sub-blocks in the id walk so split platforms offer their nested sensor ids

Merge-ready. No code changed since the last review except a merge from main, and the two earlier non-blocking suggestions are still open.

What's solid:

  • Blocks now inherit a base's id and sub-blocks. The regenerated catalog matches the description: 26 new providers (8 wk2xxx hubs and 18 sensor platforms), and no entry loses a field.
  • The pages test now checks every component body, so it can actually fail. No display body gains a pages path.
  • docs/API.md states that paths go only one level into a self-referential schema, as with lvgl.
  • The unreadable-file branch now logs a warning.
  • @bdraco's replies to Copilot's two high-severity findings hold up. The recursion concern is about sub-block descent, which the seen set already prunes. The test fixture has one sensor wrapper, the same shape as the real bundle.

Still open (both non-blocking):

  • lvgl has no test against the shipped catalog, even though it gains key_provider and 39 new paths.
  • A schema file that parses but is not a mapping is still skipped without a log.


Checklist

  • Cycle-breaking on extends terminates for self-referential bases
  • Regenerated catalog matches the described changes
  • New behaviour covered by tests that can fail
  • A skipped schema file is never silent
  • Docs reflect the shipped field semantics
  • Catalog files regenerated, not hand-edited
  • No hardcoded secrets or unsafe deserialization
ℹ️ Triage summary

2 pre-existing finding(s) on unchanged code suppressed (freeze).


Silent Failure Analysis

🟡 **1. MEDIUM** — silent skip of malformed input
script/sync_components.py:4999-5012

Risk: A schema file that parses to a list or a scalar is now dropped without any log, while an unreadable file at least gets a warning. It is also the only new silent path: before this change _collect_referenced_classes walked raw whatever its type. A dropped file leaves _root_entity_classes and the referenced-class set incomplete. The build then writes a catalog that loses nested sensor ids or references_class filters, and nothing signals it.

except (OSError, ValueError):
    _LOGGER.warning("Skipping unreadable schema file %s", path.name)
    continue
if not isinstance(raw, dict):
    continue

Fix: Log a warning for non-dict schema files, as the unreadable-file branch does. Better still, make the sync fail when any schema file is skipped, so a partial bundle can't produce a smaller catalog without anyone noticing.


Automated review by Kōan (Claude) HEAD=b61d659 32s

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

No blocking issues found — ready to merge.

@bdraco
bdraco merged commit 36f8324 into main Sep 23, 2026
24 checks passed
@bdraco
bdraco deleted the split-platform-nested-ids branch September 23, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split _i2c / _spi multi sensor platforms expose no nested sensor ids to reference pickers

3 participants