Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
7ddd953
test(emitter): pin the r202639 conventions and islanding rule the nex…
cayossarian Oct 6, 2026
597b74c
refactor(emitter): build the emitter from one PanelDefinition
cayossarian Oct 6, 2026
a73229c
feat(battery): model the panel's link to its battery
cayossarian Oct 6, 2026
8825c36
refactor(clone): translate any published tree, keeping the panel's name
cayossarian Oct 6, 2026
3646d3b
test(clone): pin that a clone keeps a non-default panel name
cayossarian Oct 6, 2026
1a0e84d
fix(firmware): one firmware string per panel, kept by a clone
cayossarian Oct 6, 2026
e9103fd
test(firmware): share the discharging-meter readout and pin the defau…
cayossarian Oct 6, 2026
93f97f7
feat(circuits): model commissioned PV and battery system circuits
cayossarian Oct 6, 2026
7b7e686
fix(circuits): lock a commissioned circuit's relay in the dashboard a…
cayossarian Oct 6, 2026
b65d83b
feat(pv): one PV device per inverter from SPAN release 202639, kept b…
cayossarian Oct 6, 2026
ef6ffb3
feat(pv): every inverter reports a firmware version
cayossarian Oct 6, 2026
b088332
feat(http): report the hardware version from SPAN release 202639
cayossarian Oct 6, 2026
5876a6e
feat(import): import a panel definition as a config
cayossarian Oct 6, 2026
b3aa722
feat(export): export a saved config as a panel definition
cayossarian Oct 6, 2026
fe41b7d
fix(import): an imported config is unsaved until saved
cayossarian Oct 6, 2026
7019519
build(deps): ebus-panel-sim 0.9.0
cayossarian Oct 6, 2026
d793d4c
feat(configs): ship the defaults on SPAN release 202633 conventions
cayossarian Oct 6, 2026
2bb8b1b
fix(tests): pin only the shipped configs, and say where to set the fi…
cayossarian Oct 6, 2026
a89f104
test(fidelity): measure against upstream's definition API, not a vend…
cayossarian Oct 6, 2026
ce1c582
test(fidelity): explain every baseline entry and refuse repeated role…
cayossarian Oct 6, 2026
092ec55
fix(clone): keep the battery's model and each SPAN Drive's firmware
cayossarian Oct 6, 2026
041e8f6
docs(changelog): note that a re-cloned battery takes its serial-based id
cayossarian Oct 6, 2026
106bace
test(fidelity): keep optional per-cell baselines for the real-panel cell
cayossarian Oct 6, 2026
41133fa
test(fidelity): measure the clone path against a real panel on SPAN r…
cayossarian Oct 6, 2026
e551701
test(fidelity): key SPAN Drives by the circuit that feeds them
cayossarian Oct 6, 2026
806dc66
docs: rehearse a SPAN firmware upgrade, and describe definition impor…
cayossarian Oct 6, 2026
38729f2
test: drop an internal design-note reference from a docstring
cayossarian Oct 6, 2026
74f9459
fix(addon): refresh the shipped default templates on every start
cayossarian Oct 6, 2026
0e28347
fix(dashboard): answer a battery link request without a JSON object w…
cayossarian Oct 6, 2026
c9398ab
docs: give an upgraded inverter circuit a PV template in the firmware…
cayossarian Oct 6, 2026
f0c00d9
docs: name the configs that ship and how a config is chosen
cayossarian Oct 6, 2026
d95c24e
docs(changelog): title the release 2.6.0 and say who the firmware con…
cayossarian Oct 6, 2026
c82d854
fix(engine): let any hybrid inverter make a several-inverter panel is…
cayossarian Oct 6, 2026
e204fe1
chore(release): 2.6.0
cayossarian Oct 6, 2026
1b57332
docs(spec-generator): stop saying the single PV device follows the en…
cayossarian Oct 6, 2026
d1ebe36
fix(pv): bind the pv section's inverter to a circuit, never to list o…
cayossarian Oct 6, 2026
8a11f3c
docs(readme): rehearse the upgrade as a real panel goes through it
cayossarian Oct 6, 2026
5cf8768
fix(dashboard): fetch a read-only template's weather without a script…
cayossarian Oct 6, 2026
e36cd8d
docs(changelog): note the pv section's circuit binding and the read-o…
cayossarian Oct 6, 2026
2eb355d
fix(pv): name every solar device after its own device id
cayossarian Oct 6, 2026
bda2445
fix(dashboard): spell a locked relay non-controllable everywhere a us…
cayossarian Oct 6, 2026
9728bca
fix(config): give a PV nameplate one source, so a dashboard edit surv…
cayossarian Oct 6, 2026
fe3376f
fix(pv): accept a circuit id only in pv.feed
cayossarian Oct 7, 2026
65aa209
fix(config): fold every legacy rating into one source, and refuse a d…
cayossarian Oct 7, 2026
48ddba8
fix(dashboard): never save a config the panel would refuse, and start…
cayossarian Oct 7, 2026
fbfe52a
fix(dashboard): name a locked relay non-controllable in the tooltip, …
cayossarian Oct 7, 2026
3e8ce82
docs(readme): remove all of a second inverter's load overrides, and p…
cayossarian Oct 7, 2026
3d631e2
feat(engine): read one circuit's template and modelled power through …
cayossarian Oct 7, 2026
e89652a
fix(config): let a released dashboard edit win over its template's st…
cayossarian Oct 7, 2026
3679b04
fix(dashboard): move the editor through one place, undo a refused sav…
cayossarian Oct 7, 2026
4697d51
fix(config): keep a shared template's override, and say how to clear …
cayossarian Oct 7, 2026
35546a9
fix(dashboard): refuse an edit the panel would not load, not the save…
cayossarian Oct 7, 2026
ffe3529
test: share the rating helpers, and guard the released template files…
cayossarian Oct 7, 2026
b76edd8
docs(engine): say modelled_circuit_power runs on the engine's event loop
cayossarian Oct 7, 2026
40240fd
fix(dashboard): open a config before starting or restarting it, and s…
cayossarian Oct 7, 2026
52879c8
fix(dashboard): gate every store write, and answer an edit's own bad …
cayossarian Oct 7, 2026
180684e
fix(dashboard): show a refused rate choice in its dialog, and keep th…
cayossarian Oct 7, 2026
bd57f93
test(dashboard): pin that Start and Restart request a good config and…
cayossarian Oct 7, 2026
4180d2f
docs(changelog): note the new circuit's placement, and say the sim/v0…
cayossarian Oct 7, 2026
4728868
docs(addon): bring the add-on store changelog up to date through 2.6.0
cayossarian Oct 7, 2026
7896c45
fix(dashboard): say a panel-source sync needs saving, and finish the …
cayossarian Oct 7, 2026
e772a0d
feat(validation): warn about a solar circuit on one tab
cayossarian Oct 7, 2026
6aa1682
docs(readme): put the rehearsal's second inverter on a two-pole circuit
cayossarian Oct 7, 2026
b875244
fix(rehearsal): lock the second inverter's circuit on release 202639
cayossarian Oct 7, 2026
61b16bc
docs(changelog): note the solar circuit warnings, and date 2.6.0
cayossarian Oct 8, 2026
1fa43cf
fix(engine): seed a panel's weather from a digest of its serial
cayossarian Oct 8, 2026
1601a13
fix(mqtt): act on Home Assistant's commands and survive a broker restart
cayossarian Oct 8, 2026
d4c6e50
docs(changelog): note the command, reconnect and weather fixes for 2.6.0
cayossarian Oct 8, 2026
9273054
fix(clone): read commissioning, size and site values from what the pa…
cayossarian Oct 8, 2026
f3f8538
fix(clone): register with a panel once and keep its secrets out of co…
cayossarian Oct 8, 2026
1adbd4d
docs(changelog): note the clone fixes for 2.6.0
cayossarian Oct 8, 2026
251f06b
fix(mqtt): end a session an operation failed on, and leave no link be…
cayossarian Oct 8, 2026
148f3b0
ci: run the test suite, with a real broker, and lint on Python 3.14
cayossarian Oct 8, 2026
c82f3ec
fix(scraper): dial the panel the user named, and wait for its whole tree
cayossarian Oct 8, 2026
07d52e0
fix(clone): keep what the panel publishes, and invent none of it
cayossarian Oct 8, 2026
aa0edf5
test(fidelity): clone the captured MAIN 32 and rehearsal-after, and d…
cayossarian Oct 8, 2026
f434428
docs(changelog): note the broker, scraper and clone fidelity fixes fo…
cayossarian Oct 8, 2026
301e510
fix(wire): name the battery, its MID and the lugs after their device ids
cayossarian Oct 8, 2026
153976f
test(fidelity): build the rehearsal-after panel in the test, not from…
cayossarian Oct 8, 2026
170491e
fix(clone): carry the panel's network, status and model, and pin ever…
cayossarian Oct 8, 2026
c3036cc
fix(scraper): register again only when the broker refuses the credent…
cayossarian Oct 8, 2026
a983e32
fix(mqtt): withdraw a connect that timed out, and leave a failed sess…
cayossarian Oct 8, 2026
b1c64a8
fix(secrets): never overwrite a store it cannot read, and never stop …
cayossarian Oct 8, 2026
cedb138
docs(changelog): describe the re-review fixes against 2.5.3
cayossarian Oct 8, 2026
3462aff
fix(hardware): default the hardware version to the form a SPAN panel …
cayossarian Oct 8, 2026
db8a670
build(deps): bound aiomqtt and paho-mqtt to the range the broker link…
cayossarian Oct 8, 2026
7a8d81f
fix(broker): leave a failed session without a DISCONNECT however it ends
cayossarian Oct 8, 2026
5a9271b
test(scraper): pin that only a credentials CONNACK is a refusal
cayossarian Oct 8, 2026
e8bcda4
fix(clone): leave the panel's door and cloud state to the emulator
cayossarian Oct 8, 2026
6ccea4a
fix(app): advertise the model the panel publishes
cayossarian Oct 8, 2026
e690518
ci: fail the tests when uv.lock disagrees with pyproject.toml
cayossarian Oct 8, 2026
ad47d52
feat(configs): ship the templates on SPAN firmware release 202639
cayossarian Oct 8, 2026
1214c5b
docs: describe the release 202639 templates and emulating release 202633
cayossarian Oct 8, 2026
2610a02
docs(changelog): say the solar circuit is named as SPAN firmware r202…
cayossarian Oct 8, 2026
3325cb2
docs(changelog): date 2.6.0 2026-10-08
cayossarian Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
version: 2
updates:
- package-ecosystem: "pip"
# uv, not pip: pip's updater bumps pyproject.toml and leaves uv.lock behind, and
# the tests workflow syncs with --locked, so every such PR would fail. uv's
# updater moves both together.
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 1
ignore:
# `ebus-panel-sim` 0.8.0 declares `ebus-sdk<0.23,>=0.20.1`, so anything from
# `ebus-panel-sim` 0.9.0 declares `ebus-sdk<0.23,>=0.20.1`, so anything from
# 0.23 up makes `pip install .` unresolvable and breaks the add-on image
# build — which is the only job that installs from `pyproject.toml`, so lint
# passes and the failure lands on `main` after the merge. Lift this when the
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ jobs:
steps:
- uses: actions/checkout@v7

# requires-python is >=3.14. The hooks run in environments of their own, which
# is why 3.13 worked, but nothing should depend on that.
- uses: actions/setup-python@v7
with:
python-version: "3.13"
python-version: "3.14"

- name: Install pre-commit
run: pip install pre-commit
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Tests

# Nothing ran the test suite in CI before this: `lint.yaml` runs pre-commit, whose
# mypy hook covers `src/panelbench` and not the tests. A regression in code the
# suite pins, the broker link above all, would have shipped green.
#
# mosquitto comes from apt so the real-broker tests in tests/integration run here.
# They skip where mosquitto is missing, which is right on a laptop and wrong here:
# PANELBENCH_REQUIRE_MOSQUITTO turns that skip into a failure, so a runner image
# that loses the package cannot pass by testing less.
#
# --locked, not --frozen: --frozen installs whatever uv.lock says even when
# pyproject.toml has moved on, so a dependency bound changed without a re-lock
# would be tested against the old resolution and pass. --locked fails instead.

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

# The interpreter comes from .python-version, which matches requires-python.
- uses: astral-sh/setup-uv@v6

- name: Install mosquitto
run: sudo apt-get update && sudo apt-get install -y mosquitto

- name: Install dependencies
run: uv sync --locked --all-groups

- name: Run tests
env:
PANELBENCH_REQUIRE_MOSQUITTO: "1"
run: uv run --locked pytest -q -rs
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ configs/*_history.db
configs/*.yaml
!configs/default_*.yaml

# Source panels' passphrases and broker credentials (panel_secrets.py)
configs/.secrets/

# Local-developer source overrides + env. See .env.example and uv.toml.example.
.env
uv.toml
8 changes: 6 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Upstream's data, vendored byte-identical; test_vendored_example_matches_the_pinned_release
# compares the bytes with the pinned release, so no hook may rewrite them.
exclude: ^tests/fidelity/fixtures/upstream/.*\.(ya?ml|json)$
repos:
- repo: local
hooks:
Expand Down Expand Up @@ -37,9 +40,10 @@ repos:
# released versions from PyPI rather than a git ref: `ebus-panel-sim` used to
# track the fork's moving `spanpanel/main` branch, so this hook could resolve a
# different build on two consecutive runs with nothing in the repo changing.
- ebus-panel-sim==0.8.0
- ebus-panel-sim==0.9.0
- ebus-sdk==0.22.0
- aiomqtt>=2.0.0
- aiomqtt>=2.5.1,<2.6
- paho-mqtt>=2.1,<2.2
- aiohttp>=3.9.0
- aiohttp-jinja2>=1.6
- jinja2>=3.1.0
Expand Down
10 changes: 5 additions & 5 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ build/
.pytest_cache/

# Byte-vendored from the eBus emitter as the fidelity reference. PROVENANCE.md makes
# byte-identity with upstream's own files the property that gives them their value, and
# `test_vendored_reference_matches_upstream` asserts it, so a formatter run here breaks the
# comparison and turns every re-sync into a merge conflict. `pyproject.toml` already excludes
# the directory from ruff for the same reason; Prettier was the half nobody had covered, and
# `scripts/fix-markdown.sh` rewrote PROVENANCE.md on its next run.
# byte-identity with the pinned release's own files the property that gives them their value,
# and `test_vendored_example_matches_the_pinned_release` asserts it, so a formatter run here
# breaks the comparison and turns every re-sync into a merge conflict. `.pre-commit-config.yaml`
# excludes the vendored files from every hook for the same reason; Prettier is the half it does
# not cover, and `scripts/fix-markdown.sh` once rewrote PROVENANCE.md on its next run.
tests/fidelity/fixtures/upstream/

# Match .gitignore: unpublished planning docs
Expand Down
101 changes: 101 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,106 @@
# Changelog

## 2.6.0 — SPAN firmware release 202639

**The shipped templates now publish SPAN firmware release 202639's conventions, and a config naming a 202633 release keeps that release's.** The add-on
refreshes its shipped templates on every start, so an upgraded install's templates publish release 202639's. Use this release with SPAN integration 2.1.2 or
later, since against release 202639 integration 2.1.1 shows the battery's Meter Power with its sign flipped. A config or clone whose `firmware_version` names
no SPAN release publishes release 202639's conventions too, so it changes on upgrade, and that includes a clone of the earlier MAIN 40 template, which named
`sim/v0.1.0`. To keep the earlier conventions, set its `firmware_version` to a 202633 release such as `spanos3/r202633/02`, or, for a clone of a panel, to the
source panel's firmware; a clone of one of these templates also needs `commissioned_system` removed from its solar template, which an earlier release refuses.

### Added

- **The dashboard can set the health of the panel's link to its battery**, which is what a SPAN panel requires before it honours Home Assistant's dominant power
source control.
- **Commissioned PV and battery system circuits**, locked as SPAN firmware release 202639 locks them, are modelled and kept by a clone, which recognises one by
its locks and the inverter or battery it feeds whatever it is named, and a config naming an earlier release is refused if it asks for one.
- **A clone of a panel keeps the panel's time zone and line voltages**, and its time zone wins over one derived from Home Assistant's home location.
- **The log warns when a clone gives the main breaker a default rating** because the panel publishes none.
- **The dashboard's Clone accepts broker credentials you already hold for a panel in place of its passphrase**, and then registers nothing with the panel.
- **The log warns about a solar circuit on a breaker whose template lacks `commissioned_system: pv` when the config's `firmware_version` names SPAN release
202639 or later, or no SPAN release**, since that release locks such a circuit, though the config still loads.
- **The log warns about a solar circuit on a single tab**, since a grid-tied inverter is 240 V on a two-pole breaker with two tabs on opposite legs, though the
config still loads.
- **From SPAN firmware release 202639, a panel's status endpoint reports its hardware version**, as SPAN's does: `1.2` or `2.0` from the config's
`hardware_version`, and `UNKNOWN` for any other value.
- **The dashboard's Import accepts a panel definition file**, such as one `panel-sim-capture` wrote from a live panel, and turns it into a PanelBench config.
- **The dashboard can export a saved config as a panel definition file**, the makeup other eBus tools read, without PanelBench's behaviour settings.

### Changed

- **Home Assistant's dominant power source control is now ignored while the panel's link to its battery is healthy**, as a SPAN panel ignores it.
- **The battery's own power reading is now positive while discharging**, as SPAN firmware r202639 publishes it, unless the panel's firmware version names an
earlier release.
- **A SPAN Drive's user charge limit is no longer published until a user sets one**, as on SPAN firmware r202639, unless the panel's firmware version names an
earlier release.
- **The shipped templates now report firmware `spanos3/r202639/03` and publish SPAN release 202639's conventions**, as a SPAN panel after that upgrade
does, and a config naming a 202633 release, such as a clone of a template set to `spanos3/r202633/02`, keeps that release's conventions.
- **The shipped templates' solar circuit is now named "Commissioned PV System", as SPAN firmware r202639 names it**, and locked as that release locks it, with
its relay not switchable and its priority fixed at `NEVER`, where it was named "Solar Inverter".
- **A panel without a battery no longer publishes load-shed state**, as the eBus specification requires.
- **A config with two or more solar circuits now publishes each inverter as its own solar device, under new device ids**, unless its `firmware_version` names a
SPAN release before 202639, as SPAN firmware does from that release, and a clone keeps every inverter.
- **The top-level `pv` section now describes the inverter whose circuit `pv.feed` names by its `id`, or the only solar circuit's, where it followed whichever
solar circuit was listed first**; a `pv.feed` that names no solar circuit's `id` is now refused, as is a config with several solar circuits and no `pv.feed`
whose `firmware_version` names a release before 202639.
- **A solar rating set in the dashboard now replaces the stale copy a clone of the shipped MAIN 40 or MAIN 32 template kept**, at the template's top level or in
the solar circuit's `overrides`, and the log warns with both values and the file until the config is saved in the dashboard.
- **A `pv.nameplate_capacity_w`, or a circuit's `overrides` rating on a template other circuits share, that disagrees with the template's `energy_profile`
rating is now refused, naming both**, where the two could each win somewhere different; an equal or lone legacy rating moves into the template's
`energy_profile`.
- **Every solar device, battery, battery MID and set of lugs is now named after its own device id**, as SPAN firmware release 202639 names them, where they
were named "Solar", "Battery", "Microgrid Interconnect Device" and "Upstream lugs"; a circuit keeps its own name.
- **The shipped configs, and any config that sets no `hardware_version`, now report hardware version `1.2`**, as a SPAN panel publishes it, where they reported
`rev2`; a config copied from an earlier template keeps its `rev2`, which the status endpoint reports as `UNKNOWN` from release 202639.
- **A new clone of a panel whose battery publishes a serial number identifies that battery by its serial, as the source panel does**, so re-cloning such a panel
gives its battery, and the MID whose id follows it as `<bess-id>-mid`, a new device id, while existing clone configs are unchanged.

### Fixed

- **Commands from Home Assistant now take effect**, such as a circuit's relay or shed priority, where PanelBench subscribed to them and then never read them.
- **PanelBench reconnects after its MQTT broker restarts or stops answering, and publishes its panels again**, where it stayed disconnected, or on a broker
that had stopped answering, until it was restarted itself, and logged an error for every topic it could not publish.
- **A config naming the serial of a panel already running is refused, naming the config that runs it**, where both started, the second silently taking over
the first's broker connection and topics.
- **A panel that fails as it starts no longer leaves its broker connection open**, where it stayed open under the panel's identity until the panel started
again.
- **Cloning or syncing a panel connects to its broker at the address you gave**, where it dialled the panel's `.local` name, which does not resolve across
subnets, and reported a panel with no circuits.
- **A panel's solar output no longer changes when PanelBench restarts**, where each start gave the same panel different weather.
- **PanelBench registers with a panel once and reuses the credentials it is given for every later clone, sync and restore**, registering again only if the
panel's broker refuses them, where each one left another client on the panel for its owner to remove.
- **A panel's passphrase is no longer written into its clone's config, an export of it, or a text field**, but kept with the panel's broker credentials in a
file only PanelBench reads, and a clone that still carries one gives it up the next time PanelBench reads it.
- **A clone takes its size from the panel's model**, where a panel whose highest spaces were empty was cloned as a smaller one.
- **A clone's energy readings carry on from its panel's**, where they started at zero.
- **A clone keeps its panel's PCS priorities, vendor name, model, off-grid shed threshold and battery MID identity**, where it gave PanelBench's own.
- **A clone keeps its panel's network links and Wi-Fi network or the absence of one**, where a panel on Ethernet cloned as one on Wi-Fi with PanelBench's own
network.
- **A clone's battery charges and discharges at the battery's rated power**, or 5 kW per 13.5 kWh where the battery publishes none, where every battery was
held to 3.5 kW.
- **A clone records a breaker rating its panel does not publish as absent**, where it wrote 20 A into the config.
- **Restoring a circuit from a panel that cannot be reached says why**, where it left the circuit as it was without a word.
- **An upgraded add-on now refreshes its shipped `default_*` templates on every start**, where it kept the copies an earlier release laid down and published
whatever firmware they named.
- **The dashboard shows a panel as islandable when any of its solar inverters is hybrid**, where only the first solar circuit listed decided it.
- **A panel's HTTP status, Homie schema endpoint and mDNS advertisement now report the same firmware version it publishes over MQTT.**
- **A cloned panel keeps the source panel's firmware version**, so a clone of a panel on a SPAN release before 202639 keeps that release's battery power sign
and SPAN Drive charge limit.
- **A cloned panel keeps each solar inverter's vendor, model, serial number and firmware version.**
- **A cloned panel keeps its battery's model and each SPAN Drive's serial number and firmware version.**
- **A cloned panel keeps the source panel's hardware version.**
- **A cloned panel keeps the source panel's name** instead of the generic "Span Panel".
- **An imported config counts as unsaved until it is saved**, so switching panels asks before discarding it.
- **The dashboard now fetches a read-only template's weather**, where a script error on its missing location search stopped it.
- **The dashboard refuses an edit the panel would not load, says why and keeps the config as it was, and starts when the active config is one the panel
refuses**, where such an edit was saved and the panel then failed to restart, and an add-on restart with it active left no dashboard to fix it from.
- **The dashboard's Add Entity places a new circuit on the first free space**, where it made a circuit with no space, which the panel refused.
- **The dashboard's relay control shows a locked relay however its config spells it**, where a circuit written `non-controllable`, as the shipped configs write
it, showed `controllable` and saving its form unlocked it.
- **A solar inverter's nameplate edited in the dashboard now survives a restart and is the rating the panel both publishes and produces at**, including in a
clone of the shipped MAIN 40 or MAIN 32 template, where the template's stale copy of the rating replaced the edit when the panel started.

## 2.5.3 — two simulated panels no longer publish over each other

**A simulated panel already added to Home Assistant has to be removed and re-added**, because its circuits get new device ids and so its circuit entities get
Expand Down
Loading
Loading