python-siseli 0.1.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.
- python_siseli-0.1.0.dist-info/METADATA +57 -0
- python_siseli-0.1.0.dist-info/RECORD +29 -0
- python_siseli-0.1.0.dist-info/WHEEL +5 -0
- python_siseli-0.1.0.dist-info/top_level.txt +1 -0
- siseli/__init__.py +51 -0
- siseli/_utils.py +29 -0
- siseli/alarms.py +159 -0
- siseli/auth.py +96 -0
- siseli/client.py +474 -0
- siseli/config.py +112 -0
- siseli/const.py +5 -0
- siseli/dashboard.py +100 -0
- siseli/device.py +89 -0
- siseli/dictionary.py +37 -0
- siseli/exceptions.py +28 -0
- siseli/history.py +123 -0
- siseli/models/__init__.py +50 -0
- siseli/models/alarm.py +41 -0
- siseli/models/auth.py +19 -0
- siseli/models/common.py +51 -0
- siseli/models/config.py +28 -0
- siseli/models/dashboard.py +45 -0
- siseli/models/device.py +29 -0
- siseli/models/dictionary.py +18 -0
- siseli/models/history.py +34 -0
- siseli/models/state.py +106 -0
- siseli/models/station.py +82 -0
- siseli/state.py +184 -0
- siseli/station.py +181 -0
siseli/models/state.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""State, attribute, and energy-flow models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .common import LookupValue
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class StateAttribute:
|
|
14
|
+
"""A single telemetry attribute."""
|
|
15
|
+
|
|
16
|
+
key: str
|
|
17
|
+
unit: str
|
|
18
|
+
value: Any
|
|
19
|
+
value_display: str
|
|
20
|
+
name_display: str
|
|
21
|
+
is_hidden: bool | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AttributeMetadata:
|
|
26
|
+
"""Metadata describing a state or configuration attribute."""
|
|
27
|
+
|
|
28
|
+
key: str
|
|
29
|
+
name: str
|
|
30
|
+
name_display: str
|
|
31
|
+
unit: str
|
|
32
|
+
value_type: int | None
|
|
33
|
+
category: int | None
|
|
34
|
+
operation_mode: int | None
|
|
35
|
+
is_hidden: bool | None
|
|
36
|
+
is_config_attribute: bool | None
|
|
37
|
+
is_writable_config_attribute: bool | None
|
|
38
|
+
is_readable_config_attribute: bool | None
|
|
39
|
+
is_state_attribute: bool | None
|
|
40
|
+
is_event_attribute: bool | None
|
|
41
|
+
enum_values: list[LookupValue]
|
|
42
|
+
raw: dict = field(repr=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class AttributeGroup:
|
|
47
|
+
"""A group of related device attributes."""
|
|
48
|
+
|
|
49
|
+
id: str
|
|
50
|
+
key: str
|
|
51
|
+
category: int | None
|
|
52
|
+
name: str
|
|
53
|
+
description: str
|
|
54
|
+
attributes: list[AttributeMetadata]
|
|
55
|
+
raw: dict = field(repr=False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class AttributeGroupSet:
|
|
60
|
+
"""Grouped attribute metadata for a device."""
|
|
61
|
+
|
|
62
|
+
gather_protocol_version_id: str
|
|
63
|
+
groups: list[AttributeGroup]
|
|
64
|
+
raw: dict = field(repr=False)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class DeviceState:
|
|
69
|
+
"""Latest telemetry snapshot for a device."""
|
|
70
|
+
|
|
71
|
+
time: datetime | None
|
|
72
|
+
fields: dict[str, StateAttribute]
|
|
73
|
+
raw: dict = field(repr=False)
|
|
74
|
+
|
|
75
|
+
def get(self, key: str) -> StateAttribute | None:
|
|
76
|
+
"""Return an attribute by its key, or *None* if not present."""
|
|
77
|
+
return self.fields.get(key)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class FlowNode:
|
|
82
|
+
"""One node of the energy-flow diagram (PV panel, grid, battery, load…)."""
|
|
83
|
+
|
|
84
|
+
key: str
|
|
85
|
+
locale_title: str
|
|
86
|
+
value: StateAttribute | None
|
|
87
|
+
flow_direction: int | None
|
|
88
|
+
is_light: bool | None
|
|
89
|
+
is_enabled: bool
|
|
90
|
+
extra_values: list[StateAttribute]
|
|
91
|
+
raw: dict = field(repr=False)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class EnergyFlow:
|
|
96
|
+
"""Energy-flow snapshot for a device."""
|
|
97
|
+
|
|
98
|
+
device_state: DeviceState
|
|
99
|
+
pv_panel: FlowNode | None
|
|
100
|
+
grid: FlowNode | None
|
|
101
|
+
battery: FlowNode | None
|
|
102
|
+
load: FlowNode | None
|
|
103
|
+
generator: FlowNode | None
|
|
104
|
+
ups: FlowNode | None
|
|
105
|
+
ct: FlowNode | None
|
|
106
|
+
raw: dict = field(repr=False)
|
siseli/models/station.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Station-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .common import TimePoint
|
|
10
|
+
from .state import FlowNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Station:
|
|
15
|
+
"""A Siseli station."""
|
|
16
|
+
|
|
17
|
+
id: str
|
|
18
|
+
name: str
|
|
19
|
+
timezone: str | None
|
|
20
|
+
country: str | None
|
|
21
|
+
province: str | None
|
|
22
|
+
city: str | None
|
|
23
|
+
area: str | None
|
|
24
|
+
address: str | None
|
|
25
|
+
longitude: float | None
|
|
26
|
+
latitude: float | None
|
|
27
|
+
station_type: int | None
|
|
28
|
+
connected_grid_type: int | None
|
|
29
|
+
state: int | None
|
|
30
|
+
is_online: bool | None
|
|
31
|
+
installed_capacity: float | None
|
|
32
|
+
total_power: float | None
|
|
33
|
+
daily_produced_quantity: float | None
|
|
34
|
+
total_produced_quantity: float | None
|
|
35
|
+
created_at: datetime | None
|
|
36
|
+
raw: dict = field(repr=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class StationEnergyFlow:
|
|
41
|
+
"""Energy-flow snapshot for a station."""
|
|
42
|
+
|
|
43
|
+
is_support_flow: bool
|
|
44
|
+
time: datetime | None
|
|
45
|
+
pv_panel: FlowNode | None
|
|
46
|
+
grid: FlowNode | None
|
|
47
|
+
battery: FlowNode | None
|
|
48
|
+
load: FlowNode | None
|
|
49
|
+
generator: FlowNode | None
|
|
50
|
+
ups: FlowNode | None
|
|
51
|
+
ct: FlowNode | None
|
|
52
|
+
raw: dict = field(repr=False)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class SummaryCategory:
|
|
57
|
+
"""Summary category metadata."""
|
|
58
|
+
|
|
59
|
+
id: str
|
|
60
|
+
key: str
|
|
61
|
+
name: str
|
|
62
|
+
raw: dict = field(repr=False)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class SummaryProperty:
|
|
67
|
+
"""One property within an aggregated summary."""
|
|
68
|
+
|
|
69
|
+
property: dict[str, Any]
|
|
70
|
+
time_points: list[TimePoint]
|
|
71
|
+
has_real_time_points: bool | None
|
|
72
|
+
raw: dict = field(repr=False)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class StationSummary:
|
|
77
|
+
"""Aggregated station summary series."""
|
|
78
|
+
|
|
79
|
+
category: SummaryCategory | None
|
|
80
|
+
properties: list[SummaryProperty]
|
|
81
|
+
has_real_time_points: bool
|
|
82
|
+
raw: dict = field(repr=False)
|
siseli/state.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Device state, metadata, and energy-flow retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Awaitable, Callable
|
|
6
|
+
|
|
7
|
+
from ._utils import parse_datetime
|
|
8
|
+
from .const import DEFAULT_DATA_SOURCE
|
|
9
|
+
from .models.common import LookupValue
|
|
10
|
+
from .models.state import (
|
|
11
|
+
AttributeGroup,
|
|
12
|
+
AttributeGroupSet,
|
|
13
|
+
AttributeMetadata,
|
|
14
|
+
DeviceState,
|
|
15
|
+
EnergyFlow,
|
|
16
|
+
FlowNode,
|
|
17
|
+
StateAttribute,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
RequestFunc = Callable[..., Awaitable[Any]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _parse_lookup_value(raw: dict) -> LookupValue:
|
|
25
|
+
return LookupValue(
|
|
26
|
+
value=raw.get("value"),
|
|
27
|
+
name=raw.get("text", raw.get("name", raw.get("localeText", ""))),
|
|
28
|
+
locale_text=raw.get("localeText"),
|
|
29
|
+
raw=raw,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_attribute(key: str, raw: dict) -> StateAttribute:
|
|
35
|
+
return StateAttribute(
|
|
36
|
+
key=raw.get("key", key),
|
|
37
|
+
unit=raw.get("unit", ""),
|
|
38
|
+
value=raw.get("value"),
|
|
39
|
+
value_display=raw.get("valueDisplay", str(raw.get("value", ""))),
|
|
40
|
+
name_display=raw.get("nameDisplay", raw.get("name", "")),
|
|
41
|
+
is_hidden=raw.get("isHidden"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _parse_attribute_metadata(raw: dict, key: str = "") -> AttributeMetadata:
|
|
47
|
+
enum_values = [_parse_lookup_value(item) for item in raw.get("enumValues", [])]
|
|
48
|
+
return AttributeMetadata(
|
|
49
|
+
key=raw.get("key", key),
|
|
50
|
+
name=raw.get("name", raw.get("nameDisplay", "")),
|
|
51
|
+
name_display=raw.get("nameDisplay", raw.get("name", "")),
|
|
52
|
+
unit=raw.get("unit", ""),
|
|
53
|
+
value_type=raw.get("valueType"),
|
|
54
|
+
category=raw.get("category"),
|
|
55
|
+
operation_mode=raw.get("operationMode"),
|
|
56
|
+
is_hidden=raw.get("isHidden"),
|
|
57
|
+
is_config_attribute=raw.get("isConfigAttribute"),
|
|
58
|
+
is_writable_config_attribute=raw.get("isWritableConfigAttribute"),
|
|
59
|
+
is_readable_config_attribute=raw.get("isReadableConfigAttribute"),
|
|
60
|
+
is_state_attribute=raw.get("isStateAttribute"),
|
|
61
|
+
is_event_attribute=raw.get("isEventAttribute"),
|
|
62
|
+
enum_values=enum_values,
|
|
63
|
+
raw=raw,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _parse_attribute_group(raw: dict) -> AttributeGroup:
|
|
69
|
+
return AttributeGroup(
|
|
70
|
+
id=raw.get("id", ""),
|
|
71
|
+
key=raw.get("key", ""),
|
|
72
|
+
category=raw.get("category"),
|
|
73
|
+
name=raw.get("name", ""),
|
|
74
|
+
description=raw.get("description", ""),
|
|
75
|
+
attributes=[_parse_attribute_metadata(item) for item in raw.get("attributes", [])],
|
|
76
|
+
raw=raw,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _parse_device_state(raw: dict) -> DeviceState:
|
|
82
|
+
fields_raw: dict = raw.get("fields", {})
|
|
83
|
+
fields = {key: _parse_attribute(key, attr) for key, attr in fields_raw.items()}
|
|
84
|
+
return DeviceState(
|
|
85
|
+
time=parse_datetime(raw.get("time")),
|
|
86
|
+
fields=fields,
|
|
87
|
+
raw=raw,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _parse_flow_node(raw: dict | None) -> FlowNode | None:
|
|
93
|
+
if raw is None:
|
|
94
|
+
return None
|
|
95
|
+
value_raw = raw.get("value")
|
|
96
|
+
value = _parse_attribute(raw.get("key", ""), value_raw) if value_raw else None
|
|
97
|
+
extra_values = [_parse_attribute(item.get("key", ""), item) for item in raw.get("extraValues", [])]
|
|
98
|
+
return FlowNode(
|
|
99
|
+
key=raw.get("key", ""),
|
|
100
|
+
locale_title=raw.get("localeTitle", ""),
|
|
101
|
+
value=value,
|
|
102
|
+
flow_direction=raw.get("flowDirection"),
|
|
103
|
+
is_light=raw.get("isLight"),
|
|
104
|
+
is_enabled=raw.get("isEnabled", raw.get("enabled", True)),
|
|
105
|
+
extra_values=extra_values,
|
|
106
|
+
raw=raw,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def fetch_device_state(
|
|
111
|
+
request: RequestFunc,
|
|
112
|
+
device_id: str,
|
|
113
|
+
*,
|
|
114
|
+
data_source: int = DEFAULT_DATA_SOURCE,
|
|
115
|
+
) -> DeviceState:
|
|
116
|
+
"""Return the latest telemetry snapshot for *device_id*."""
|
|
117
|
+
data = await request(
|
|
118
|
+
"GET",
|
|
119
|
+
"/apis/deviceState/simple/state/latest/v1",
|
|
120
|
+
params={"deviceId": device_id, "dataSource": data_source},
|
|
121
|
+
)
|
|
122
|
+
return _parse_device_state(data)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def fetch_device_attributes(
|
|
126
|
+
request: RequestFunc,
|
|
127
|
+
device_id: str,
|
|
128
|
+
*,
|
|
129
|
+
category: str = "",
|
|
130
|
+
render_in: str = "",
|
|
131
|
+
) -> list[AttributeMetadata]:
|
|
132
|
+
"""Return attribute metadata for the device state screen."""
|
|
133
|
+
data = await request(
|
|
134
|
+
"GET",
|
|
135
|
+
"/apis/deviceState/simple/gatherAttributes/v1",
|
|
136
|
+
params={"deviceId": device_id, "category": category, "renderIn": render_in},
|
|
137
|
+
)
|
|
138
|
+
return [_parse_attribute_metadata(item) for item in data]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def fetch_device_attribute_groups(
|
|
142
|
+
request: RequestFunc,
|
|
143
|
+
device_id: str,
|
|
144
|
+
*,
|
|
145
|
+
category: str = "",
|
|
146
|
+
render_in: str = "",
|
|
147
|
+
) -> AttributeGroupSet:
|
|
148
|
+
"""Return grouped device attribute metadata."""
|
|
149
|
+
data = await request(
|
|
150
|
+
"GET",
|
|
151
|
+
"/apis/device/query/attribute/group",
|
|
152
|
+
params={"deviceId": device_id, "category": category, "renderIn": render_in},
|
|
153
|
+
)
|
|
154
|
+
return AttributeGroupSet(
|
|
155
|
+
gather_protocol_version_id=data.get("gatherProtocolVersionId", ""),
|
|
156
|
+
groups=[_parse_attribute_group(item) for item in data.get("attributesGroups", [])],
|
|
157
|
+
raw=data,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def fetch_energy_flow(
|
|
162
|
+
request: RequestFunc,
|
|
163
|
+
device_id: str,
|
|
164
|
+
*,
|
|
165
|
+
data_source: int = DEFAULT_DATA_SOURCE,
|
|
166
|
+
) -> EnergyFlow:
|
|
167
|
+
"""Return the current energy-flow diagram data for *device_id*."""
|
|
168
|
+
data = await request(
|
|
169
|
+
"GET",
|
|
170
|
+
"/apis/deviceState/simple/energy/flow/v1",
|
|
171
|
+
params={"deviceId": device_id, "dataSource": data_source},
|
|
172
|
+
)
|
|
173
|
+
device_state = _parse_device_state(data.get("deviceAttributeState", {}))
|
|
174
|
+
return EnergyFlow(
|
|
175
|
+
device_state=device_state,
|
|
176
|
+
pv_panel=_parse_flow_node(data.get("pvPanelFlow")),
|
|
177
|
+
grid=_parse_flow_node(data.get("gridFlow")),
|
|
178
|
+
battery=_parse_flow_node(data.get("batteryFlow")),
|
|
179
|
+
load=_parse_flow_node(data.get("loadFlow")),
|
|
180
|
+
generator=_parse_flow_node(data.get("generatorFlow")),
|
|
181
|
+
ups=_parse_flow_node(data.get("upsFlow")),
|
|
182
|
+
ct=_parse_flow_node(data.get("ctFlow")),
|
|
183
|
+
raw=data,
|
|
184
|
+
)
|
siseli/station.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Station retrieval and aggregation helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Awaitable, Callable
|
|
6
|
+
|
|
7
|
+
from ._utils import parse_datetime, serialize_datetime
|
|
8
|
+
from .const import DEFAULT_PAGE_SIZE
|
|
9
|
+
from .models.common import PagedResult, TimePoint
|
|
10
|
+
from .models.station import Station, StationEnergyFlow, StationSummary, SummaryCategory, SummaryProperty
|
|
11
|
+
from .state import _parse_flow_node
|
|
12
|
+
|
|
13
|
+
RequestFunc = Callable[..., Awaitable[Any]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_station(raw: dict) -> Station:
|
|
18
|
+
return Station(
|
|
19
|
+
id=raw.get("id", ""),
|
|
20
|
+
name=raw.get("name", ""),
|
|
21
|
+
timezone=raw.get("timezone"),
|
|
22
|
+
country=raw.get("country"),
|
|
23
|
+
province=raw.get("province"),
|
|
24
|
+
city=raw.get("city"),
|
|
25
|
+
area=raw.get("area"),
|
|
26
|
+
address=raw.get("address"),
|
|
27
|
+
longitude=raw.get("longitude"),
|
|
28
|
+
latitude=raw.get("latitude"),
|
|
29
|
+
station_type=raw.get("stationType"),
|
|
30
|
+
connected_grid_type=raw.get("connectedGridType"),
|
|
31
|
+
state=raw.get("state"),
|
|
32
|
+
is_online=raw.get("isOnline"),
|
|
33
|
+
installed_capacity=raw.get("installedCapacity"),
|
|
34
|
+
total_power=raw.get("totalPower"),
|
|
35
|
+
daily_produced_quantity=raw.get("dailyProducedQuantity"),
|
|
36
|
+
total_produced_quantity=raw.get("totalProducedQuantity"),
|
|
37
|
+
created_at=parse_datetime(raw.get("createdAt")),
|
|
38
|
+
raw=raw,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_time_point(raw: dict) -> TimePoint:
|
|
44
|
+
return TimePoint(
|
|
45
|
+
time=parse_datetime(raw.get("time")),
|
|
46
|
+
time_display=raw.get("timeDisplay", ""),
|
|
47
|
+
value=raw.get("value", raw.get("generatedEnergy")),
|
|
48
|
+
is_real_value=raw.get("isRealValue"),
|
|
49
|
+
raw=raw,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parse_summary_category(raw: dict | None) -> SummaryCategory | None:
|
|
55
|
+
if raw is None:
|
|
56
|
+
return None
|
|
57
|
+
return SummaryCategory(
|
|
58
|
+
id=raw.get("id", ""),
|
|
59
|
+
key=raw.get("key", ""),
|
|
60
|
+
name=raw.get("name", ""),
|
|
61
|
+
raw=raw,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_station_summary(raw: dict) -> StationSummary:
|
|
67
|
+
properties = [
|
|
68
|
+
SummaryProperty(
|
|
69
|
+
property=item.get("property", {}),
|
|
70
|
+
time_points=[_parse_time_point(point) for point in item.get("timePoints", [])],
|
|
71
|
+
has_real_time_points=item.get("hasRealTimePoints"),
|
|
72
|
+
raw=item,
|
|
73
|
+
)
|
|
74
|
+
for item in raw.get("properties", [])
|
|
75
|
+
]
|
|
76
|
+
return StationSummary(
|
|
77
|
+
category=_parse_summary_category(raw.get("category")),
|
|
78
|
+
properties=properties,
|
|
79
|
+
has_real_time_points=raw.get("hasRealTimePoints", False),
|
|
80
|
+
raw=raw,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def fetch_station_list(
|
|
85
|
+
request: RequestFunc,
|
|
86
|
+
*,
|
|
87
|
+
page: int = 1,
|
|
88
|
+
count: int = DEFAULT_PAGE_SIZE,
|
|
89
|
+
name: str = "",
|
|
90
|
+
connected_grid_type: str = "",
|
|
91
|
+
state: str = "",
|
|
92
|
+
station_type: str = "",
|
|
93
|
+
) -> PagedResult[Station]:
|
|
94
|
+
"""Return a page of stations."""
|
|
95
|
+
data = await request(
|
|
96
|
+
"POST",
|
|
97
|
+
"/apis/station/list",
|
|
98
|
+
json={
|
|
99
|
+
"page": page,
|
|
100
|
+
"count": count,
|
|
101
|
+
"name": name,
|
|
102
|
+
"connectedGridType": connected_grid_type,
|
|
103
|
+
"state": state,
|
|
104
|
+
"stationType": station_type,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
items = [_parse_station(item) for item in data.get("list", [])]
|
|
108
|
+
return PagedResult(
|
|
109
|
+
page=data.get("page", page),
|
|
110
|
+
count=data.get("count", count),
|
|
111
|
+
total=data.get("total", len(items)),
|
|
112
|
+
items=items,
|
|
113
|
+
raw=data,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def fetch_station_details(request: RequestFunc, station_id: str) -> Station:
|
|
118
|
+
"""Return station details."""
|
|
119
|
+
data = await request("GET", "/apis/station/details", params={"stationId": station_id})
|
|
120
|
+
return _parse_station(data)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def fetch_station_energy_flow(
|
|
124
|
+
request: RequestFunc,
|
|
125
|
+
station_id: str,
|
|
126
|
+
*,
|
|
127
|
+
is_manual_refresh: bool = False,
|
|
128
|
+
) -> StationEnergyFlow:
|
|
129
|
+
"""Return current station energy-flow data."""
|
|
130
|
+
data = await request(
|
|
131
|
+
"GET",
|
|
132
|
+
"/apis/station/energy/flow",
|
|
133
|
+
params={"stationId": station_id, "isManualRefresh": is_manual_refresh},
|
|
134
|
+
)
|
|
135
|
+
return StationEnergyFlow(
|
|
136
|
+
is_support_flow=data.get("isSupportFlow", False),
|
|
137
|
+
time=parse_datetime(data.get("time")),
|
|
138
|
+
pv_panel=_parse_flow_node(data.get("pvPanelFlow")),
|
|
139
|
+
grid=_parse_flow_node(data.get("gridFlow")),
|
|
140
|
+
battery=_parse_flow_node(data.get("batteryFlow")),
|
|
141
|
+
load=_parse_flow_node(data.get("loadFlow")),
|
|
142
|
+
generator=_parse_flow_node(data.get("generatorFlow")),
|
|
143
|
+
ups=_parse_flow_node(data.get("upsFlow")),
|
|
144
|
+
ct=_parse_flow_node(data.get("ctFlow")),
|
|
145
|
+
raw=data,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def fetch_station_income(
|
|
150
|
+
request: RequestFunc,
|
|
151
|
+
station_id: str,
|
|
152
|
+
aggregation: str,
|
|
153
|
+
*,
|
|
154
|
+
time: Any = None,
|
|
155
|
+
) -> list[TimePoint]:
|
|
156
|
+
"""Return aggregated station income points."""
|
|
157
|
+
data = await request(
|
|
158
|
+
"POST",
|
|
159
|
+
f"/apis/stationOverView/income/{aggregation}",
|
|
160
|
+
params={"stationId": station_id},
|
|
161
|
+
json={"time": serialize_datetime(time)},
|
|
162
|
+
)
|
|
163
|
+
return [_parse_time_point(item) for item in data]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def fetch_station_state_summary(
|
|
167
|
+
request: RequestFunc,
|
|
168
|
+
station_id: str,
|
|
169
|
+
summary_category_key: str,
|
|
170
|
+
aggregation: str,
|
|
171
|
+
*,
|
|
172
|
+
time: Any = None,
|
|
173
|
+
) -> StationSummary:
|
|
174
|
+
"""Return aggregated station summary metrics."""
|
|
175
|
+
data = await request(
|
|
176
|
+
"POST",
|
|
177
|
+
f"/apis/stationOverView/stateAttributeSummary/category/{aggregation}",
|
|
178
|
+
params={"stationId": station_id, "summaryCategoryKey": summary_category_key},
|
|
179
|
+
json={"time": serialize_datetime(time)},
|
|
180
|
+
)
|
|
181
|
+
return _parse_station_summary(data)
|