Skip to content

Surface project_name / project_version / network for configured devices - #2692

Open
davidcoulson wants to merge 1 commit into
esphome:mainfrom
davidcoulson:project-txt-columns
Open

davidcoulson wants to merge 1 commit into
esphome:mainfrom
davidcoulson:project-txt-columns

Conversation

@davidcoulson

@davidcoulson davidcoulson commented Sep 12, 2026 •

Copy link
Copy Markdown

What does this implement/fix?

AI-assisted contribution. This change was written with AI assistance (Claude Code). I've reviewed the diff myself and verified it locally — full test suite, type-check, lint and format all clean (details in the checklist below). Flagging it up front so you can calibrate review depth.

ESPHome broadcasts project_name, project_version and network in the same mDNS TXT as the identity trio (version / config_hash / mac), but only the adoption path ever read them — AdoptableDevice carries all three, while a configured device dropped them on the floor. So the device table has nothing to sort or filter a fleet by, even though the data is already on the wire:

board          esp32-c3
config_hash    8600af66
friendly_name  Eilee Bedroom RRN00 4e88e8
mac            dc06754e88e8
network        wifi
platform       ESP32
project_name   dcoulson.ble-rrn00-wroom06
project_version 2026.09.06.0+ble2026.09.12.0
version        2026.8.2

This surfaces all three on DeviceRuntimeState for configured devices.

Descriptive, not identity. The new keys are ingested through their own _DESCRIPTIVE_TXT_APPLIERS table rather than joining _IDENTITY_TXT_APPLIERS. That separation is load-bearing: the identity appliers' presence is what stamps deployed_identity_live, and a project_name-only TXT that vouched for identity freshness would stop the verify-resolve loop from ever re-resolving. They also gate no update or pending-changes verdict. Both halves are pinned by tests.

Applied on both the _esphomelib._tcp and _http._tcp paths — ESPHome publishes the same descriptive set on whichever service the device has, so no-API devices get them too.

Persisted to the per-flavor device store alongside deployed_version, for the same reason that one is: an mDNS-only field leaves offline rows permanently blank and silently drops them out of a project filter.

One implementation note for reviewers. DeviceStateMonitor sits at exactly ruff's PLR0904 ceiling of 20 public methods, and the policy in pyproject.toml is explicit that the rule exists to catch new growth, with existing offenders grandfathered rather than new ones added. So instead of three public apply_* siblings of apply_version, the appliers go through one private _apply_descriptive_observation driven by a (txt_key, field, callback selector) table. Every entry is the same differ-gate-then-forward, so the table loses nothing — but say the word if you'd rather have the explicit trio and a threshold bump.

Related issue or feature (if applicable):

  • n/a — no filed issue; the mDNS TXT already carries the data.

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

The frontend PR adds the Project / Project Version / Network columns and the Project / Network facets that consume these fields. This PR is inert without it (the values ride runtime_state and simply go unrendered); that PR without this one renders three empty columns and self-hides both facets.

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.

Verification: 8587 passed, 26 skipped; mypy esphome_device_builder clean; ruff check + ruff format --check clean.

🤖 Generated with Claude Code

ESPHome broadcasts the running firmware's ``esphome: project:`` pair
and the link it announced over in the same mDNS TXT as the identity
trio, but only the adoption path ever read them — a configured device
dropped all three on the floor, so the device table had nothing to
sort or filter a fleet by.

Ingest them through a descriptive applier table kept separate from
``_IDENTITY_TXT_APPLIERS``: these keys describe the firmware and never
vouch for it, so a project-only TXT must not stamp
``deployed_identity_live`` and latch the verify-resolve loop off. The
table holds ``(key, field, callback selector)`` triples driven through
one private monitor applier rather than three public ``apply_*``
sisters — every entry is the same differ-gate-then-forward, and the
monitor is at its PLR0904 ceiling.

Persisted to the per-flavor device store alongside ``deployed_version``
for the same reason that one is: an mDNS-only field leaves offline
rows permanently blank and silently drops them out of a project
filter.
@codspeed

codspeed Bot commented Sep 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 28 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing davidcoulson:project-txt-columns (61e306c) with main (761af30)

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 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.73%. Comparing base (761af30) to head (61e306c).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #2692   +/-   ##
=======================================
  Coverage   99.73%   99.73%           
=======================================
  Files         283      283           
  Lines       24149    24188   +39     
=======================================
+ Hits        24086    24125   +39     
  Misses         63       63           
Flag Coverage Δ
py3.12 99.69% <100.00%> (-0.01%) ⬇️
py3.14 99.57% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
...home_device_builder/controllers/_device_scanner.py 100.00% <100.00%> (ø)
...er/controllers/_device_state_monitor/controller.py 99.15% <100.00%> (+0.03%) ⬆️
..._builder/controllers/_device_state_monitor/mdns.py 99.63% <100.00%> (+0.01%) ⬆️
...ice_builder/controllers/devices/_metadata_store.py 100.00% <ø> (ø)
...e_device_builder/controllers/devices/controller.py 100.00% <100.00%> (ø)
...ome_device_builder/controllers/devices/metadata.py 100.00% <100.00%> (ø)
...ice_builder/controllers/devices/state_callbacks.py 100.00% <100.00%> (ø)
...ome_device_builder/helpers/device_yaml/_loading.py 100.00% <ø> (ø)
esphome_device_builder/models/devices.py 100.00% <100.00%> (ø)
🚀 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.

@davidcoulson

Copy link
Copy Markdown
Author

Some deployment validation from running this branch against my own fleet (157 configured devices).

The gap on dev today. A device's runtime_state from devices/list currently carries eight keys:

active_source, api_encryption_active, deployed_config_hash,
deployed_identity_live, deployed_version, ip_addresses,
queued_update, state

Meanwhile the device itself is advertising this over mDNS right now:

project_name    = dcoulson.lumary-esp8685
project_version = 2026.09.12.0
network         = wifi
board           = esp32-c3
platform        = ESP32
mac             = dc06754e7dac
config_hash     = 600c84cd
version         = 2026.8.2
friendly_name   = Basement Lumary 4e7dac

Three keys already on the wire, already in the TXT record this code path parses — just not surfaced.

Scale on a real fleet. 33 of my 157 devices share that single project_name / project_version pair: one firmware cohort I currently have no way to select or sort on in the dashboard. Running this branch, 111 of the 157 populated at least one of the three fields, and the Project facet became searchable on its own once the distinct-value count crossed the threshold.

network is the one I'd have underrated from the diff alone. It's the only place the dashboard can tell you wifi vs ethernet, which is exactly what you want to know before deciding what's safe to OTA.

Happy to pull more numbers off this fleet if that would help review.


Disclosure: this PR and this comment were written with AI assistance (Claude Code). I've reviewed the change and verified the test suite, mypy, and ruff locally, in addition to the deployment described above.

@jesserockz

Copy link
Copy Markdown
Member

Thanks for your contributions.

I'm not sure I see the usefulness of the project name and version for the majority of users that would be using the ESPHome Device Builder.

The project name and version are fields to be used by makers of devices that are providing / selling them and that users are simply on-boarding to their network and using.

For this reason we don't think at this time that we want to have this extra code to have them showing in the tables.

The network type is something we are willing to merge in and would like you to split that out so we can get it in there.

Jesse

@davidcoulson

Copy link
Copy Markdown
Author

Maybe I'm abnormal (wouldn't be the first time), but I use project name/version extensively in my own projects. For example, I have 60+ Lumary lights running ESPHome on esp32-c3s instead of using the tuya MCUs. There's maybe 3-4 different models of lights, so each model has a different project name that maps to the actual project.

99% of the time I am making a bulk change to one type of light (or as population that have addressable LEDs, etc), so being able to filter by project name then 'select all' when doing select multiple would be super helpful. There's always the odd one that chokes during an update bc 2.4ghz wifi is what it is, so being able to find that one 'old project version' out of a list of 60+ things is nice. I've been exposing that sensor into HA then using that to drive a build list, but it should all be doable in ESPHome Builder.

I know there are labels and I've manually added Labels to a bunch of projects, but I've not figured out a good way to handle that automatically and the volume of ESPHome devices I have makes maintaining that super painful. If there is a better approach so I can use labels in a more useful way and not depend on project sensors LMK and I'll be happy to shift my focus :)

@bdraco

bdraco commented Sep 26, 2026

Copy link
Copy Markdown
Member

The project field is intended for importable (usually off the shelf or Made for ESPHome) devices. If you pull those fields out we can merge the network one

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants