span-panel-api-schema-1 1.2.0__tar.gz → 1.2.1__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.0 → span_panel_api_schema_1-1.2.1}/CHANGELOG.md +22 -11
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/PKG-INFO +2 -2
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/pyproject.toml +3 -2
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/circuits.py +2 -2
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/snapshot.py +37 -32
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/.gitignore +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/README.md +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/breaker.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/charge-limit.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/connection.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/door.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/grid-forming.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/grid.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/info.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/load-shed.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/meter.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/pcs.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/power-flows.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/shed-forecast.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/shed.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/soc.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/status.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/switch.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/registries/device-types.md +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/__init__.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/adapter.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/adoption.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/catalog.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/charge_limit.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/const.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/description.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/devices.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/extension.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/field_metadata.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/firmware.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/panel.py +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/py.typed +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/reference/parent_child_tree.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/spec_lock.json +0 -0
- {span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/transport.py +0 -0
|
@@ -9,26 +9,37 @@ 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
|
-
## [1.2.
|
|
12
|
+
## [1.2.1]
|
|
13
13
|
|
|
14
|
-
Every PV inverter a panel commissions is read, and the battery's power keeps its sign on firmware r202639. Requires `span-panel-api` **3.6.
|
|
14
|
+
Every PV inverter a panel commissions is read, and the battery's power keeps its sign on firmware r202639. Requires `span-panel-api` **3.6.1 or newer**.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Every PV inverter is read, not only the first**, each landing in `pv_inverters` with its serial number when published, its device id and its key.
|
|
19
|
+
- **`pv.serial_number` is read from `info/serial-number`**, so that property no longer appears as a discovery row.
|
|
20
|
+
- **`TreeRoles.pvs` and `devices.pv_inverter_key`** list every inverter and give an inverter's key.
|
|
21
|
+
- **`span_panel_api_schema_1.firmware.release_build`** reads the six-digit release build number out of a firmware version string, or returns `None` when it has none.
|
|
22
|
+
- **`build_battery` takes keyword-only `firmware_version` and `power_flow_battery`** to pick the BESS meter's wire frame, and `SchemaOneAdapter` a keyword-only `clock` for the wait on feeding circuits, and both behave as before without them.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`pv` describes the inverters together when a panel commissions more than one**, with their shared vendor and model, the sum of their installed DC sizes and a link that is down if any is down, rather than whichever came first in the device tree.
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- **`TreeRoles.pv`**, which held only the first inverter, replaced by `TreeRoles.pvs`.
|
|
15
31
|
|
|
16
32
|
### Fixed
|
|
17
33
|
|
|
18
|
-
- **
|
|
19
|
-
- **A PV inverter wired through two breakers
|
|
20
|
-
- **`pv` is the inverter on the lowest breaker space when more than one is commissioned**, rather than whichever came first in the device tree.
|
|
34
|
+
- **Every inverter's feeding circuit is labeled `device_type="pv"`**, so a second inverter's power no longer reads as a load with the opposite sign.
|
|
35
|
+
- **A PV inverter or charger wired through two breakers reports its power and link status consistently**, with both circuits labeled for it and its feed and link status read from the one with the lowest circuit id.
|
|
21
36
|
- **Each inverter's extension properties are filed under that inverter**, with its `pv_inverters` key as the `instance_key` when more than one is commissioned.
|
|
22
37
|
- **The first snapshot waits briefly for the circuits that feed an inverter**, so an inverter's key and its circuit's label do not change just after connecting.
|
|
23
38
|
- **`battery.power_w` is discharge-positive on firmware r202639 and later, from the first snapshot on**, where a charging battery read positive.
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
## [1.2.0] [YANKED]
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
- **`build_battery` takes keyword-only `firmware_version` and `power_flow_battery`** to pick the BESS meter's wire frame, and behaves as before without them.
|
|
29
|
-
- **Every PV inverter is read, not only the first**, each landing in `pv_inverters` with its serial number when published, its device id and its key.
|
|
30
|
-
- **`TreeRoles.pvs` lists every inverter**, and `snapshot.primary_pv`, `devices.pv_inverter_key` and `circuits.circuit_tabs` choose the one `pv` describes, give an inverter's key, and read a circuit's breaker spaces.
|
|
31
|
-
- **`pv.serial_number` is read from `info/serial-number`**, so that property no longer appears as a discovery row.
|
|
42
|
+
Withdrawn from PyPI; 1.2.1 replaces it.
|
|
32
43
|
|
|
33
44
|
## [1.1.4]
|
|
34
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: span-panel-api-schema-1
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
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.6.
|
|
11
|
+
Requires-Dist: span-panel-api<4.0,>=3.6.1
|
|
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.2.
|
|
3
|
+
version = "1.2.1"
|
|
4
4
|
description = "Parent/child schema (data-model-version 1.x) parser for span-panel-api"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "SpanPanel"}
|
|
@@ -25,7 +25,8 @@ dependencies = [
|
|
|
25
25
|
# and the inverter's `serial_number`, `device_id` and `node_id`, which this
|
|
26
26
|
# parser fills. Against an earlier one, building a snapshot fails on those
|
|
27
27
|
# arguments.
|
|
28
|
-
|
|
28
|
+
# Raised to 3.6.1, whose SpanPanelSnapshot.pv documents what this parser builds.
|
|
29
|
+
"span-panel-api>=3.6.1,<4.0",
|
|
29
30
|
# Only this distribution depends on the eBus SDK. The bootstrap and
|
|
30
31
|
# schema-0 stay clean, so a flat-panel install never pulls it in — which is
|
|
31
32
|
# what bounds the release coupling this dependency introduces to panels on
|
|
@@ -116,7 +116,7 @@ def _optional_integer(device: DiscoveredDevice, node: str, prop: str) -> int | N
|
|
|
116
116
|
return None if raw is None else int(raw)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def
|
|
119
|
+
def _tabs(device: DiscoveredDevice) -> list[int]:
|
|
120
120
|
"""Breaker spaces from ``info/spaces``.
|
|
121
121
|
|
|
122
122
|
v1.0 publishes the occupied spaces literally (``"36,38"``), where the flat
|
|
@@ -254,7 +254,7 @@ def build_circuit(
|
|
|
254
254
|
# circuit's.
|
|
255
255
|
produced_energy_wh=_number(device, NODE_METER, PROP_IMPORTED_ENERGY),
|
|
256
256
|
consumed_energy_wh=_number(device, NODE_METER, PROP_EXPORTED_ENERGY),
|
|
257
|
-
tabs=
|
|
257
|
+
tabs=_tabs(device),
|
|
258
258
|
priority=priority,
|
|
259
259
|
# `always-on` is `not relay-controllable`, and the flat schema derived
|
|
260
260
|
# user-controllability from `always-on` — so this is the same answer by
|
|
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING
|
|
|
13
13
|
|
|
14
14
|
from span_panel_api.models import ExtensionSubject, SpanPanelSnapshot, SpanPVSnapshot
|
|
15
15
|
from span_panel_api_schema_1.adoption import build_adopted_devices
|
|
16
|
-
from span_panel_api_schema_1.circuits import build_circuit
|
|
16
|
+
from span_panel_api_schema_1.circuits import build_circuit
|
|
17
17
|
from span_panel_api_schema_1.const import (
|
|
18
18
|
NODE_CONNECTION,
|
|
19
19
|
NODE_INFO,
|
|
@@ -75,7 +75,8 @@ class TreeRoles:
|
|
|
75
75
|
self.evse: list[DiscoveredDevice] = []
|
|
76
76
|
self.bess: DiscoveredDevice | None = None
|
|
77
77
|
# Every inverter, in tree order. From r202639 each commissioned inverter
|
|
78
|
-
# is its own device; earlier firmware published one.
|
|
78
|
+
# is its own device; earlier firmware published one. None of them is
|
|
79
|
+
# primary.
|
|
79
80
|
self.pvs: list[DiscoveredDevice] = []
|
|
80
81
|
self.mid: DiscoveredDevice | None = None
|
|
81
82
|
|
|
@@ -94,33 +95,37 @@ class TreeRoles:
|
|
|
94
95
|
elif declared == TYPE_MID and self.mid is None:
|
|
95
96
|
self.mid = device
|
|
96
97
|
|
|
97
|
-
self.pv: DiscoveredDevice | None = primary_pv(self.pvs, self.circuits)
|
|
98
98
|
|
|
99
|
+
def _pv_for(inverters: Sequence[SpanPVSnapshot]) -> SpanPVSnapshot:
|
|
100
|
+
"""`SpanPanelSnapshot.pv`: the lone inverter, or the inverters together.
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
one with no feeding circuit (or a circuit that publishes none);
|
|
108
|
-
2. among those, the lowest breaker space the feeding circuit occupies;
|
|
109
|
-
3. then the lowest device id.
|
|
110
|
-
|
|
111
|
-
A single inverter is chosen whatever it publishes, so a panel with one
|
|
112
|
-
inverter reads exactly as it did before more than one could be published.
|
|
102
|
+
No inverter stands for the others. With several, `pv` identifies none of
|
|
103
|
+
them -- no device id, key, serial, firmware, feeding circuit or position --
|
|
104
|
+
and carries only what describes them together: the vendor and model where
|
|
105
|
+
every inverter shares one, the sum of their installed DC sizes where every
|
|
106
|
+
inverter publishes one, and their link as a three-valued AND (down if any
|
|
107
|
+
is reported down, up if every one is reported up, unknown otherwise).
|
|
108
|
+
Independent of order.
|
|
113
109
|
"""
|
|
114
|
-
if not
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
if not inverters:
|
|
111
|
+
return SpanPVSnapshot()
|
|
112
|
+
if len(inverters) == 1:
|
|
113
|
+
return inverters[0]
|
|
114
|
+
vendors = {inverter.vendor_name for inverter in inverters}
|
|
115
|
+
models = {inverter.model for inverter in inverters}
|
|
116
|
+
nameplates = [inverter.nameplate_capacity_w for inverter in inverters if inverter.nameplate_capacity_w is not None]
|
|
117
|
+
links = [inverter.connected for inverter in inverters]
|
|
118
|
+
connected: bool | None = None
|
|
119
|
+
if any(link is False for link in links):
|
|
120
|
+
connected = False
|
|
121
|
+
elif all(link is True for link in links):
|
|
122
|
+
connected = True
|
|
123
|
+
return SpanPVSnapshot(
|
|
124
|
+
vendor_name=vendors.pop() if len(vendors) == 1 else None,
|
|
125
|
+
model=models.pop() if len(models) == 1 else None,
|
|
126
|
+
nameplate_capacity_w=sum(nameplates) if len(nameplates) == len(inverters) else None,
|
|
127
|
+
connected=connected,
|
|
128
|
+
)
|
|
124
129
|
|
|
125
130
|
|
|
126
131
|
def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], ready_since: float = 0.0) -> SpanPanelSnapshot:
|
|
@@ -138,10 +143,10 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
138
143
|
# A DER's device type decides how its feeding circuit is labelled, so the
|
|
139
144
|
# circuit inherits it — matching the flat adapter, where the same circuit
|
|
140
145
|
# reports device_type "pv" rather than "circuit". Every inverter's circuit,
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
146
|
+
# however many are commissioned: a PV circuit left labeled "circuit" is
|
|
147
|
+
# read as a load, with its power in the opposite sign. Read from the
|
|
148
|
+
# circuit side rather than through `feeds`, which keeps one circuit per
|
|
149
|
+
# DER: every circuit of a DER fed by several carries its power.
|
|
145
150
|
kind_by_der = {device.device_id: "pv" for device in roles.pvs} | {device.device_id: "evse" for device in roles.evse}
|
|
146
151
|
der_type_by_circuit = {
|
|
147
152
|
circuit.device_id: kind
|
|
@@ -214,7 +219,7 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
214
219
|
("panel", panel),
|
|
215
220
|
("battery", roles.bess),
|
|
216
221
|
("mid", roles.mid),
|
|
217
|
-
*((("pv", roles.
|
|
222
|
+
*((("pv", roles.pvs[0]),) if len(roles.pvs) == 1 else ()),
|
|
218
223
|
)
|
|
219
224
|
if device is not None
|
|
220
225
|
]
|
|
@@ -297,7 +302,7 @@ def build_snapshot(panel: DiscoveredDevice, children: list[DiscoveredDevice], re
|
|
|
297
302
|
firmware_version=fields.firmware_version,
|
|
298
303
|
power_flow_battery=fields.power_flow_battery,
|
|
299
304
|
),
|
|
300
|
-
pv=pv_inverters
|
|
305
|
+
pv=_pv_for(list(pv_inverters.values())),
|
|
301
306
|
pv_inverters=pv_inverters,
|
|
302
307
|
mid=build_mid(roles.mid, device_names),
|
|
303
308
|
# Gated on the node being declared, not on any value: every limit this
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/charge-limit.json
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/connection.json
RENAMED
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/grid-forming.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/load-shed.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/spec/catalogs/power-flows.json
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/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.0 → span_panel_api_schema_1-1.2.1}/spec/registries/device-types.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/const.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/panel.py
RENAMED
|
File without changes
|
{span_panel_api_schema_1-1.2.0 → span_panel_api_schema_1-1.2.1}/src/span_panel_api_schema_1/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|