span-panel-api-schema-1 1.2.2__tar.gz → 1.3.0b1__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.
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/CHANGELOG.md +20 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/PKG-INFO +2 -2
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/pyproject.toml +5 -2
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/adapter.py +4 -4
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/circuits.py +13 -4
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/const.py +5 -8
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/panel.py +34 -47
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/snapshot.py +10 -18
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/.gitignore +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/README.md +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/breaker.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/charge-limit.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/connection.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/door.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/grid-forming.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/grid.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/info.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/load-shed.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/meter.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/pcs.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/power-flows.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/shed-forecast.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/shed.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/soc.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/status.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/switch.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/registries/device-types.md +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/__init__.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/adoption.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/catalog.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/charge_limit.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/description.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/devices.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/extension.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/field_metadata.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/firmware.py +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/py.typed +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/reference/parent_child_tree.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/spec_lock.json +0 -0
- {span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/src/span_panel_api_schema_1/transport.py +0 -0
|
@@ -9,6 +9,26 @@ number. A release here means this parser changed, never that the panel did.
|
|
|
9
9
|
|
|
10
10
|
Pre-releases are not listed separately. A beta is a step towards the next public version, so its changes are folded into that version's entry as they land and are described against the last public release, never against the beta before it.
|
|
11
11
|
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
Requires `span-panel-api` **3.7.0 or newer**, whose snapshot fields accept the unknown values this parser now reports.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`proximity_proven` is `None`**, where it was a hard-coded `True`, because the tree publishes no proximity property.
|
|
19
|
+
- **`uptime_s` is `None`**, where it was always `0`, because no device publishes an uptime and this parser keeps no readiness clock to measure one.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- **BREAKING FOR CONSUMERS: `snapshot.build_snapshot()` no longer takes the `ready_since` argument**, which no caller in this repository passed, so a call that passes it by keyword or position now raises `TypeError`.
|
|
24
|
+
|
|
25
|
+
- **BREAKING: snapshots no longer include synthesised `unmapped_tab_N` entries for unoccupied breaker positions, which carried no measured data**, and `panel.build_unmapped_tabs` and `const.UNMAPPED_TAB_PREFIX` are gone with them.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **A circuit whose shed priority is `UNKNOWN` is no longer reported as sheddable.**
|
|
30
|
+
- **The grid-forming device name falls back to vendor and model instead of showing a device id.**
|
|
31
|
+
|
|
12
32
|
## [1.2.2]
|
|
13
33
|
|
|
14
34
|
Requires `span-panel-api` **3.6.2 or newer**, which waits on this parser's missing labels after a reconnect as well as on connect.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: span-panel-api-schema-1
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0b1
|
|
4
4
|
Summary: Parent/child schema (data-model-version 1.x) parser for span-panel-api
|
|
5
5
|
Project-URL: Homepage, https://github.com/SpanPanel/span-panel-api
|
|
6
6
|
Project-URL: Issues, https://github.com/SpanPanel/span-panel-api/issues
|
|
@@ -8,7 +8,7 @@ Author: SpanPanel
|
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
Requires-Python: <4.0,>=3.14
|
|
10
10
|
Requires-Dist: ebus-sdk<0.24,>=0.19.0
|
|
11
|
-
Requires-Dist: span-panel-api<4.0,>=3.
|
|
11
|
+
Requires-Dist: span-panel-api<4.0,>=3.7.0b1
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
|
|
14
14
|
# span-panel-api-schema-1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "span-panel-api-schema-1"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0b1"
|
|
4
4
|
description = "Parent/child schema (data-model-version 1.x) parser for span-panel-api"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "SpanPanel"}
|
|
@@ -28,7 +28,10 @@ dependencies = [
|
|
|
28
28
|
# Raised to 3.6.1, whose SpanPanelSnapshot.pv documents what this parser builds.
|
|
29
29
|
# Raised to 3.6.2, which waits on this parser's missing labels after a bridge
|
|
30
30
|
# rebuild as well as on connect; the fix spans both, so the pair moves together.
|
|
31
|
-
|
|
31
|
+
# Raised to 3.7.0b1, whose snapshot fields accept the unknown values this
|
|
32
|
+
# parser now reports: against an earlier one, the update times it leaves
|
|
33
|
+
# unset read as a constant 0.
|
|
34
|
+
"span-panel-api>=3.7.0b1,<4.0",
|
|
32
35
|
# Only this distribution depends on the eBus SDK. The bootstrap and
|
|
33
36
|
# schema-0 stay clean, so a flat-panel install never pulls it in — which is
|
|
34
37
|
# what bounds the release coupling this dependency introduces to panels on
|
|
@@ -167,10 +167,10 @@ class SchemaOneAdapter:
|
|
|
167
167
|
|
|
168
168
|
The model is required only when the root's description declares it: the
|
|
169
169
|
panel's size comes from nowhere else, and a snapshot built a moment too
|
|
170
|
-
early reports zero spaces, which
|
|
171
|
-
|
|
172
|
-
promised keeps a firmware that omits the property connectable —
|
|
173
|
-
falls back to the drift warning in `panel_size_from_model`.
|
|
170
|
+
early reports zero spaces, which leaves a consumer laying out the
|
|
171
|
+
panel's positions with none to lay out. Asking only for what the panel
|
|
172
|
+
itself promised keeps a firmware that omits the property connectable —
|
|
173
|
+
it falls back to the drift warning in `panel_size_from_model`.
|
|
174
174
|
"""
|
|
175
175
|
root = self._controller.get_root(self._serial_number)
|
|
176
176
|
if root is None or root.state != STATE_READY or not root.description:
|
|
@@ -10,9 +10,14 @@ Flat property v1.0 source
|
|
|
10
10
|
====================== ===========================================================
|
|
11
11
|
``always-on`` ``switch/relay-controllable``, inverted
|
|
12
12
|
``never-backup`` ``$settable`` on ``load-shed/priority``, inverted
|
|
13
|
-
``sheddable`` computed:
|
|
13
|
+
``sheddable`` computed: a shed priority and ``relay-controllable``
|
|
14
14
|
====================== ===========================================================
|
|
15
15
|
|
|
16
|
+
A shed priority is ``OFF_GRID`` or ``SOC_THRESHOLD``. SPAN's r202633 changelog
|
|
17
|
+
derives sheddable as ``priority != NEVER`` and asks consumers to treat ``UNKNOWN``
|
|
18
|
+
as "not yet known" rather than as sheddable, so the rule names the priorities the
|
|
19
|
+
panel sheds by instead of excluding the one it never sheds.
|
|
20
|
+
|
|
16
21
|
Sign and direction are unchanged from the flat schema, and both are the reverse
|
|
17
22
|
of what the property names suggest. Values are in the enclosure's reference
|
|
18
23
|
frame: a normal load reads **negative** ``active-power`` and accumulates
|
|
@@ -23,7 +28,7 @@ accumulators are swapped.
|
|
|
23
28
|
|
|
24
29
|
from __future__ import annotations
|
|
25
30
|
|
|
26
|
-
from typing import TYPE_CHECKING
|
|
31
|
+
from typing import TYPE_CHECKING, Final
|
|
27
32
|
|
|
28
33
|
from span_panel_api.models import SpanCircuitSnapshot
|
|
29
34
|
from span_panel_api_schema_1.const import (
|
|
@@ -33,7 +38,8 @@ from span_panel_api_schema_1.const import (
|
|
|
33
38
|
NODE_METER,
|
|
34
39
|
NODE_PCS,
|
|
35
40
|
NODE_SWITCH,
|
|
36
|
-
|
|
41
|
+
PRIORITY_OFF_GRID,
|
|
42
|
+
PRIORITY_SOC_THRESHOLD,
|
|
37
43
|
PROP_ACTIVE_POWER,
|
|
38
44
|
PROP_CURRENT,
|
|
39
45
|
PROP_EXPORTED_ENERGY,
|
|
@@ -54,6 +60,9 @@ from span_panel_api_schema_1.description import declared_settable, node_properti
|
|
|
54
60
|
if TYPE_CHECKING:
|
|
55
61
|
from ebus_sdk.homie import DiscoveredDevice
|
|
56
62
|
|
|
63
|
+
_SHED_PRIORITIES: Final = frozenset({PRIORITY_OFF_GRID, PRIORITY_SOC_THRESHOLD})
|
|
64
|
+
"""The `load-shed/priority` values the panel sheds a circuit by; see the module docstring."""
|
|
65
|
+
|
|
57
66
|
|
|
58
67
|
def _text(device: DiscoveredDevice, node: str, prop: str, default: str = "") -> str:
|
|
59
68
|
value = device.get_property(node, prop)
|
|
@@ -260,7 +269,7 @@ def build_circuit(
|
|
|
260
269
|
# user-controllability from `always-on` — so this is the same answer by
|
|
261
270
|
# a shorter route.
|
|
262
271
|
is_user_controllable=relay_controllable,
|
|
263
|
-
is_sheddable=priority
|
|
272
|
+
is_sheddable=priority in _SHED_PRIORITIES and relay_controllable,
|
|
264
273
|
is_never_backup=not priority_settable,
|
|
265
274
|
device_type=device_type,
|
|
266
275
|
relative_position=relative_position,
|
|
@@ -152,6 +152,8 @@ PROP_COMMUNICATION_STATE = "communication-state"
|
|
|
152
152
|
# -- Values -----------------------------------------------------------------
|
|
153
153
|
|
|
154
154
|
PRIORITY_NEVER = "NEVER"
|
|
155
|
+
PRIORITY_OFF_GRID = "OFF_GRID"
|
|
156
|
+
PRIORITY_SOC_THRESHOLD = "SOC_THRESHOLD"
|
|
155
157
|
UNKNOWN = "UNKNOWN"
|
|
156
158
|
CLOUD_CONNECTED = "CONNECTED"
|
|
157
159
|
|
|
@@ -181,9 +183,9 @@ STATE_READY = "ready"
|
|
|
181
183
|
# exists because of that split — the panel can tell us a model we have no size
|
|
182
184
|
# for, and we would rather say so than guess.
|
|
183
185
|
#
|
|
184
|
-
# Total size matters beyond a display field:
|
|
185
|
-
#
|
|
186
|
-
#
|
|
186
|
+
# Total size matters beyond a display field: the tree lists only occupied
|
|
187
|
+
# positions, so the total is the only way a consumer knows how many positions
|
|
188
|
+
# to lay out, empty ones included.
|
|
187
189
|
PANEL_SIZE_BY_MODEL: dict[str, int] = {
|
|
188
190
|
"MAIN_16": 16,
|
|
189
191
|
"MLO_24": 24,
|
|
@@ -192,11 +194,6 @@ PANEL_SIZE_BY_MODEL: dict[str, int] = {
|
|
|
192
194
|
"MLO_48": 48,
|
|
193
195
|
}
|
|
194
196
|
|
|
195
|
-
# Prefix for synthesised unoccupied-position entries. Must match the flat
|
|
196
|
-
# adapter's, because the integration keys entities off it and a rename would
|
|
197
|
-
# strand every existing unmapped-tab entity.
|
|
198
|
-
UNMAPPED_TAB_PREFIX = "unmapped_tab_"
|
|
199
|
-
|
|
200
197
|
# The Homie attribute that carries what the flat schema published as the
|
|
201
198
|
# `never-backup` boolean. v1.0 retires the property and expresses it as
|
|
202
199
|
# mutability: a circuit commissioned never-backup has its priority locked, and
|
|
@@ -35,7 +35,7 @@ import json
|
|
|
35
35
|
import logging
|
|
36
36
|
from typing import TYPE_CHECKING, NamedTuple
|
|
37
37
|
|
|
38
|
-
from span_panel_api.models import
|
|
38
|
+
from span_panel_api.models import SpanPcsSnapshot
|
|
39
39
|
from span_panel_api_schema_1.const import (
|
|
40
40
|
CLOUD_CONNECTED,
|
|
41
41
|
NODE_BREAKER,
|
|
@@ -89,7 +89,6 @@ from span_panel_api_schema_1.const import (
|
|
|
89
89
|
TYPE_BESS,
|
|
90
90
|
TYPE_PV,
|
|
91
91
|
UNKNOWN,
|
|
92
|
-
UNMAPPED_TAB_PREFIX,
|
|
93
92
|
)
|
|
94
93
|
|
|
95
94
|
if TYPE_CHECKING:
|
|
@@ -201,20 +200,19 @@ def panel_size_from_model(model: str) -> int:
|
|
|
201
200
|
device publishes no size property.
|
|
202
201
|
|
|
203
202
|
The highest *occupied* space is not a substitute: it is a lower bound, so a
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
unmapped-circuit sensors rather than merely miscount a display value.
|
|
203
|
+
panel whose highest occupied slot is below its size would report that slot,
|
|
204
|
+
and a consumer laying out positions `1..panel_size` would silently lose
|
|
205
|
+
every position above it.
|
|
208
206
|
|
|
209
207
|
Unknown models return 0 and log, because inventing a size is worse than
|
|
210
|
-
reporting none: a wrong total
|
|
211
|
-
|
|
208
|
+
reporting none: a wrong total lays out positions that are not there, or
|
|
209
|
+
hides real ones.
|
|
212
210
|
"""
|
|
213
211
|
size = PANEL_SIZE_BY_MODEL.get(model.strip().upper())
|
|
214
212
|
if size is None:
|
|
215
213
|
if model:
|
|
216
214
|
_LOGGER.warning(
|
|
217
|
-
"Unknown panel model %r; panel size unavailable
|
|
215
|
+
"Unknown panel model %r; panel size unavailable. Known models: %s",
|
|
218
216
|
model,
|
|
219
217
|
", ".join(sorted(PANEL_SIZE_BY_MODEL)),
|
|
220
218
|
)
|
|
@@ -248,47 +246,13 @@ def panel_model_drift(panel: DiscoveredDevice) -> tuple[str, ...]:
|
|
|
248
246
|
]
|
|
249
247
|
if unknown:
|
|
250
248
|
_LOGGER.warning(
|
|
251
|
-
"Panel advertises model(s) %s that this adapter cannot size; "
|
|
252
|
-
"unmapped positions would be wrong for such a panel. Known: %s",
|
|
249
|
+
"Panel advertises model(s) %s that this adapter cannot size; its panel size would be reported as 0. Known: %s",
|
|
253
250
|
", ".join(unknown),
|
|
254
251
|
", ".join(sorted(PANEL_SIZE_BY_MODEL)),
|
|
255
252
|
)
|
|
256
253
|
return tuple(unknown)
|
|
257
254
|
|
|
258
255
|
|
|
259
|
-
def build_unmapped_tabs(panel_size: int, occupied: set[int]) -> dict[str, SpanCircuitSnapshot]:
|
|
260
|
-
"""Synthesise a zero-power entry for every unoccupied breaker position.
|
|
261
|
-
|
|
262
|
-
The integration surfaces these as unmapped-circuit sensors, gated by its
|
|
263
|
-
own `enable_unmapped_circuit_sensors` option, and builds entity ids from
|
|
264
|
-
the circuit id — so the `unmapped_tab_<n>` naming is a compatibility
|
|
265
|
-
contract with entities that already exist, not an internal detail.
|
|
266
|
-
|
|
267
|
-
Reproducible under v1.0 only because the model gives a true total: the tree
|
|
268
|
-
itself lists occupied positions and says nothing about the rest. A panel
|
|
269
|
-
whose model is unrecognised yields nothing rather than a guess.
|
|
270
|
-
"""
|
|
271
|
-
unmapped: dict[str, SpanCircuitSnapshot] = {}
|
|
272
|
-
for tab in range(1, panel_size + 1):
|
|
273
|
-
if tab in occupied:
|
|
274
|
-
continue
|
|
275
|
-
circuit_id = f"{UNMAPPED_TAB_PREFIX}{tab}"
|
|
276
|
-
unmapped[circuit_id] = SpanCircuitSnapshot(
|
|
277
|
-
circuit_id=circuit_id,
|
|
278
|
-
name=f"Unmapped Tab {tab}",
|
|
279
|
-
relay_state="CLOSED",
|
|
280
|
-
instant_power_w=0.0,
|
|
281
|
-
produced_energy_wh=0.0,
|
|
282
|
-
consumed_energy_wh=0.0,
|
|
283
|
-
tabs=[tab],
|
|
284
|
-
priority=UNKNOWN,
|
|
285
|
-
is_user_controllable=False,
|
|
286
|
-
is_sheddable=False,
|
|
287
|
-
is_never_backup=False,
|
|
288
|
-
)
|
|
289
|
-
return unmapped
|
|
290
|
-
|
|
291
|
-
|
|
292
256
|
def find_lugs(devices: list[DiscoveredDevice], upstream: bool) -> DiscoveredDevice | None:
|
|
293
257
|
"""Locate a lugs device by its declared direction.
|
|
294
258
|
|
|
@@ -795,6 +759,29 @@ def resolve_dominant_power_source(
|
|
|
795
759
|
return _POWER_SOURCE_BY_TYPE.get(device_types.get(forming, ""), UNKNOWN)
|
|
796
760
|
|
|
797
761
|
|
|
762
|
+
def device_display_name(device: DiscoveredDevice) -> str | None:
|
|
763
|
+
"""A device's readable name, or `None` when nothing it publishes names it.
|
|
764
|
+
|
|
765
|
+
Homie's `$description.name` (`Battery`, `Solar`, `SPAN Drive - Garage`) where it
|
|
766
|
+
is set and is not the device's own id. Real r202639 panels publish each non-root
|
|
767
|
+
device's id there, and an id shown as a name is the outcome
|
|
768
|
+
`resolve_grid_forming_device_name` exists to avoid. Otherwise the name comes from
|
|
769
|
+
the device's `info`: vendor and model together (`Tesla Powerwall 2 AC`), or the
|
|
770
|
+
model alone. A vendor alone names a maker rather than a device, so it gives `None`.
|
|
771
|
+
|
|
772
|
+
An empty value names nothing, exactly as an unpublished one does.
|
|
773
|
+
"""
|
|
774
|
+
description: dict[str, object] = device.description or {}
|
|
775
|
+
name = description.get("name")
|
|
776
|
+
if name and str(name) != device.device_id:
|
|
777
|
+
return str(name)
|
|
778
|
+
vendor = text(device, NODE_INFO, PROP_VENDOR_NAME)
|
|
779
|
+
model = text(device, NODE_INFO, PROP_MODEL)
|
|
780
|
+
if vendor and model:
|
|
781
|
+
return f"{vendor} {model}"
|
|
782
|
+
return model or None
|
|
783
|
+
|
|
784
|
+
|
|
798
785
|
def resolve_grid_forming_device_name(
|
|
799
786
|
mid: DiscoveredDevice | None,
|
|
800
787
|
device_names: Mapping[str, str],
|
|
@@ -803,9 +790,9 @@ def resolve_grid_forming_device_name(
|
|
|
803
790
|
|
|
804
791
|
The wire value is a Homie device id -- `sim-40t-001-SIM-BESS-40T-001`. That means
|
|
805
792
|
nothing to someone reading a dashboard: it is not a Home Assistant device id, and an
|
|
806
|
-
opaque string is worse than no string.
|
|
807
|
-
|
|
808
|
-
|
|
793
|
+
opaque string is worse than no string. `device_names` holds each device's
|
|
794
|
+
`device_display_name`, which is what a person would recognise, so that is what gets
|
|
795
|
+
surfaced.
|
|
809
796
|
|
|
810
797
|
`None` when the grid is forming (there is no device to name), when no MID publishes
|
|
811
798
|
an answer, or when the id resolves to nothing -- the raw id is still on
|
|
@@ -8,7 +8,6 @@ likes, and the type string is what the schema defines.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
import time
|
|
12
11
|
from typing import TYPE_CHECKING
|
|
13
12
|
|
|
14
13
|
from span_panel_api.models import ExtensionSubject, SpanPanelSnapshot, SpanPVSnapshot
|
|
@@ -43,7 +42,7 @@ from span_panel_api_schema_1.field_metadata import addressed_rows
|
|
|
43
42
|
from span_panel_api_schema_1.panel import (
|
|
44
43
|
PanelFields,
|
|
45
44
|
build_pcs,
|
|
46
|
-
|
|
45
|
+
device_display_name,
|
|
47
46
|
find_lugs,
|
|
48
47
|
panel_size_from_model,
|
|
49
48
|
resolve_dominant_power_source,
|
|
@@ -128,7 +127,7 @@ def _pv_for(inverters: Sequence[SpanPVSnapshot]) -> SpanPVSnapshot:
|
|
|
128
127
|
)
|
|
129
128
|
|
|
130
129
|
|
|
131
|
-
def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice]
|
|
130
|
+
def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice]) -> SpanPanelSnapshot:
|
|
132
131
|
"""Build a full snapshot from the panel and its descendants."""
|
|
133
132
|
roles = TreeRoles(children)
|
|
134
133
|
upstream = find_lugs(roles.lugs, upstream=True)
|
|
@@ -165,12 +164,8 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
165
164
|
circuits[snapshot.circuit_id] = snapshot
|
|
166
165
|
circuit_subjects.append((circuit, ExtensionSubject(kind="circuit", instance_key=snapshot.circuit_id)))
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
# Unoccupied positions are `total - occupied`, so this is only meaningful
|
|
170
|
-
# when the model gave a real total. An unknown model yields size 0 and no
|
|
171
|
-
# unmapped entries rather than a fabricated set.
|
|
167
|
+
# An unknown model yields size 0 rather than a guessed total.
|
|
172
168
|
panel_size = panel_size_from_model(text(panel, NODE_INFO, PROP_MODEL))
|
|
173
|
-
circuits.update(build_unmapped_tabs(panel_size, occupied))
|
|
174
169
|
|
|
175
170
|
# Owners are every device that can claim a DER through a `connection` node.
|
|
176
171
|
owners = [*roles.lugs, *roles.circuits, panel]
|
|
@@ -180,12 +175,7 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
180
175
|
# is what makes PANEL_BACKUP distinguishable from PANEL_OFF_GRID authoritatively
|
|
181
176
|
# instead of guessed from a power source the way flat had to.
|
|
182
177
|
device_types = {device.device_id: device_type(device) for device in children}
|
|
183
|
-
device_names:
|
|
184
|
-
for device in children:
|
|
185
|
-
description: dict[str, object] = device.description or {}
|
|
186
|
-
name = description.get("name")
|
|
187
|
-
if name:
|
|
188
|
-
device_names[device.device_id] = str(name)
|
|
178
|
+
device_names = {device.device_id: name for device in children if (name := device_display_name(device)) is not None}
|
|
189
179
|
inverters = [device for device in children if device_type(device) == TYPE_INVERTER]
|
|
190
180
|
islanding = resolve_islanding_state(roles.mid, panel)
|
|
191
181
|
|
|
@@ -259,10 +249,12 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
259
249
|
dsm_state=resolve_dsm_state(islanding),
|
|
260
250
|
current_run_config=resolve_run_config(roles.mid, islanding, device_types),
|
|
261
251
|
door_state=fields.door_state,
|
|
262
|
-
# The panel
|
|
263
|
-
#
|
|
264
|
-
proximity_proven=
|
|
265
|
-
|
|
252
|
+
# The panel publishes no proximity property, and being connected and
|
|
253
|
+
# ready is not one, so the snapshot does not know.
|
|
254
|
+
proximity_proven=None,
|
|
255
|
+
# No device publishes an uptime, and this adapter keeps no readiness
|
|
256
|
+
# clock of its own to measure one from.
|
|
257
|
+
uptime_s=None,
|
|
266
258
|
eth0_link=fields.eth0_link,
|
|
267
259
|
wlan_link=fields.wlan_link,
|
|
268
260
|
wwan_link=fields.wwan_link,
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/breaker.json
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/charge-limit.json
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/connection.json
RENAMED
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/grid-forming.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/load-shed.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/power-flows.json
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/catalogs/shed-forecast.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.2 → span_panel_api_schema_1-1.3.0b1}/spec/registries/device-types.md
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
|