ebus-panel-sim 0.3.0__py3-none-any.whl
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/__init__.py +119 -0
- ebus_panel_sim/conventions/__init__.py +11 -0
- ebus_panel_sim/conventions/tab_legs.py +47 -0
- ebus_panel_sim/emitter.py +750 -0
- ebus_panel_sim/energy_integrator.py +89 -0
- ebus_panel_sim/exceptions.py +38 -0
- ebus_panel_sim/manifest.py +35 -0
- ebus_panel_sim/manifest_physics.py +451 -0
- ebus_panel_sim/native_devices/__init__.py +24 -0
- ebus_panel_sim/native_devices/bess.py +185 -0
- ebus_panel_sim/native_devices/load_shedding.py +60 -0
- ebus_panel_sim/native_devices/protocol.py +38 -0
- ebus_panel_sim/panel_meter.py +216 -0
- ebus_panel_sim/py.typed +0 -0
- ebus_panel_sim/relay_resolver.py +113 -0
- ebus_panel_sim/snapshot.py +301 -0
- ebus_panel_sim/tick_inputs.py +66 -0
- ebus_panel_sim/wire/__init__.py +0 -0
- ebus_panel_sim/wire/_sdk_seam.py +55 -0
- ebus_panel_sim/wire/bag_builder.py +429 -0
- ebus_panel_sim/wire/catalogs/breaker.json +52 -0
- ebus_panel_sim/wire/catalogs/charge-limit.json +37 -0
- ebus_panel_sim/wire/catalogs/connection.json +72 -0
- ebus_panel_sim/wire/catalogs/door.json +17 -0
- ebus_panel_sim/wire/catalogs/grid.json +38 -0
- ebus_panel_sim/wire/catalogs/info.json +52 -0
- ebus_panel_sim/wire/catalogs/load-shed.json +18 -0
- ebus_panel_sim/wire/catalogs/meter.json +201 -0
- ebus_panel_sim/wire/catalogs/pcs.json +111 -0
- ebus_panel_sim/wire/catalogs/power-flows.json +35 -0
- ebus_panel_sim/wire/catalogs/shed-forecast.json +41 -0
- ebus_panel_sim/wire/catalogs/shed.json +24 -0
- ebus_panel_sim/wire/catalogs/soc.json +35 -0
- ebus_panel_sim/wire/catalogs/status.json +28 -0
- ebus_panel_sim/wire/catalogs/switch.json +29 -0
- ebus_panel_sim/wire/graph_builder.py +309 -0
- ebus_panel_sim/wire/mapping/.gitkeep +0 -0
- ebus_panel_sim/wire/mapping/bess.yaml +16 -0
- ebus_panel_sim/wire/mapping/circuit.yaml +16 -0
- ebus_panel_sim/wire/mapping/evse.yaml +16 -0
- ebus_panel_sim/wire/mapping/lugs.yaml +16 -0
- ebus_panel_sim/wire/mapping/mid.yaml +16 -0
- ebus_panel_sim/wire/mapping/panel.yaml +15 -0
- ebus_panel_sim/wire/mapping/pv.yaml +16 -0
- ebus_panel_sim/wire/mapping_loader.py +144 -0
- ebus_panel_sim/wire/profile_loader.py +215 -0
- ebus_panel_sim/wire/profiles/.gitkeep +0 -0
- ebus_panel_sim/wire/profiles/bess.json +58 -0
- ebus_panel_sim/wire/profiles/circuit.json +88 -0
- ebus_panel_sim/wire/profiles/evse.json +26 -0
- ebus_panel_sim/wire/profiles/lugs.json +52 -0
- ebus_panel_sim/wire/profiles/mid.json +40 -0
- ebus_panel_sim/wire/profiles/panel.json +160 -0
- ebus_panel_sim/wire/profiles/pv.json +28 -0
- ebus_panel_sim/wire/profiles/span/circuit.json +19 -0
- ebus_panel_sim/wire/profiles/span/evse.json +52 -0
- ebus_panel_sim/wire/profiles/span/lugs.json +16 -0
- ebus_panel_sim/wire/profiles/span/panel.json +77 -0
- ebus_panel_sim/wire/property_bag.py +56 -0
- ebus_panel_sim/wire/publisher.py +36 -0
- ebus_panel_sim/wire/set_router.py +122 -0
- ebus_panel_sim-0.3.0.dist-info/METADATA +14 -0
- ebus_panel_sim-0.3.0.dist-info/RECORD +66 -0
- ebus_panel_sim-0.3.0.dist-info/WHEEL +4 -0
- ebus_panel_sim-0.3.0.dist-info/licenses/AUTHORS +7 -0
- ebus_panel_sim-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""ebus-panel-sim — producer-side Homie wire publisher with native-device runtime.
|
|
2
|
+
|
|
3
|
+
Architecture (v0.3.0):
|
|
4
|
+
|
|
5
|
+
- **Wire layer** (``wire/``): vendored Homie 5 device profiles + mapping descriptors,
|
|
6
|
+
graph builder, diff-only publisher, /set setter fan-in, and the property/bag diff
|
|
7
|
+
cache over a thin ebus-sdk seam (ebus-sdk owns $description/$state/LWT/encoding).
|
|
8
|
+
- **Native devices** (``native_devices/``): emitter-resident, configured-and-self-driving
|
|
9
|
+
device runtimes (BESS dispatch, load shedding).
|
|
10
|
+
- **Manifest physics** (``manifest_physics.py``): typed accessor over
|
|
11
|
+
``DeviceInstance.metadata`` for physics-relevant fields (voltage, breaker rating,
|
|
12
|
+
tabs/legs, placement, default priority, relay behaviour).
|
|
13
|
+
- **Tick pipeline** (``relay_resolver.py`` + ``energy_integrator.py`` + ``panel_meter.py``
|
|
14
|
+
+ ``conventions/tab_legs.py``): per-tick state machinery the emitter uses to
|
|
15
|
+
resolve circuit relay state, integrate energy, derive per-leg currents, and
|
|
16
|
+
aggregate panel-level fields.
|
|
17
|
+
|
|
18
|
+
Producer contract (v0.3.0): build a ``DeviceManifest`` once at startup, then call
|
|
19
|
+
``Emitter.publish_tick(TickInputs)`` each tick with signed circuit/EVSE powers,
|
|
20
|
+
``current_time``, and ``grid_online``. The emitter does the rest."""
|
|
21
|
+
|
|
22
|
+
from ebus_panel_sim.conventions.tab_legs import Leg, legs_for_tabs
|
|
23
|
+
from ebus_panel_sim.emitter import Emitter
|
|
24
|
+
from ebus_panel_sim.exceptions import (
|
|
25
|
+
EmitterError,
|
|
26
|
+
EmitterStateError,
|
|
27
|
+
ManifestValidationError,
|
|
28
|
+
MissingSetterError,
|
|
29
|
+
ProfileValidationError,
|
|
30
|
+
RuntimeSpecValidationError,
|
|
31
|
+
)
|
|
32
|
+
from ebus_panel_sim.manifest import DeviceInstance, DeviceManifest
|
|
33
|
+
from ebus_panel_sim.manifest_physics import (
|
|
34
|
+
BessPhysics,
|
|
35
|
+
CircuitPhysics,
|
|
36
|
+
EvsePhysics,
|
|
37
|
+
LugsPhysics,
|
|
38
|
+
ManifestPhysicsView,
|
|
39
|
+
PanelPhysics,
|
|
40
|
+
PvPhysics,
|
|
41
|
+
)
|
|
42
|
+
from ebus_panel_sim.native_devices import (
|
|
43
|
+
BESSConfig,
|
|
44
|
+
BESSDevice,
|
|
45
|
+
LoadSheddingConfig,
|
|
46
|
+
LoadSheddingDevice,
|
|
47
|
+
NativeDevice,
|
|
48
|
+
NativeTickContext,
|
|
49
|
+
)
|
|
50
|
+
from ebus_panel_sim.relay_resolver import RelayRequester, RelayResolver, RelayState
|
|
51
|
+
from ebus_panel_sim.snapshot import (
|
|
52
|
+
EbusBatterySnapshot,
|
|
53
|
+
EbusCircuitSnapshot,
|
|
54
|
+
EbusEvseSnapshot,
|
|
55
|
+
EbusLugsSnapshot,
|
|
56
|
+
EbusPanelDoor,
|
|
57
|
+
EbusPanelInfo,
|
|
58
|
+
EbusPanelMeter,
|
|
59
|
+
EbusPanelPcs,
|
|
60
|
+
EbusPanelPowerFlows,
|
|
61
|
+
EbusPanelSnapshot,
|
|
62
|
+
EbusPanelStatus,
|
|
63
|
+
EbusPvSnapshot,
|
|
64
|
+
)
|
|
65
|
+
from ebus_panel_sim.tick_inputs import PanelEnvelopeTick, TickInputs
|
|
66
|
+
from ebus_panel_sim.wire.set_router import SetterHandler, SetterRegistry
|
|
67
|
+
|
|
68
|
+
# Single source of truth for the distribution version: pyproject reads it from here
|
|
69
|
+
# via `[tool.hatch.version]`, and publish.yml refuses to release when the git tag
|
|
70
|
+
# disagrees. Bump it in this one place. Note this is the PACKAGE version and is
|
|
71
|
+
# distinct from the producer-contract version the docstrings above refer to.
|
|
72
|
+
__version__ = "0.3.0"
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"BESSConfig",
|
|
76
|
+
"BESSDevice",
|
|
77
|
+
"BessPhysics",
|
|
78
|
+
"CircuitPhysics",
|
|
79
|
+
"DeviceInstance",
|
|
80
|
+
"DeviceManifest",
|
|
81
|
+
"EbusBatterySnapshot",
|
|
82
|
+
"EbusCircuitSnapshot",
|
|
83
|
+
"EbusEvseSnapshot",
|
|
84
|
+
"EbusLugsSnapshot",
|
|
85
|
+
"EbusPanelDoor",
|
|
86
|
+
"EbusPanelInfo",
|
|
87
|
+
"EbusPanelMeter",
|
|
88
|
+
"EbusPanelPcs",
|
|
89
|
+
"EbusPanelPowerFlows",
|
|
90
|
+
"EbusPanelSnapshot",
|
|
91
|
+
"EbusPanelStatus",
|
|
92
|
+
"EbusPvSnapshot",
|
|
93
|
+
"Emitter",
|
|
94
|
+
"EmitterError",
|
|
95
|
+
"EmitterStateError",
|
|
96
|
+
"EvsePhysics",
|
|
97
|
+
"Leg",
|
|
98
|
+
"LoadSheddingConfig",
|
|
99
|
+
"LoadSheddingDevice",
|
|
100
|
+
"LugsPhysics",
|
|
101
|
+
"ManifestPhysicsView",
|
|
102
|
+
"ManifestValidationError",
|
|
103
|
+
"MissingSetterError",
|
|
104
|
+
"NativeDevice",
|
|
105
|
+
"NativeTickContext",
|
|
106
|
+
"PanelEnvelopeTick",
|
|
107
|
+
"PanelPhysics",
|
|
108
|
+
"ProfileValidationError",
|
|
109
|
+
"PvPhysics",
|
|
110
|
+
"RelayRequester",
|
|
111
|
+
"RelayResolver",
|
|
112
|
+
"RelayState",
|
|
113
|
+
"RuntimeSpecValidationError",
|
|
114
|
+
"SetterHandler",
|
|
115
|
+
"SetterRegistry",
|
|
116
|
+
"TickInputs",
|
|
117
|
+
"__version__",
|
|
118
|
+
"legs_for_tabs",
|
|
119
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Centralized panel-wiring conventions used across the emitter.
|
|
2
|
+
|
|
3
|
+
Each module in this package isolates a single physical-layer convention so the
|
|
4
|
+
rest of the codebase can be convention-agnostic. Convention changes (e.g.,
|
|
5
|
+
supporting European single-phase or 3-phase commercial panels) land here without
|
|
6
|
+
rippling into ``PanelMeter``, ``EnergyIntegrator``, or per-property derivations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ebus_panel_sim.conventions.tab_legs import Leg, legs_for_tabs
|
|
10
|
+
|
|
11
|
+
__all__ = ["Leg", "legs_for_tabs"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Tab-to-leg assignment convention for US residential split-phase panels.
|
|
2
|
+
|
|
3
|
+
US residential load centers alternate breaker slots between L1 and L2: odd-numbered
|
|
4
|
+
tabs land on L1, even-numbered tabs land on L2. A 240 V dipole circuit occupies
|
|
5
|
+
two adjacent tabs (e.g. 1 and 2) — one per leg.
|
|
6
|
+
|
|
7
|
+
This module is the single source of truth for that convention. Per-leg current
|
|
8
|
+
calculations in ``PanelMeter`` and the per-circuit ``current_a`` derivation go
|
|
9
|
+
through ``legs_for_tabs`` rather than reaching for ``tab % 2`` directly.
|
|
10
|
+
|
|
11
|
+
Future support for non-US panels (European single-phase, 3-phase commercial) lands
|
|
12
|
+
here as an additional ``Convention`` enum + dispatch, without touching call sites."""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from enum import StrEnum
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Leg(StrEnum):
|
|
20
|
+
"""A panel power leg. ``L1`` and ``L2`` are the two 120 V legs of a US
|
|
21
|
+
residential split-phase service; line-to-line voltage between them is 240 V."""
|
|
22
|
+
|
|
23
|
+
L1 = "L1"
|
|
24
|
+
L2 = "L2"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def legs_for_tabs(tabs: tuple[int, ...]) -> tuple[Leg, ...]:
|
|
28
|
+
"""Return the leg assignment for each tab in ``tabs``, US residential
|
|
29
|
+
convention: odd tabs → L1, even tabs → L2.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
>>> legs_for_tabs((1,))
|
|
33
|
+
(<Leg.L1: 'L1'>,)
|
|
34
|
+
>>> legs_for_tabs((2,))
|
|
35
|
+
(<Leg.L2: 'L2'>,)
|
|
36
|
+
>>> legs_for_tabs((1, 2)) # standard dipole
|
|
37
|
+
(<Leg.L1: 'L1'>, <Leg.L2: 'L2'>)
|
|
38
|
+
>>> legs_for_tabs((1, 3)) # both on L1 — invalid for a dipole
|
|
39
|
+
(<Leg.L1: 'L1'>, <Leg.L1: 'L1'>)
|
|
40
|
+
|
|
41
|
+
The function does not enforce dipole leg-spanning — that's the validation
|
|
42
|
+
job of ``ManifestPhysicsView``, which sees the ``dipole`` flag.
|
|
43
|
+
|
|
44
|
+
Raises ``ValueError`` if any tab is < 1 (panel tabs are 1-indexed)."""
|
|
45
|
+
if any(t < 1 for t in tabs):
|
|
46
|
+
raise ValueError(f"tab numbers must be >= 1; got {tabs!r}")
|
|
47
|
+
return tuple(Leg.L1 if t % 2 == 1 else Leg.L2 for t in tabs)
|