From b6350a17530bf0f517d379a51390dbe368935cd2 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:13:39 -0700 Subject: [PATCH 1/3] fix(dev-setup): install ebus-emitter from its lock instead of re-resolving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `uv pip install --editable ` re-resolves the emitter's dependency constraints against PyPI and ignores the emitter's own uv.lock. Its `ebus-sdk>=0.1.5` constraint therefore resolved to 0.12.0 on a fresh bootstrap, pulling in an SDK the emitter has never been tested against. ebus-sdk 0.2.0 changed the `Device` constructor's `mqtt_cfg` default from `{}` to `None` (alongside the parent/child tree rework). The emitter's build_graph() constructs the root Device as a passive topic/schema model with no mqtt_cfg, so `connect_broker()` dereferenced None and every panel failed to start: AttributeError: 'NoneType' object has no attribute 'get' tests/test_panel.py::TestPanelInstance::test_start_and_stop already covered this — it just had not been re-run after the venv drifted. Install the emitter's locked runtime dependencies first (`uv export` from its lock), then the emitter itself with `--no-deps` so nothing is re-resolved. The venv now matches exactly what the emitter pins. ebus-emitter carries a matching `ebus-sdk<0.2` upper bound so the constraint is enforced declaratively as well. --- CHANGELOG.md | 1 + scripts/dev-setup.sh | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40d5bea..2830649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,4 +11,5 @@ ### Fixed +- **Dev bootstrap dependency drift**: `scripts/dev-setup.sh` installed `ebus-emitter` with a bare `uv pip install --editable`, which re-resolves the emitter's dependency constraints against PyPI and ignores its `uv.lock`. A fresh bootstrap pulled `ebus-sdk` 0.12.0 — whose `Device` constructor is incompatible with the 0.1.x API the emitter targets — and panel startup died with `AttributeError: 'NoneType' object has no attribute 'get'` in `connect_broker()`. The script now installs the emitter's locked runtime dependencies first, then the emitter itself with `--no-deps`, so the venv matches what the emitter pins. - **Type safety**: Fixed mypy error in simulator runtime (`_first_feed_for_device_type`) where template_name could be None. diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh index be70b51..c6e0452 100755 --- a/scripts/dev-setup.sh +++ b/scripts/dev-setup.sh @@ -35,7 +35,18 @@ fi echo "Syncing simulator deps (excluding ebus-emitter, which is local)…" uv sync --group dev --no-install-package ebus-emitter -echo "Installing ebus-emitter from ${EBUS_EMITTER_PATH} (editable)…" -uv pip install --editable "${EBUS_EMITTER_PATH}" +# Install the emitter's *locked* runtime deps, then the emitter itself with +# --no-deps. A bare `uv pip install --editable ` re-resolves the emitter's +# constraints against PyPI and ignores its uv.lock, so a fresh bootstrap can pull +# a transitive dependency the emitter was never tested against — that is how +# ebus-sdk 0.12.0 (whose Device constructor is incompatible with the 0.1.x API +# the emitter targets) landed in this venv and broke `span-simulator` at startup. +# Sourcing from the lock keeps this venv on exactly what the emitter pins. +echo "Installing ebus-emitter's locked runtime deps…" +uv export --project "${EBUS_EMITTER_PATH}" --no-dev --no-emit-project --no-hashes \ + | uv pip install --requirements - + +echo "Installing ebus-emitter from ${EBUS_EMITTER_PATH} (editable, no re-resolution)…" +uv pip install --no-deps --editable "${EBUS_EMITTER_PATH}" echo "Done. Verify with: uv run python -c 'import ebus_emitter; print(ebus_emitter.__file__)'" From 6d65302497262beadbd8ae58d7e534ee234cafcd Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:15:21 -0700 Subject: [PATCH 2/3] chore: bump version to 1.0.12 and stamp the changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the release/v1.0.12 branch. sync-version.sh propagated the pyproject version to __init__.py, config.yaml, and the Dockerfile label. The root CHANGELOG's `[unreleased]` section — emitter live-schema alignment and abstraction, plus the dev-bootstrap dependency-drift fix — is now stamped as 1.0.12. --- CHANGELOG.md | 2 +- pyproject.toml | 2 +- span_panel_simulator/Dockerfile | 2 +- span_panel_simulator/config.yaml | 2 +- src/span_panel_simulator/__init__.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2830649..45baf38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [unreleased] — emitter live-schema alignment and abstraction (feat/emitter-integration) +## 1.0.12 — 2026-07-30 — emitter live-schema alignment and abstraction ### Changed diff --git a/pyproject.toml b/pyproject.toml index cd1f29b..95c7d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "span-panel-simulator" -version = "1.0.11" +version = "1.0.12" description = "Standalone eBus simulator for SPAN panels" requires-python = ">=3.14" dependencies = [ diff --git a/span_panel_simulator/Dockerfile b/span_panel_simulator/Dockerfile index 3c47664..26e9098 100644 --- a/span_panel_simulator/Dockerfile +++ b/span_panel_simulator/Dockerfile @@ -32,7 +32,7 @@ EXPOSE 18883 8081 18080 LABEL io.hass.name="SPAN Panel Simulator" \ io.hass.description="Simulates a SPAN electrical panel for testing and upgrade modeling" \ io.hass.type="addon" \ - io.hass.version="1.0.11" \ + io.hass.version="1.0.12" \ io.hass.arch="aarch64|amd64" CMD ["/run.sh"] diff --git a/span_panel_simulator/config.yaml b/span_panel_simulator/config.yaml index e96e05d..4c987e6 100644 --- a/span_panel_simulator/config.yaml +++ b/span_panel_simulator/config.yaml @@ -1,6 +1,6 @@ name: "SPAN Panel Simulator" description: "Simulates a SPAN electrical panel for testing and upgrade modeling" -version: "1.0.11" +version: "1.0.12" slug: "span_panel_simulator" url: "https://github.com/SpanPanel/simulator" image: "ghcr.io/spanpanel/simulator/{arch}" diff --git a/src/span_panel_simulator/__init__.py b/src/span_panel_simulator/__init__.py index 156f0f0..7c78be3 100644 --- a/src/span_panel_simulator/__init__.py +++ b/src/span_panel_simulator/__init__.py @@ -1,3 +1,3 @@ """Standalone eBus simulator for SPAN panels.""" -__version__ = "1.0.11" +__version__ = "1.0.12" From 130fd5d9c7dd7e5f0e859075ee9ff26cf5d90ce7 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:32:59 -0700 Subject: [PATCH 3/3] docs(changelog): add 1.0.12 add-on changelog entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The add-on changelog stopped at 1.0.11, so 1.0.12 would have shipped with no user-facing release notes. Covers the emitter cutover and the schema changes downstream consumers will notice — flat Homie 5 layout, renamed lugs and firmware-version keys, multi-EVSE publishing, per-panel MQTT connections, retained-topic cleanup on stop — plus the startup failure caused by the unpinned emitter dependency resolve. --- span_panel_simulator/CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/span_panel_simulator/CHANGELOG.md b/span_panel_simulator/CHANGELOG.md index af6bea7..5e442f7 100644 --- a/span_panel_simulator/CHANGELOG.md +++ b/span_panel_simulator/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 1.0.12 — 2026-07-30 + +### Emitter & Homie Schema + +- MQTT publishing and per-tick property emission now run through the external `ebus-emitter` package. The simulator builds a device manifest per panel and drives + the emitter each tick, replacing the built-in publisher +- Published topics follow the emitter's live SPAN panel Homie 5 schema — flat node layout with topology and properties matching real hardware, so downstream + consumers (span-panel-api, span-card) see the same structure a physical panel emits +- Lugs node IDs renamed to `lugs-upstream` and `lugs-downstream` to match the emitter convention +- Device metadata key `software-version` renamed to `firmware-version` +- Multiple EVSE devices per panel are now published: every circuit whose template is tagged `device_type: evse` gets its own EVSE device (`evse`, `evse-2`, …) + driven by that circuit's power, instead of a single hardcoded EVSE +- BESS and PV device feeds and metadata are derived from circuit templates via stable circuit UUIDs rather than being configured independently +- Each panel now owns its own MQTT connection and subscribes to its own `/set` topics, replacing the single shared connection and the central topic router +- Stopping a panel clears its retained topics, so a shut-down clone no longer leaves stale state on the broker + +### Fixes + +- Panel startup no longer fails with `AttributeError: 'NoneType' object has no attribute 'get'`. The developer bootstrap script re-resolved the emitter's + dependencies against PyPI instead of its lock file and pulled an `ebus-sdk` release incompatible with the `Device` API the emitter targets; setup now installs + the emitter's locked dependency set + ## 1.0.11 — 2026-04-19 ### Fixes