ebus-panel-sim 0.3.1__tar.gz → 0.3.2__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.
Files changed (109) hide show
  1. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/CHANGELOG.md +15 -0
  2. ebus_panel_sim-0.3.2/PKG-INFO +203 -0
  3. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/README.md +7 -6
  4. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/pyproject.toml +27 -1
  5. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/__init__.py +1 -1
  6. ebus_panel_sim-0.3.1/PKG-INFO +0 -14
  7. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.ebus-spec.json +0 -0
  8. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.github/CODEOWNERS +0 -0
  9. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.github/workflows/ci.yaml +0 -0
  10. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.github/workflows/publish.yml +0 -0
  11. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.gitignore +0 -0
  12. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.pre-commit-config.yaml +0 -0
  13. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/.python-version +0 -0
  14. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/AUTHORS +0 -0
  15. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/CONTRIBUTING.md +0 -0
  16. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/DESIGN.md +0 -0
  17. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/DEVELOPER.md +0 -0
  18. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/LICENSE +0 -0
  19. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/examples/forty_tab_minimal.yaml +0 -0
  20. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/examples/run_forty_tab_minimal.py +0 -0
  21. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/conventions/__init__.py +0 -0
  22. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/conventions/tab_legs.py +0 -0
  23. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/emitter.py +0 -0
  24. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/energy_integrator.py +0 -0
  25. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/exceptions.py +0 -0
  26. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/manifest.py +0 -0
  27. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/manifest_physics.py +0 -0
  28. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/native_devices/__init__.py +0 -0
  29. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/native_devices/bess.py +0 -0
  30. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/native_devices/load_shedding.py +0 -0
  31. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/native_devices/protocol.py +0 -0
  32. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/panel_meter.py +0 -0
  33. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/py.typed +0 -0
  34. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/relay_resolver.py +0 -0
  35. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/snapshot.py +0 -0
  36. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/tick_inputs.py +0 -0
  37. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/__init__.py +0 -0
  38. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/_sdk_seam.py +0 -0
  39. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/bag_builder.py +0 -0
  40. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/breaker.json +0 -0
  41. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/charge-limit.json +0 -0
  42. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/connection.json +0 -0
  43. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/door.json +0 -0
  44. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/grid.json +0 -0
  45. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/info.json +0 -0
  46. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/load-shed.json +0 -0
  47. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/meter.json +0 -0
  48. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/pcs.json +0 -0
  49. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/power-flows.json +0 -0
  50. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/shed-forecast.json +0 -0
  51. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/shed.json +0 -0
  52. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/soc.json +0 -0
  53. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/status.json +0 -0
  54. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/catalogs/switch.json +0 -0
  55. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/graph_builder.py +0 -0
  56. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/.gitkeep +0 -0
  57. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/bess.yaml +0 -0
  58. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/circuit.yaml +0 -0
  59. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/evse.yaml +0 -0
  60. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/lugs.yaml +0 -0
  61. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/mid.yaml +0 -0
  62. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/panel.yaml +0 -0
  63. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping/pv.yaml +0 -0
  64. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/mapping_loader.py +0 -0
  65. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profile_loader.py +0 -0
  66. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/.gitkeep +0 -0
  67. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/bess.json +0 -0
  68. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/circuit.json +0 -0
  69. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/evse.json +0 -0
  70. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/lugs.json +0 -0
  71. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/mid.json +0 -0
  72. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/panel.json +0 -0
  73. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/pv.json +0 -0
  74. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/span/circuit.json +0 -0
  75. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/span/evse.json +0 -0
  76. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/span/lugs.json +0 -0
  77. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/profiles/span/panel.json +0 -0
  78. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/property_bag.py +0 -0
  79. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/publisher.py +0 -0
  80. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/src/ebus_panel_sim/wire/set_router.py +0 -0
  81. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/__init__.py +0 -0
  82. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/conftest.py +0 -0
  83. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/conventions/__init__.py +0 -0
  84. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/conventions/test_tab_legs.py +0 -0
  85. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_catalog_drift.py +0 -0
  86. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_connection.py +0 -0
  87. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_emitter_public_surface.py +0 -0
  88. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_energy_integrator.py +0 -0
  89. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_exceptions.py +0 -0
  90. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_manifest.py +0 -0
  91. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_manifest_physics.py +0 -0
  92. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_mid_placement.py +0 -0
  93. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_panel_meter.py +0 -0
  94. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_publish_tick.py +0 -0
  95. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_relay_resolver.py +0 -0
  96. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_shed_forecast.py +0 -0
  97. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_teardown.py +0 -0
  98. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_tick_inputs.py +0 -0
  99. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_variant.py +0 -0
  100. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/test_wire_units.py +0 -0
  101. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/__init__.py +0 -0
  102. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_circuit_energy_frame.py +0 -0
  103. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_graph_builder.py +0 -0
  104. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_graph_builder_topology.py +0 -0
  105. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_profile_mapping_validation.py +0 -0
  106. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_property_bag.py +0 -0
  107. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_sdk_seam.py +0 -0
  108. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/tests/wire/test_set_router.py +0 -0
  109. {ebus_panel_sim-0.3.1 → ebus_panel_sim-0.3.2}/uv.lock +0 -0
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.2] - 2026-08-07
6
+
7
+ 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.
8
+
9
+ ### Fixed
10
+
11
+ - **The PyPI project page was blank.** `pyproject.toml` declared no `readme`, so the published metadata carried no description at all: the page showed the summary line and nothing else. It now carries the README (`description_content_type: text/markdown`). The README's repo-relative links (`DESIGN.md`, `DEVELOPER.md`, `CONTRIBUTING.md`, `LICENSE`, `AUTHORS`) are absolutised, since those resolve on GitHub but 404 when the same Markdown is rendered on PyPI.
12
+ - **No licence was declared.** The README badge claimed MIT and a `LICENSE` file was present, but nothing said so in the metadata. Now `license = "MIT"` (an SPDX expression) with `license-files = ["LICENSE"]`, so the wheel carries `License-Expression: MIT` and `License-File: LICENSE`. No `License :: OSI Approved` classifier is paired with it, per PEP 639.
13
+ - **No classifiers.** PyPI indexed nothing about the package. Now covers 3.11 through 3.14, audience, topic, and `Typing :: Typed` — the last being how PyPI surfaces the `py.typed` marker shipped in 0.3.0, on the page and in the "Typed" search filter.
14
+ - The `hatchling` build requirement is floored at `>=1.27`, the version that supports PEP 639. Below it the build **succeeds** and quietly emits legacy metadata instead (measured on 1.26.3: `License: MIT`, no `License-Expression`, no `License-File`), so an unpinned backend would have degraded the published artifact without failing anything. ([#15](https://github.com/electrification-bus/distribution-enclosure-simulator/pull/15))
15
+
16
+ ### Changed
17
+
18
+ - The README's Python badge is now the dynamic `pypi/pyversions` one used across the eBus family, replacing a hardcoded `3.11+`. It reads classifiers from the published release, so it could not be adopted until this release carried them.
19
+
5
20
  ## [0.3.1] - 2026-08-07
6
21
 
7
22
  ### Fixed
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: ebus-panel-sim
3
+ Version: 0.3.2
4
+ Summary: Producer-side Homie publisher with embedded behaviour runtime for the eBus convention
5
+ Project-URL: Homepage, https://ebus.energy
6
+ Project-URL: Repository, https://github.com/electrification-bus/distribution-enclosure-simulator
7
+ Project-URL: Issues, https://github.com/electrification-bus/distribution-enclosure-simulator/issues
8
+ Author: Bill Flood, Clark Communications Corporation
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: distribution-enclosure,ebus,energy,homie,homie5,iot,mqtt,simulator,span
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Home Automation
20
+ Classifier: Topic :: System :: Emulators
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: ebus-sdk<0.20,>=0.19
24
+ Requires-Dist: pyyaml>=6.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ebus-panel-sim
28
+
29
+ [![PyPI](https://img.shields.io/pypi/v/ebus-panel-sim)](https://pypi.org/project/ebus-panel-sim/)
30
+ [![CI](https://github.com/electrification-bus/distribution-enclosure-simulator/actions/workflows/ci.yaml/badge.svg)](https://github.com/electrification-bus/distribution-enclosure-simulator/actions/workflows/ci.yaml)
31
+ [![Python versions](https://img.shields.io/pypi/pyversions/ebus-panel-sim.svg)](https://pypi.org/project/ebus-panel-sim/)
32
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
33
+ [![eBus spec](https://img.shields.io/badge/eBus%20spec-6e582c9-green)](https://github.com/electrification-bus/specification/tree/6e582c994fff4c77853a79d8bab26ef9924e22c7)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
35
+
36
+ A fully-loaded, spec-conformant **distribution-enclosure simulator** and producer-side Homie 5 publisher for the eBus convention. It publishes a complete eBus Homie device tree (the enclosure plus a device for every circuit, lugs pair, and integrated DER: BESS, PV, EVSE, and MID) so external developers can build and test their consumers against a realistic SPAN-like panel without beta firmware, a live panel, or the commissioned add-ons (SPAN Drive/EVSE, BESS, PV, MID) a real installation would have.
37
+
38
+ It serves two roles:
39
+
40
+ - **Simulator / test fixture.** Drive it from a small YAML definition and it publishes a spec-conformant, fully-commissioned enclosure to any MQTT broker. Consumers (Home Assistant integrations, dashboards, SDK code) validate against it before shipping to the field.
41
+ - **Producer library.** The canonical eBus Homie publisher. A producer (a simulator, a real panel gateway, an LLM-driven model) hands the emitter a small per-tick driving signal (signed power per circuit, current time, grid-online flag) via `TickInputs`; the emitter derives all telemetry and publishes Homie-conformant retained MQTT with diff-only updates. The split is **identity = manifest (once at startup), telemetry = derived from TickInputs (per tick)**.
42
+
43
+ For the internals (the per-tick pipeline, the native BESS/load-shed devices, `/set` handling, the wire model) see [DESIGN.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DESIGN.md); for the dev setup see [DEVELOPER.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DEVELOPER.md).
44
+
45
+ ## Requirements
46
+
47
+ - Python >= 3.11
48
+ - [uv](https://docs.astral.sh/uv/)
49
+ - An MQTT broker reachable at `localhost:1883` (plaintext). The companion [broker-quickstart](https://github.com/electrification-bus/broker-quickstart) bundle brings one up in one command; any `mosquitto` works too.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install ebus-panel-sim # or: uv add ebus-panel-sim
55
+ ```
56
+
57
+ The import package is `ebus_panel_sim`. During local development, pin a path instead:
58
+
59
+ ```toml
60
+ ebus-panel-sim = { path = "../distribution-enclosure-simulator", editable = true }
61
+ ```
62
+
63
+ It depends on `ebus-sdk`.
64
+
65
+ > Before 0.3.0 this package was named `panel-sim`, importing as `panel_sim`, and was
66
+ > installable only from git. Update both the dependency and your imports.
67
+
68
+ ## Run
69
+
70
+ The repo ships a runnable example: it builds an emitter from a YAML definition, publishes a couple of ticks to an MQTT broker, then reads the retained tree back through an ebus-sdk `Controller` and prints it. It expects a plaintext broker on `localhost:1883`.
71
+
72
+ The quickest broker is the companion [broker-quickstart](https://github.com/electrification-bus/broker-quickstart) in its `open` profile (plaintext, anonymous, port 1883):
73
+
74
+ ```bash
75
+ # in a broker-quickstart checkout — a plaintext :1883 broker (anon read + write)
76
+ python -m laptop.run --profile open
77
+ ```
78
+
79
+ Then, in this repo, publish to it and print the retained tree:
80
+
81
+ ```bash
82
+ uv sync --group dev
83
+ uv run python examples/run_forty_tab_minimal.py # print the retained tree
84
+ uv run python examples/run_forty_tab_minimal.py --broker 127.0.0.1:1883 --ticks 2 > tree.txt
85
+ ```
86
+
87
+ Any broker that accepts anonymous connections on `localhost:1883` works; `--broker host:port` points the example elsewhere.
88
+
89
+ The definition is `examples/forty_tab_minimal.yaml`: a fully-commissioned enclosure with circuits, upstream/downstream lugs, a BESS (plus its MID), PV, and SPAN Drive EVSEs. Each node is its own Homie device: the enclosure at `ebus/5/<enclosure-id>/…` and each circuit, lugs pair, and DER at its own topic root, for example `ebus/5/<circuit-id>/switch/relay`, `ebus/5/<lugs-id>/meter/current-a`, `ebus/5/<bess-id>-mid/grid/islanding-state`.
90
+
91
+ ## Configure
92
+
93
+ The simulator is driven by a config that says which enclosure, which add-ons, and which circuits. There are two entry points.
94
+
95
+ ### 1. Example YAML
96
+
97
+ `examples/forty_tab_minimal.yaml` is the quickest path. Top-level sections:
98
+
99
+ - `panel_config` — enclosure identity plus `total_tabs`, `main_size`, `postal_code`, `time_zone`, and `islandable`. A grid-forming BESS in an islandable enclosure automatically exposes an integrated MID (the islanding authority), mirroring a real SPAN panel.
100
+ - `circuit_templates` and `circuits` — per-circuit `tabs`, breaker rating, priority, relay behavior, and an optional `device_type` (`evse` or `pv`) to land a DER on a circuit.
101
+ - `bess` — nameplate capacity, charge mode, charge/discharge limits.
102
+ - `ticks` — the per-tick driving signal: signed watts per circuit and the grid-online flag.
103
+
104
+ ### 2. DeviceManifest (programmatic)
105
+
106
+ A producer can build `DeviceInstance`s directly instead of using the YAML loader. Each device class's identity and static attributes live in the instance's `metadata`, validated once at startup by `ManifestPhysicsView` (missing required keys or malformed values raise `ManifestValidationError` naming the offending instance). The metadata keys per device class:
107
+
108
+ | entity_class | required keys | optional keys |
109
+ | --- | --- | --- |
110
+ | `panel` | `vendor-name`, `serial-number`, `firmware-version` (or `software-version`), `hardware-version`, `panel-size`, `main-breaker-rating-a`, `panel-model`, `postal-code`, `time-zone` | `service-voltage-v` (240), `line-voltage-v` (120), `islandable` (false) |
111
+ | `lugs` | `direction` (`upstream` \| `downstream`) | |
112
+ | `circuit` | `tab-numbers` (CSV ints), `breaker-rating-a`, `default-priority`, `relay-behavior`, `placement` (`upstream-of-lugs` \| `downstream-of-lugs`) | `always-on`, `pcs-priority` (0), `initial-consumed-wh` (0), `initial-produced-wh` (0) |
113
+ | `bess` | `vendor-name`, `nameplate-capacity-kwh` | `product-name`, `model`, `serial-number`, `firmware-version`/`software-version`, `relative-position` (`UPSTREAM`), `feed`, `initial-soe-kwh` |
114
+ | `pv` | `vendor-name`, `nameplate-capacity-w`, `inverter-type` (`hybrid` \| `ac-coupled`) | `product-name`, `serial-number`, `firmware-version`/`software-version`, `relative-position` (`IN_PANEL`), `feed` |
115
+ | `evse` | `vendor-name`, `product-name`, `part-number`, `serial-number`, `firmware-version` (or `software-version`), `max-current-a` | `feed` |
116
+ | `mid` | (none) | `vendor-name`, `serial-number`, `product-name`, `model`, `firmware-version`/`software-version`, `hardware-version` |
117
+
118
+ ## Usage (as a producer library)
119
+
120
+ ```python
121
+ import time
122
+
123
+ from ebus_panel_sim import (
124
+ BESSConfig, DeviceInstance, DeviceManifest, Emitter,
125
+ LoadSheddingConfig, SetterRegistry, TickInputs,
126
+ )
127
+
128
+
129
+ def main() -> None:
130
+ manifest = DeviceManifest(instances=(
131
+ DeviceInstance("panel", "abc-123", "Span Panel", metadata={
132
+ "vendor-name": "Span", "serial-number": "abc-123",
133
+ "firmware-version": "sim/v0.1.0", "hardware-version": "rev2",
134
+ "panel-size": "40", "main-breaker-rating-a": "200",
135
+ "panel-model": "MAIN_40", "postal-code": "94103",
136
+ "time-zone": "America/Los_Angeles", "islandable": "true",
137
+ }),
138
+ DeviceInstance("lugs", "abc-123-lugs-up", "Upstream lugs", {"direction": "upstream"}),
139
+ DeviceInstance("circuit", "kitchen", "Kitchen", metadata={
140
+ "tab-numbers": "1", "breaker-rating-a": "20",
141
+ "default-priority": "NICE_TO_HAVE", "relay-behavior": "controllable",
142
+ "placement": "downstream-of-lugs",
143
+ }),
144
+ DeviceInstance("bess", "abc-123-bess", "Battery", metadata={
145
+ "vendor-name": "Span", "nameplate-capacity-kwh": "13.5",
146
+ }),
147
+ ))
148
+ bess_cfg = BESSConfig(instance_id="abc-123-bess", nameplate_capacity_kwh=13.5,
149
+ max_charge_w=3500.0, max_discharge_w=3500.0)
150
+
151
+ # The emitter owns the MQTT connection: ebus-sdk builds the client from
152
+ # mqtt_cfg and sets the enclosure's LWT. Empty SetterRegistry -> the emitter
153
+ # installs internal default /set handlers; register your own before
154
+ # construction to override them.
155
+ emitter = Emitter(
156
+ manifest, SetterRegistry(),
157
+ mqtt_cfg={"host": "127.0.0.1", "port": 1883},
158
+ bess_configs=(bess_cfg,),
159
+ load_shedding_config=LoadSheddingConfig(soc_threshold_pct=20.0),
160
+ )
161
+ emitter.start()
162
+ try:
163
+ while True:
164
+ emitter.publish_tick(TickInputs(
165
+ current_time=time.time(),
166
+ grid_online=True,
167
+ circuits=collect_powers_from_your_model(), # instance_id -> signed watts
168
+ ))
169
+ time.sleep(1.0)
170
+ finally:
171
+ emitter.stop()
172
+
173
+
174
+ main()
175
+ ```
176
+
177
+ Read the most recently published state back through `emitter.last_snapshot`. `mqtt_cfg` is handed straight to ebus-sdk: beyond `host`/`port` it takes the ebus-mqtt-client TLS and authentication keys for secured brokers (e.g. broker-quickstart's mTLS `discovery`/`strict` profiles).
178
+
179
+ ## Layout
180
+
181
+ - `src/ebus_panel_sim/` — the package (`emitter.py`, `manifest.py`, `wire/` profiles + publishing, `native_devices/`); see [DESIGN.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DESIGN.md).
182
+ - `examples/` — the runnable example and its YAML definition.
183
+ - `tests/` — the pytest suite.
184
+
185
+ ## Tests
186
+
187
+ ```bash
188
+ uv run pytest
189
+ uv run mypy --strict src/ebus_panel_sim tests
190
+ uv run ruff check src tests
191
+ ```
192
+
193
+ ## Contributing
194
+
195
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/CONTRIBUTING.md) for how to file issues, start a [discussion](https://github.com/electrification-bus/distribution-enclosure-simulator/discussions), and open pull requests, plus the local quality gates (ruff, mypy `--strict`, pytest).
196
+
197
+ ## Credits
198
+
199
+ A fork of, and building on, the original simulator created by **Bill Flood** ([@cayossarian](https://github.com/cayossarian)); since updated to track the latest eBus specification. See [AUTHORS](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/AUTHORS).
200
+
201
+ ## License
202
+
203
+ See [LICENSE](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/LICENSE).
@@ -1,7 +1,8 @@
1
1
  # ebus-panel-sim
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/ebus-panel-sim)](https://pypi.org/project/ebus-panel-sim/)
3
4
  [![CI](https://github.com/electrification-bus/distribution-enclosure-simulator/actions/workflows/ci.yaml/badge.svg)](https://github.com/electrification-bus/distribution-enclosure-simulator/actions/workflows/ci.yaml)
4
- [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/ebus-panel-sim.svg)](https://pypi.org/project/ebus-panel-sim/)
5
6
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6
7
  [![eBus spec](https://img.shields.io/badge/eBus%20spec-6e582c9-green)](https://github.com/electrification-bus/specification/tree/6e582c994fff4c77853a79d8bab26ef9924e22c7)
7
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -13,7 +14,7 @@ It serves two roles:
13
14
  - **Simulator / test fixture.** Drive it from a small YAML definition and it publishes a spec-conformant, fully-commissioned enclosure to any MQTT broker. Consumers (Home Assistant integrations, dashboards, SDK code) validate against it before shipping to the field.
14
15
  - **Producer library.** The canonical eBus Homie publisher. A producer (a simulator, a real panel gateway, an LLM-driven model) hands the emitter a small per-tick driving signal (signed power per circuit, current time, grid-online flag) via `TickInputs`; the emitter derives all telemetry and publishes Homie-conformant retained MQTT with diff-only updates. The split is **identity = manifest (once at startup), telemetry = derived from TickInputs (per tick)**.
15
16
 
16
- For the internals (the per-tick pipeline, the native BESS/load-shed devices, `/set` handling, the wire model) see [DESIGN.md](DESIGN.md); for the dev setup see [DEVELOPER.md](DEVELOPER.md).
17
+ For the internals (the per-tick pipeline, the native BESS/load-shed devices, `/set` handling, the wire model) see [DESIGN.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DESIGN.md); for the dev setup see [DEVELOPER.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DEVELOPER.md).
17
18
 
18
19
  ## Requirements
19
20
 
@@ -151,7 +152,7 @@ Read the most recently published state back through `emitter.last_snapshot`. `mq
151
152
 
152
153
  ## Layout
153
154
 
154
- - `src/ebus_panel_sim/` — the package (`emitter.py`, `manifest.py`, `wire/` profiles + publishing, `native_devices/`); see [DESIGN.md](DESIGN.md).
155
+ - `src/ebus_panel_sim/` — the package (`emitter.py`, `manifest.py`, `wire/` profiles + publishing, `native_devices/`); see [DESIGN.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/DESIGN.md).
155
156
  - `examples/` — the runnable example and its YAML definition.
156
157
  - `tests/` — the pytest suite.
157
158
 
@@ -165,12 +166,12 @@ uv run ruff check src tests
165
166
 
166
167
  ## Contributing
167
168
 
168
- Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to file issues, start a [discussion](https://github.com/electrification-bus/distribution-enclosure-simulator/discussions), and open pull requests, plus the local quality gates (ruff, mypy `--strict`, pytest).
169
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/CONTRIBUTING.md) for how to file issues, start a [discussion](https://github.com/electrification-bus/distribution-enclosure-simulator/discussions), and open pull requests, plus the local quality gates (ruff, mypy `--strict`, pytest).
169
170
 
170
171
  ## Credits
171
172
 
172
- A fork of, and building on, the original simulator created by **Bill Flood** ([@cayossarian](https://github.com/cayossarian)); since updated to track the latest eBus specification. See [AUTHORS](AUTHORS).
173
+ A fork of, and building on, the original simulator created by **Bill Flood** ([@cayossarian](https://github.com/cayossarian)); since updated to track the latest eBus specification. See [AUTHORS](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/AUTHORS).
173
174
 
174
175
  ## License
175
176
 
176
- See [LICENSE](LICENSE).
177
+ See [LICENSE](https://github.com/electrification-bus/distribution-enclosure-simulator/blob/main/LICENSE).
@@ -1,5 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ # PEP 639 floor: `license = "MIT"` as an SPDX expression plus `license-files` needs
3
+ # hatchling >= 1.27. Below it the build SUCCEEDS and quietly emits the legacy
4
+ # metadata instead (measured on 1.26.3: `License: MIT`, no `License-Expression`,
5
+ # no `License-File`), so an unpinned backend would degrade the published metadata
6
+ # without failing anything.
7
+ requires = ["hatchling>=1.27"]
3
8
  build-backend = "hatchling.build"
4
9
 
5
10
  [project]
@@ -9,11 +14,32 @@ name = "ebus-panel-sim"
9
14
  # git tag disagrees. Matches the convention across the eBus family.
10
15
  dynamic = ["version"]
11
16
  description = "Producer-side Homie publisher with embedded behaviour runtime for the eBus convention"
17
+ readme = "README.md"
18
+ license = "MIT"
19
+ license-files = ["LICENSE"]
12
20
  authors = [
13
21
  {name = "Bill Flood"},
14
22
  {name = "Clark Communications Corporation"},
15
23
  ]
16
24
  keywords = ["homie", "homie5", "mqtt", "iot", "ebus", "simulator", "span", "distribution-enclosure", "energy"]
25
+ # `requires-python` is what pip enforces; these are what PyPI indexes and what the
26
+ # README's Python badge reads. Kept in step with the floor above, not with the CI
27
+ # matrix, which tests the ends of the range (3.11 and 3.14) rather than every point
28
+ # in it.
29
+ classifiers = [
30
+ "Development Status :: 4 - Beta",
31
+ "Intended Audience :: Developers",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
37
+ "Topic :: Home Automation",
38
+ "Topic :: System :: Emulators",
39
+ # The package ships py.typed; this is how PyPI surfaces that, on the page and
40
+ # in the "Typed" search filter.
41
+ "Typing :: Typed",
42
+ ]
17
43
  requires-python = ">=3.11"
18
44
  dependencies = [
19
45
  "ebus-sdk>=0.19,<0.20",
@@ -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.1"
72
+ __version__ = "0.3.2"
73
73
 
74
74
  __all__ = [
75
75
  "BESSConfig",
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ebus-panel-sim
3
- Version: 0.3.1
4
- Summary: Producer-side Homie publisher with embedded behaviour runtime for the eBus convention
5
- Project-URL: Homepage, https://ebus.energy
6
- Project-URL: Repository, https://github.com/electrification-bus/distribution-enclosure-simulator
7
- Project-URL: Issues, https://github.com/electrification-bus/distribution-enclosure-simulator/issues
8
- Author: Bill Flood, Clark Communications Corporation
9
- License-File: AUTHORS
10
- License-File: LICENSE
11
- Keywords: distribution-enclosure,ebus,energy,homie,homie5,iot,mqtt,simulator,span
12
- Requires-Python: >=3.11
13
- Requires-Dist: ebus-sdk<0.20,>=0.19
14
- Requires-Dist: pyyaml>=6.0
File without changes
File without changes
File without changes
File without changes