ebus-panel-sim 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/CHANGELOG.md +6 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/CONTRIBUTING.md +14 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/DESIGN.md +1 -1
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/DEVELOPER.md +3 -4
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/PKG-INFO +1 -1
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/__init__.py +1 -1
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/_sdk_seam.py +15 -1
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_teardown.py +22 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.ebus-spec.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.github/CODEOWNERS +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.github/workflows/ci.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.github/workflows/publish.yml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.gitignore +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.pre-commit-config.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/.python-version +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/AUTHORS +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/LICENSE +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/README.md +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/examples/forty_tab_minimal.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/examples/run_forty_tab_minimal.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/pyproject.toml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/conventions/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/conventions/tab_legs.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/emitter.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/energy_integrator.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/exceptions.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/manifest.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/manifest_physics.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/native_devices/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/native_devices/bess.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/native_devices/load_shedding.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/native_devices/protocol.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/panel_meter.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/py.typed +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/relay_resolver.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/snapshot.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/tick_inputs.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/bag_builder.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/breaker.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/charge-limit.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/connection.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/door.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/grid.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/info.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/load-shed.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/meter.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/pcs.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/power-flows.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/shed-forecast.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/shed.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/soc.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/status.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/switch.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/graph_builder.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/.gitkeep +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/bess.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/circuit.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/evse.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/lugs.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/mid.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/panel.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping/pv.yaml +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/mapping_loader.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profile_loader.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/.gitkeep +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/bess.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/circuit.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/evse.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/lugs.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/mid.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/panel.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/pv.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/circuit.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/evse.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/lugs.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/panel.json +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/property_bag.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/publisher.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/set_router.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/conftest.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/conventions/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/conventions/test_tab_legs.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_catalog_drift.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_connection.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_emitter_public_surface.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_energy_integrator.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_exceptions.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_manifest.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_manifest_physics.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_mid_placement.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_panel_meter.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_publish_tick.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_relay_resolver.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_shed_forecast.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_tick_inputs.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_variant.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/test_wire_units.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/__init__.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_circuit_energy_frame.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_graph_builder.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_graph_builder_topology.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_profile_mapping_validation.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_property_bag.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_sdk_seam.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/tests/wire/test_set_router.py +0 -0
- {ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/uv.lock +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.3] - 2026-08-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`stop(graceful=False)` published `$state=lost` but left the root Device still holding `ready`.** The wire and the object disagreed, so anything re-announcing from that object republished `ready` straight over the `lost`. It now goes through the SDK's public `Device.set_state(DeviceState.LOST)` first, mirroring what `Device.stop()` already does for `disconnected`. The flushed publish that follows is deliberately the same retained value a second time: `set_state` uses the ordinary unflushed path, and on the owned path the connection closes immediately behind the call, so only a flushed publish is guaranteed to land. Costs one message at teardown. The observable retained outcome is unchanged (confirmed against a real broker: `p1=lost`); what changes is that it now survives a subsequent re-announce. Two tests pin it, both failing against 0.3.2. Found by [@cayossarian](https://github.com/cayossarian) while building on this code in [#17](https://github.com/electrification-bus/distribution-enclosure-simulator/pull/17).
|
|
10
|
+
|
|
5
11
|
## [0.3.2] - 2026-08-07
|
|
6
12
|
|
|
7
13
|
Metadata-only. No source changes, so the published tree and the public API are identical to 0.3.1; this release exists to get the packaging metadata below onto PyPI, where it only takes effect on a publish.
|
|
@@ -39,6 +39,20 @@ Pull requests are welcome.
|
|
|
39
39
|
- **Keep comments to a minimum.** The project style is self-explanatory code, with comments reserved for the non-obvious *why* (a spec quirk, a Homie nuance, a SPAN-variant deviation). Don't add comments that just restate the code.
|
|
40
40
|
- One commit per logical change is fine; we don't require squash or any particular branch naming.
|
|
41
41
|
|
|
42
|
+
## What "done" means
|
|
43
|
+
|
|
44
|
+
This package is published to PyPI, and a PyPI upload can never be replaced. So an increment that lands behaviour and leaves its documentation for later is not a smaller version of the change: it is a release whose docs contradict its code, and the correction costs another release. Ship the whole thing.
|
|
45
|
+
|
|
46
|
+
A change is done when, in the same PR:
|
|
47
|
+
|
|
48
|
+
- **Prose it invalidates is fixed.** If a change makes a sentence in the README, `DESIGN.md`, `DEVELOPER.md`, or a docstring untrue, that sentence is part of the change. A new option whose README still describes the old single path is not finished.
|
|
49
|
+
- **Types its signatures name are reachable.** A public parameter annotated with a type a caller cannot import from `ebus_panel_sim` forces them to depend on `ebus_sdk` directly, which is what the SDK seam exists to spare them.
|
|
50
|
+
- **It carries its `CHANGELOG.md` entry**, under `[Unreleased]` or a version heading.
|
|
51
|
+
- **New behaviour has a test that fails without it.** Say so in the PR, and say which. A test that passes against the previous commit is not evidence.
|
|
52
|
+
- **Caller obligations are written down where the caller will look.** If correct use requires the caller to do something (wire a callback, set something before connecting, let an event loop turn), a `#` comment in our source does not reach them.
|
|
53
|
+
|
|
54
|
+
Splitting a change is fine when the parts are genuinely independent, or when a decision is needed that only a maintainer can make. It is not fine as a way to defer the half that needs no decision. If you are unsure which you have, open the PR with the whole thing and let the review split it.
|
|
55
|
+
|
|
42
56
|
## Local development
|
|
43
57
|
|
|
44
58
|
Python >= 3.11 (developed and CI-tested on 3.11 and 3.14), managed with [uv](https://docs.astral.sh/uv/). See [DEVELOPER.md](DEVELOPER.md) for the full guide.
|
|
@@ -17,7 +17,7 @@ The producer builds a `DeviceManifest` (identity plus physics keys per device) a
|
|
|
17
17
|
|
|
18
18
|
The enclosure is a Homie root device (`energy.ebus.device.distribution-enclosure`); every circuit, lugs pair, and integrated DER (BESS, PV, EVSE, MID) is a separate child Homie device with `root` and `parent` back-references to the enclosure. Each device's properties are grouped into capability-typed nodes (`info`, `meter`, `switch`, `breaker`, `load-shed`, `pcs`, `connection`, `status`, `door`, `soc`, `shed`, `shed-forecast`, `grid`, `config`, `power-flows`), whose node `$type` is `energy.ebus.capability.<capability>`. A child device therefore publishes under its own topic root, for example `ebus/5/<circuit-id>/switch/relay` and `ebus/5/<bess-id>-mid/grid/islanding-state`.
|
|
19
19
|
|
|
20
|
-
Placement is declarative. Each `wire/mapping/*.yaml` descriptor says whether its device class is the `root-device` or a `child-of-parent`; `graph_builder` walks the manifest, mappings, and profiles to build the SDK device graph, and the SDK's `Device` owns the `$state` cascade: `graph_builder` wraps each device's node/property build in a `state_transition()` that coalesces the description republish into a single `init` then `ready` cycle, while `Emitter.start()`/`stop()` drive connect and disconnect. A graceful `stop()` publishes only the root's `$state=disconnected` (by Homie's effective-state rule that covers every child);
|
|
20
|
+
Placement is declarative. Each `wire/mapping/*.yaml` descriptor says whether its device class is the `root-device` or a `child-of-parent`; `graph_builder` walks the manifest, mappings, and profiles to build the SDK device graph, and the SDK's `Device` owns the `$state` cascade: `graph_builder` wraps each device's node/property build in a `state_transition()` that coalesces the description republish into a single `init` then `ready` cycle, while `Emitter.start()`/`stop()` drive connect and disconnect. A graceful `stop()` publishes only the root's `$state=disconnected` (by Homie's effective-state rule that covers every child); `stop(graceful=False)` publishes the root's `$state=lost` itself, because the registered LWT cannot deliver it (a will fires only on an *unclean* disconnect, and every teardown path here closes cleanly, deliberately, so an orderly shutdown is not reported as a crash); retained topics are cleared only when `stop(clear_retained=True)` is passed, which is graceful-only, since a producer that died clears nothing. The vendored `wire/profiles/*.json` are the schema (capabilities, properties, datatypes, units, `$format`, settability); `bag_builder` maps each profile-declared property to a snapshot accessor and fails loud at construction if any declared property has no source.
|
|
21
21
|
|
|
22
22
|
## Native devices
|
|
23
23
|
|
|
@@ -131,13 +131,12 @@ distribution-enclosure-simulator/
|
|
|
131
131
|
bag_builder.py # Snapshot -> PropertyBag translator
|
|
132
132
|
property_bag.py # Per-tick property values + diff cache
|
|
133
133
|
publisher.py # Per-tick diff/publish loop
|
|
134
|
-
lifecycle.py # $state, $description, /set subscription, LWT
|
|
135
134
|
set_router.py # Setter registry and /set dispatch
|
|
136
|
-
|
|
137
|
-
_sdk_seam.py # Internal seam over ebus_sdk.property
|
|
135
|
+
_sdk_seam.py # Internal seam over ebus_sdk (property build, owned-client narrowing, will publish)
|
|
138
136
|
profiles/ # Vendored Homie 5 device profiles (JSON), per device type
|
|
139
137
|
mapping/ # Vendored mapping descriptors (YAML), per device type
|
|
140
|
-
|
|
138
|
+
catalogs/ # Vendored spec capability catalogs (JSON); the wire type contract
|
|
139
|
+
tests/ # pytest suite; paho is patched, so no broker and no socket
|
|
141
140
|
conftest.py
|
|
142
141
|
conventions/ # convention tests
|
|
143
142
|
wire/ # wire-layer tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ebus-panel-sim
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Producer-side Homie publisher with embedded behaviour runtime for the eBus convention
|
|
5
5
|
Project-URL: Homepage, https://ebus.energy
|
|
6
6
|
Project-URL: Repository, https://github.com/electrification-bus/distribution-enclosure-simulator
|
|
@@ -69,7 +69,7 @@ from ebus_panel_sim.wire.set_router import SetterHandler, SetterRegistry
|
|
|
69
69
|
# via `[tool.hatch.version]`, and publish.yml refuses to release when the git tag
|
|
70
70
|
# disagrees. Bump it in this one place. Note this is the PACKAGE version and is
|
|
71
71
|
# distinct from the producer-contract version the docstrings above refer to.
|
|
72
|
-
__version__ = "0.3.
|
|
72
|
+
__version__ = "0.3.3"
|
|
73
73
|
|
|
74
74
|
__all__ = [
|
|
75
75
|
"BESSConfig",
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
import ebus_sdk
|
|
14
|
-
from ebus_sdk import EBUS_HOMIE_MQTT_QOS, MqttClient, PropertyDatatype, Unit
|
|
14
|
+
from ebus_sdk import EBUS_HOMIE_MQTT_QOS, DeviceState, MqttClient, PropertyDatatype, Unit
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def make_property(
|
|
@@ -74,12 +74,26 @@ def publish_will_now(root: ebus_sdk.Device) -> bool:
|
|
|
74
74
|
delivered by the broker from its own state, whereas this goes out over a live
|
|
75
75
|
connection that is about to close and has to actually land first.
|
|
76
76
|
|
|
77
|
+
``set_state`` moves the root's own state to ``LOST`` first, mirroring what
|
|
78
|
+
``Device.stop()`` does for ``DISCONNECTED``. Publishing a state the device
|
|
79
|
+
object does not itself hold leaves the two disagreeing, and anything that later
|
|
80
|
+
re-announces from that object (``refresh_tree()``, which the SDK asks a
|
|
81
|
+
bring-your-own-transport caller to wire onto their client's on-connect handler)
|
|
82
|
+
republishes ``ready`` straight over the ``lost`` we just sent.
|
|
83
|
+
|
|
84
|
+
That call also publishes ``$state``, so the flushed publish below is the same
|
|
85
|
+
retained value a second time. The duplicate is deliberate and costs one message
|
|
86
|
+
at teardown: ``set_state`` goes through the ordinary unflushed path, and on the
|
|
87
|
+
owned path the connection closes immediately behind this function, so only a
|
|
88
|
+
flushed publish is actually guaranteed to land.
|
|
89
|
+
|
|
77
90
|
Returns True when the broker acknowledged it, False if there is no owned
|
|
78
91
|
client or the publish did not flush in time.
|
|
79
92
|
"""
|
|
80
93
|
client = owned_client(root.mqttc)
|
|
81
94
|
if client is None:
|
|
82
95
|
return False
|
|
96
|
+
root.set_state(DeviceState.LOST)
|
|
83
97
|
will = root.will()
|
|
84
98
|
return bool(
|
|
85
99
|
client.publish_and_flush(
|
|
@@ -71,3 +71,25 @@ def test_ungraceful_stop_publishes_lost_retained(rec: PahoRecorder) -> None:
|
|
|
71
71
|
lost = [(t, d, q, r) for (t, d, q, r) in rec.published if t == ROOT_STATE and d == "lost"]
|
|
72
72
|
assert lost, "no $state=lost publish reached the transport"
|
|
73
73
|
assert all(retain for (_t, _d, _q, retain) in lost)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_ungraceful_stop_moves_the_device_state_not_just_the_wire(rec: PahoRecorder) -> None:
|
|
77
|
+
"""Publishing `lost` while the Device object still holds `ready` leaves the two
|
|
78
|
+
disagreeing, and anything that re-announces from the object undoes the publish.
|
|
79
|
+
`Device.stop()` sets `_state` for `disconnected`; this must match."""
|
|
80
|
+
emitter = _started()
|
|
81
|
+
emitter.stop(graceful=False)
|
|
82
|
+
assert rec.retained[ROOT_STATE] == "lost"
|
|
83
|
+
assert emitter._root.state().value == "lost"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_a_later_refresh_tree_re_announces_lost_not_ready(rec: PahoRecorder) -> None:
|
|
87
|
+
"""`refresh_tree()` republishes from the Device's own state, and the SDK asks a
|
|
88
|
+
bring-your-own-transport caller to wire it onto their client's on-connect
|
|
89
|
+
handler. If the ungraceful stop left `_state` on `ready`, that hook would
|
|
90
|
+
resurrect `ready` over the `lost` and restore the very stale-tree bug 0.3.1
|
|
91
|
+
removed."""
|
|
92
|
+
emitter = _started()
|
|
93
|
+
emitter.stop(graceful=False)
|
|
94
|
+
emitter._root.refresh_tree()
|
|
95
|
+
assert rec.retained[ROOT_STATE] == "lost"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/native_devices/load_shedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/charge-limit.json
RENAMED
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/connection.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/load-shed.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/power-flows.json
RENAMED
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/catalogs/shed-forecast.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/circuit.json
RENAMED
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/evse.json
RENAMED
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/lugs.json
RENAMED
|
File without changes
|
{ebus_panel_sim-0.3.2 → ebus_panel_sim-0.3.3}/src/ebus_panel_sim/wire/profiles/span/panel.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|