pyenphase 2.2.2__tar.gz → 2.3.0__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.
- {pyenphase-2.2.2 → pyenphase-2.3.0}/PKG-INFO +1 -1
- {pyenphase-2.2.2 → pyenphase-2.3.0}/pyproject.toml +3 -3
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/__init__.py +4 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/const.py +2 -0
- pyenphase-2.3.0/src/pyenphase/models/c6combiner.py +59 -0
- pyenphase-2.3.0/src/pyenphase/models/collar.py +73 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/dry_contacts.py +1 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/envoy.py +6 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/ensemble.py +26 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/LICENSE +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/README.md +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/auth.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/envoy.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/exceptions.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/firmware.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/json.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/__init__.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/acb.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/common.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/encharge.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/enpower.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/home.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/inverter.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/meters.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/storage_settings.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/system_consumption.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/system_production.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/models/tariff.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/py.typed +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/ssl.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/__init__.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/api_v1_production.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/api_v1_production_inverters.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/base.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/device_data_inverters.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/generator.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/meters.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/production.py +0 -0
- {pyenphase-2.2.2 → pyenphase-2.3.0}/src/pyenphase/updaters/tariff.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyenphase"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Library to control enphase envoy"
|
|
5
5
|
authors = [{ name = "pyenphase", email = "cgarwood@gmail.com" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -10,7 +10,7 @@ dynamic = ["classifiers", "dependencies"]
|
|
|
10
10
|
|
|
11
11
|
[tool.poetry]
|
|
12
12
|
name = "pyenphase"
|
|
13
|
-
version = "2.
|
|
13
|
+
version = "2.3.0"
|
|
14
14
|
description = "Library to control enphase envoy"
|
|
15
15
|
authors = ["pyenphase <cgarwood@gmail.com>"]
|
|
16
16
|
classifiers = [
|
|
@@ -46,7 +46,7 @@ pytest = ">=7,<9"
|
|
|
46
46
|
pytest-cov = ">=5,<7"
|
|
47
47
|
types-orjson = "^3.6.2"
|
|
48
48
|
aioresponses = ">=0.7.6"
|
|
49
|
-
pytest-asyncio = ">=0.21.1,<1.
|
|
49
|
+
pytest-asyncio = ">=0.21.1,<1.2.0"
|
|
50
50
|
syrupy = "^4.5.0"
|
|
51
51
|
pytest-timeout = "^2.4.0"
|
|
52
52
|
|
|
@@ -12,6 +12,8 @@ from .exceptions import (
|
|
|
12
12
|
EnvoyProbeFailed,
|
|
13
13
|
)
|
|
14
14
|
from .models.acb import EnvoyACBPower, EnvoyBatteryAggregate
|
|
15
|
+
from .models.c6combiner import EnvoyC6CC
|
|
16
|
+
from .models.collar import EnvoyCollar
|
|
15
17
|
from .models.dry_contacts import EnvoyDryContactSettings, EnvoyDryContactStatus
|
|
16
18
|
from .models.encharge import EnvoyEncharge, EnvoyEnchargeAggregate, EnvoyEnchargePower
|
|
17
19
|
from .models.enpower import EnvoyEnpower
|
|
@@ -45,5 +47,7 @@ __all__ = (
|
|
|
45
47
|
"EnvoyBatteryAggregate",
|
|
46
48
|
"EnvoyDryContactSettings",
|
|
47
49
|
"EnvoyDryContactStatus",
|
|
50
|
+
"EnvoyCollar",
|
|
51
|
+
"EnvoyC6CC",
|
|
48
52
|
"EnvoyTariff",
|
|
49
53
|
)
|
|
@@ -101,6 +101,8 @@ class SupportedFeatures(enum.IntFlag):
|
|
|
101
101
|
GENERATOR = 2048 #: Envoy reports generator data
|
|
102
102
|
ACB = 4096 #: Envoy reports ACB Battery data
|
|
103
103
|
DETAILED_INVERTERS = 8192 #: Detailed inverter data is reported
|
|
104
|
+
COLLAR = 0x4000 #: Envoy reports a Collar
|
|
105
|
+
C6CC = 0x8000 #: Envoy reports a C6 Combiner controller
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
class PhaseNames(enum.StrEnum):
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Model for the Enphase C6 Combiner."""
|
|
2
|
+
|
|
3
|
+
# Data Source: URL_ENSEMBLE_INVENTORY
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Required keys for C6 Combiner Controller inventories
|
|
11
|
+
C6CC_REQUIRED_KEYS: frozenset[str] = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"admin_state",
|
|
14
|
+
"admin_state_str",
|
|
15
|
+
"communicating",
|
|
16
|
+
"img_load_date",
|
|
17
|
+
"installed",
|
|
18
|
+
"last_rpt_date",
|
|
19
|
+
"part_num",
|
|
20
|
+
"serial_num",
|
|
21
|
+
"dmir_version",
|
|
22
|
+
"fw_version",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class EnvoyC6CC:
|
|
29
|
+
"""Model for the Enphase C6 Combiner."""
|
|
30
|
+
|
|
31
|
+
admin_state: int
|
|
32
|
+
admin_state_str: str
|
|
33
|
+
firmware_loaded_date: int
|
|
34
|
+
firmware_version: str
|
|
35
|
+
installed_date: int
|
|
36
|
+
last_report_date: int
|
|
37
|
+
communicating: bool
|
|
38
|
+
part_number: str
|
|
39
|
+
serial_number: str
|
|
40
|
+
dmir_version: str
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_api(cls, inventory: dict[str, Any]) -> EnvoyC6CC | None:
|
|
44
|
+
"""Initialize from the API. Returns None if required keys are missing."""
|
|
45
|
+
missing_keys = C6CC_REQUIRED_KEYS - set(inventory)
|
|
46
|
+
if missing_keys:
|
|
47
|
+
return None
|
|
48
|
+
return cls(
|
|
49
|
+
admin_state=inventory["admin_state"],
|
|
50
|
+
admin_state_str=inventory["admin_state_str"],
|
|
51
|
+
communicating=inventory["communicating"],
|
|
52
|
+
firmware_loaded_date=inventory["img_load_date"],
|
|
53
|
+
firmware_version=inventory["fw_version"],
|
|
54
|
+
installed_date=inventory["installed"],
|
|
55
|
+
last_report_date=inventory["last_rpt_date"],
|
|
56
|
+
part_number=inventory["part_num"],
|
|
57
|
+
serial_number=inventory["serial_num"],
|
|
58
|
+
dmir_version=inventory["dmir_version"],
|
|
59
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Model for the IQ Meter Collar."""
|
|
2
|
+
|
|
3
|
+
# Data Source: URL_ENSEMBLE_INVENTORY
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Required keys for IQ Meter Collar inventories
|
|
11
|
+
COLLAR_REQUIRED_KEYS: frozenset[str] = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"admin_state",
|
|
14
|
+
"admin_state_str",
|
|
15
|
+
"communicating",
|
|
16
|
+
"img_load_date",
|
|
17
|
+
"img_pnum_running",
|
|
18
|
+
"installed",
|
|
19
|
+
"last_rpt_date",
|
|
20
|
+
"part_num",
|
|
21
|
+
"serial_num",
|
|
22
|
+
"temperature",
|
|
23
|
+
"mid_state",
|
|
24
|
+
"grid_state",
|
|
25
|
+
"control_error",
|
|
26
|
+
"collar_state",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(slots=True)
|
|
32
|
+
class EnvoyCollar:
|
|
33
|
+
"""Model for the Enphase IQ Meter Collar."""
|
|
34
|
+
|
|
35
|
+
admin_state: int
|
|
36
|
+
admin_state_str: str
|
|
37
|
+
firmware_loaded_date: int
|
|
38
|
+
firmware_version: str
|
|
39
|
+
installed_date: int
|
|
40
|
+
last_report_date: int
|
|
41
|
+
communicating: bool
|
|
42
|
+
mid_state: str
|
|
43
|
+
grid_state: str
|
|
44
|
+
part_number: str
|
|
45
|
+
serial_number: str
|
|
46
|
+
temperature: int
|
|
47
|
+
temperature_unit: str
|
|
48
|
+
control_error: int
|
|
49
|
+
collar_state: str
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_api(cls, inventory: dict[str, Any]) -> EnvoyCollar | None:
|
|
53
|
+
"""Initialize from the API. Returns None if required keys are missing."""
|
|
54
|
+
missing_keys = COLLAR_REQUIRED_KEYS - set(inventory)
|
|
55
|
+
if missing_keys:
|
|
56
|
+
return None
|
|
57
|
+
return cls(
|
|
58
|
+
admin_state=inventory["admin_state"],
|
|
59
|
+
admin_state_str=inventory["admin_state_str"],
|
|
60
|
+
communicating=inventory["communicating"],
|
|
61
|
+
firmware_loaded_date=inventory["img_load_date"],
|
|
62
|
+
firmware_version=inventory["img_pnum_running"],
|
|
63
|
+
installed_date=inventory["installed"],
|
|
64
|
+
last_report_date=inventory["last_rpt_date"],
|
|
65
|
+
part_number=inventory["part_num"],
|
|
66
|
+
serial_number=inventory["serial_num"],
|
|
67
|
+
temperature=inventory["temperature"],
|
|
68
|
+
temperature_unit="C",
|
|
69
|
+
mid_state=inventory["mid_state"],
|
|
70
|
+
grid_state=inventory["grid_state"],
|
|
71
|
+
control_error=inventory["control_error"],
|
|
72
|
+
collar_state=inventory["collar_state"],
|
|
73
|
+
)
|
|
@@ -4,6 +4,8 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from .acb import EnvoyACBPower, EnvoyBatteryAggregate
|
|
7
|
+
from .c6combiner import EnvoyC6CC
|
|
8
|
+
from .collar import EnvoyCollar
|
|
7
9
|
from .dry_contacts import EnvoyDryContactSettings, EnvoyDryContactStatus
|
|
8
10
|
from .encharge import EnvoyEncharge, EnvoyEnchargeAggregate, EnvoyEnchargePower
|
|
9
11
|
from .enpower import EnvoyEnpower
|
|
@@ -36,6 +38,10 @@ class EnvoyData:
|
|
|
36
38
|
acb_power: EnvoyACBPower | None = None
|
|
37
39
|
#: aggregated Enphase and ACB battery SOC and total capacity
|
|
38
40
|
battery_aggregate: EnvoyBatteryAggregate | None = None
|
|
41
|
+
#: IQ Meter collar, only for Envoy with IQ Meter Collar integrated consumption metering installed
|
|
42
|
+
collar: EnvoyCollar | None = None
|
|
43
|
+
#: Envoy C6 Combiner controller
|
|
44
|
+
c6cc: EnvoyC6CC | None = None
|
|
39
45
|
#: Consumption power & energy values, only for Envoy metered with CT installed
|
|
40
46
|
system_consumption: EnvoySystemConsumption | None = None
|
|
41
47
|
#: Solar Production power & energy values
|
|
@@ -14,6 +14,8 @@ from ..const import (
|
|
|
14
14
|
)
|
|
15
15
|
from ..exceptions import ENDPOINT_PROBE_EXCEPTIONS
|
|
16
16
|
from ..models.acb import EnvoyBatteryAggregate
|
|
17
|
+
from ..models.c6combiner import EnvoyC6CC
|
|
18
|
+
from ..models.collar import EnvoyCollar
|
|
17
19
|
from ..models.dry_contacts import EnvoyDryContactSettings, EnvoyDryContactStatus
|
|
18
20
|
from ..models.encharge import EnvoyEncharge, EnvoyEnchargeAggregate, EnvoyEnchargePower
|
|
19
21
|
from ..models.enpower import EnvoyEnpower
|
|
@@ -51,6 +53,10 @@ class EnvoyEnembleUpdater(EnvoyUpdater):
|
|
|
51
53
|
self._supported_features |= SupportedFeatures.ENPOWER
|
|
52
54
|
if item["type"] == "ENCHARGE":
|
|
53
55
|
self._supported_features |= SupportedFeatures.ENCHARGE
|
|
56
|
+
if item["type"] == "COLLAR":
|
|
57
|
+
self._supported_features |= SupportedFeatures.COLLAR
|
|
58
|
+
if item["type"] == "C6 COMBINER CONTROLLER":
|
|
59
|
+
self._supported_features |= SupportedFeatures.C6CC
|
|
54
60
|
|
|
55
61
|
return self._supported_features
|
|
56
62
|
|
|
@@ -132,3 +138,23 @@ class EnvoyEnembleUpdater(EnvoyUpdater):
|
|
|
132
138
|
envoy_data.battery_aggregate = EnvoyBatteryAggregate.from_api(
|
|
133
139
|
ensemble_secctrl_data
|
|
134
140
|
)
|
|
141
|
+
|
|
142
|
+
# IQ Meter collar seems like a single instance only
|
|
143
|
+
if supported_features & SupportedFeatures.COLLAR:
|
|
144
|
+
# Update Collar data
|
|
145
|
+
for item in ensemble_inventory_data:
|
|
146
|
+
if item["type"] != "COLLAR":
|
|
147
|
+
continue
|
|
148
|
+
if item.get("devices"):
|
|
149
|
+
collar_data = item["devices"][0]
|
|
150
|
+
envoy_data.collar = EnvoyCollar.from_api(collar_data)
|
|
151
|
+
|
|
152
|
+
# C6 Combiner seems like a single instance only
|
|
153
|
+
if supported_features & SupportedFeatures.C6CC:
|
|
154
|
+
# Update C6CC data
|
|
155
|
+
for item in ensemble_inventory_data:
|
|
156
|
+
if item["type"] != "C6 COMBINER CONTROLLER":
|
|
157
|
+
continue
|
|
158
|
+
if item.get("devices"):
|
|
159
|
+
c6cc_data = item["devices"][0]
|
|
160
|
+
envoy_data.c6cc = EnvoyC6CC.from_api(c6cc_data)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|