hyxi-cloud-api 1.2.4__tar.gz → 1.2.6__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.
- {hyxi_cloud_api-1.2.4/src/hyxi_cloud_api.egg-info → hyxi_cloud_api-1.2.6}/PKG-INFO +1 -1
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/pyproject.toml +1 -1
- hyxi_cloud_api-1.2.6/src/hyxi_cloud_api/__init__.py +9 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api/api.py +36 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6/src/hyxi_cloud_api.egg-info}/PKG-INFO +1 -1
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_api.py +7 -4
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_extract_device_info_metadata.py +14 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_metrics_errors.py +5 -3
- hyxi_cloud_api-1.2.4/src/hyxi_cloud_api/__init__.py +0 -6
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/LICENSE +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/README.md +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/setup.cfg +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/SOURCES.txt +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/dependency_links.txt +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/requires.txt +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/top_level.txt +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_all_in_one.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_build_plant_tasks.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_caching.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_compute_derived_metrics.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_device_control.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_device_entry.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_devices_errors.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_discovery.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_device_tasks.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_metric_tasks.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_metrics_and_map_alarms.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_extract_battery_info.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_and_process_alarms.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_device_list_for_plant.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_plants.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_state.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_sub_device_list.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_filter_metrics.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fuzz_parser.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_get_f.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_handle_back_discovery_alarm.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_info_errors.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_mask_id.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parse_data_list.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parse_ems_kv.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parser.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_process_alarms_and_back_discovery.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_process_devices_for_plant.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_sanitize_dict.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_sanitize_list.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_security_fix.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_token_errors.py +0 -0
- {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_token_handling.py +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Initialization module for HYXi Cloud API."""
|
|
2
|
+
|
|
3
|
+
from .api import HyxiApiClient
|
|
4
|
+
|
|
5
|
+
# Module-level alias so callers can do: from hyxi_cloud_api import VPP_ACTIVE_MODES
|
|
6
|
+
VPP_ACTIVE_MODES: frozenset[str] = HyxiApiClient.VPP_ACTIVE_MODES
|
|
7
|
+
|
|
8
|
+
__version__ = "1.2.6"
|
|
9
|
+
__all__ = ["VPP_ACTIVE_MODES", "HyxiApiClient"]
|
|
@@ -752,6 +752,27 @@ _PEAK_SHAVING_VALUES = {
|
|
|
752
752
|
class HyxiApiClient: # pylint: disable=too-many-instance-attributes
|
|
753
753
|
"""Client for interacting with the HYXI Cloud API."""
|
|
754
754
|
|
|
755
|
+
DEFAULT_BASE_URL = "https://open.hyxicloud.com"
|
|
756
|
+
|
|
757
|
+
# ── VPP Awareness ────────────────────────────────────────────────────
|
|
758
|
+
# workMode values that indicate an active VPP program is controlling
|
|
759
|
+
# this device. The 'workMode' field is already returned in regular
|
|
760
|
+
# polling metrics — no separate API call is needed.
|
|
761
|
+
#
|
|
762
|
+
# Confirmed values (live observation + HYXI community research):
|
|
763
|
+
# "16" = VPP mode (virtual power plant dispatch active)
|
|
764
|
+
#
|
|
765
|
+
# Standard non-VPP modes for reference (NOT included here):
|
|
766
|
+
# "0" = Self-use / general
|
|
767
|
+
# "1" = Backup priority
|
|
768
|
+
# "2" = Time-of-use / peak shaving
|
|
769
|
+
# "3" = Feed-in priority
|
|
770
|
+
#
|
|
771
|
+
# workMode is returned as a string from the API ("16", not 16).
|
|
772
|
+
# Source: live workMode value observed during active VPP dispatch,
|
|
773
|
+
# corroborated by HYXI community register documentation.
|
|
774
|
+
VPP_ACTIVE_MODES: frozenset[str] = frozenset({"16"})
|
|
775
|
+
|
|
755
776
|
class ControlError(Exception):
|
|
756
777
|
"""Raised when a device control command fails."""
|
|
757
778
|
|
|
@@ -991,6 +1012,10 @@ class HyxiApiClient: # pylint: disable=too-many-instance-attributes
|
|
|
991
1012
|
if hw_ver:
|
|
992
1013
|
entry["hw_version"] = hw_ver
|
|
993
1014
|
|
|
1015
|
+
detailed_model = i_raw.get("model")
|
|
1016
|
+
if detailed_model and detailed_model != entry.get("model"):
|
|
1017
|
+
entry["model"] = detailed_model
|
|
1018
|
+
|
|
994
1019
|
base_info = {
|
|
995
1020
|
"hw_version": hw_ver,
|
|
996
1021
|
"_sw_ver_sys": sw_ver,
|
|
@@ -1069,6 +1094,17 @@ class HyxiApiClient: # pylint: disable=too-many-instance-attributes
|
|
|
1069
1094
|
"HYXI EMS telemetry probe returned no data for %s", _mask_id(sn)
|
|
1070
1095
|
)
|
|
1071
1096
|
|
|
1097
|
+
if not is_comm_unit:
|
|
1098
|
+
# Always write VPP keys so HA sensor attributes are never missing.
|
|
1099
|
+
# Pivot: VPP active detection is now driven by workMode.
|
|
1100
|
+
# If workMode is "16" (VPP active), set vppMode to "16" to trigger HA lockout.
|
|
1101
|
+
work_mode = entry["metrics"].get("workMode", "")
|
|
1102
|
+
entry["metrics"]["vppMode"] = work_mode
|
|
1103
|
+
entry["metrics"]["vppCode"] = ""
|
|
1104
|
+
entry["metrics"]["vppName"] = ""
|
|
1105
|
+
entry["metrics"]["vppManufacturer"] = ""
|
|
1106
|
+
entry["metrics"]["vppSupplierName"] = ""
|
|
1107
|
+
|
|
1072
1108
|
return sn, entry
|
|
1073
1109
|
|
|
1074
1110
|
async def _fetch_device_list_for_plant(self, plant_id: str) -> list[dict] | None:
|
|
@@ -286,8 +286,10 @@ async def test_fetch_ems_basic_data_success(caplog):
|
|
|
286
286
|
# Assert query_ems_basic_details was called
|
|
287
287
|
api.query_ems_basic_details.assert_called_once_with(ems_sn)
|
|
288
288
|
|
|
289
|
-
# Assert entry['metrics']
|
|
290
|
-
|
|
289
|
+
# Assert entry['metrics'] contains the merged EMS data (subset check — VPP
|
|
290
|
+
# keys are always written too, so we don't assert the exact full dict).
|
|
291
|
+
assert entry["metrics"]["existing_metric"] == "value"
|
|
292
|
+
assert entry["metrics"]["new_metric"] == "new_value"
|
|
291
293
|
|
|
292
294
|
|
|
293
295
|
@pytest.mark.asyncio
|
|
@@ -309,8 +311,9 @@ async def test_fetch_ems_basic_data_no_data(caplog):
|
|
|
309
311
|
# Assert query_ems_basic_details was called
|
|
310
312
|
api.query_ems_basic_details.assert_called_once_with("EMS123")
|
|
311
313
|
|
|
312
|
-
# Assert
|
|
313
|
-
|
|
314
|
+
# Assert the original metric is still present (VPP keys are added but
|
|
315
|
+
# existing_metric must be untouched).
|
|
316
|
+
assert entry["metrics"]["existing_metric"] == "value"
|
|
314
317
|
|
|
315
318
|
# Assert the correct debug log was emitted
|
|
316
319
|
assert "HYXI EMS telemetry probe returned no data for " in caplog.text
|
|
@@ -110,3 +110,17 @@ def test_extract_device_info_metadata_micro_ess():
|
|
|
110
110
|
assert base_info["wifiVer"] == "V01.00.00.01"
|
|
111
111
|
assert base_info["ratedFrequency"] == "50"
|
|
112
112
|
assert "batCap" in entry["metrics"]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_extract_device_info_metadata_model():
|
|
116
|
+
"""Test that the model field is extracted and updates the entry when changed."""
|
|
117
|
+
entry = {"metrics": {}, "device_type_code": "UNKNOWN", "model": "generic inverter"}
|
|
118
|
+
i_raw = {"model": "H10K-HT"}
|
|
119
|
+
HyxiApiClient._extract_device_info_metadata(entry, i_raw)
|
|
120
|
+
assert entry["model"] == "H10K-HT"
|
|
121
|
+
|
|
122
|
+
# Verify model is NOT overwritten if not present in response
|
|
123
|
+
entry2 = {"metrics": {}, "device_type_code": "UNKNOWN", "model": "H10K-HT"}
|
|
124
|
+
i_raw2 = {}
|
|
125
|
+
HyxiApiClient._extract_device_info_metadata(entry2, i_raw2)
|
|
126
|
+
assert entry2["model"] == "H10K-HT"
|
|
@@ -139,8 +139,8 @@ async def test_fetch_ems_basic_data_no_data(caplog):
|
|
|
139
139
|
await api._fetch_all_for_device("10602251600016", entry, "INVERTER")
|
|
140
140
|
|
|
141
141
|
assert "HYXI EMS telemetry probe returned no data for fefbfd75" in caplog.text
|
|
142
|
-
#
|
|
143
|
-
assert not entry["metrics"]
|
|
142
|
+
# VPP keys are always written for INVERTER types; no EMS data should be present.
|
|
143
|
+
assert "new_metric" not in entry["metrics"]
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
@pytest.mark.asyncio
|
|
@@ -150,6 +150,7 @@ async def test_fetch_ems_basic_data_error(caplog):
|
|
|
150
150
|
mock_session = MagicMock()
|
|
151
151
|
api = HyxiApiClient("ak", "sk", "https://api.com", mock_session)
|
|
152
152
|
|
|
153
|
+
# Raise inside _request so query_ems_basic_details catches it and returns {}
|
|
153
154
|
api._request = AsyncMock(side_effect=Exception("EMS data fetch failed"))
|
|
154
155
|
api._fetch_device_info = AsyncMock()
|
|
155
156
|
api._fetch_device_metrics = AsyncMock()
|
|
@@ -157,7 +158,8 @@ async def test_fetch_ems_basic_data_error(caplog):
|
|
|
157
158
|
entry = {"metrics": {}, "device_type_code": "EMS"}
|
|
158
159
|
await api._fetch_all_for_device("10602251600016", entry, "INVERTER")
|
|
159
160
|
|
|
160
|
-
|
|
161
|
+
# No EMS data should be present; VPP keys are written regardless.
|
|
162
|
+
assert "batSoc" not in entry["metrics"]
|
|
161
163
|
assert "HYXI EMS telemetry probe returned no data for fefbfd75" in caplog.text
|
|
162
164
|
|
|
163
165
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/dependency_links.txt
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
|
|
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
|
{hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_process_alarms_and_back_discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|