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.
Files changed (49) hide show
  1. {hyxi_cloud_api-1.2.4/src/hyxi_cloud_api.egg-info → hyxi_cloud_api-1.2.6}/PKG-INFO +1 -1
  2. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/pyproject.toml +1 -1
  3. hyxi_cloud_api-1.2.6/src/hyxi_cloud_api/__init__.py +9 -0
  4. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api/api.py +36 -0
  5. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6/src/hyxi_cloud_api.egg-info}/PKG-INFO +1 -1
  6. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_api.py +7 -4
  7. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_extract_device_info_metadata.py +14 -0
  8. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_metrics_errors.py +5 -3
  9. hyxi_cloud_api-1.2.4/src/hyxi_cloud_api/__init__.py +0 -6
  10. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/LICENSE +0 -0
  11. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/README.md +0 -0
  12. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/setup.cfg +0 -0
  13. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/SOURCES.txt +0 -0
  14. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/dependency_links.txt +0 -0
  15. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/requires.txt +0 -0
  16. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/src/hyxi_cloud_api.egg-info/top_level.txt +0 -0
  17. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_all_in_one.py +0 -0
  18. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_build_plant_tasks.py +0 -0
  19. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_caching.py +0 -0
  20. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_compute_derived_metrics.py +0 -0
  21. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_device_control.py +0 -0
  22. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_device_entry.py +0 -0
  23. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_devices_errors.py +0 -0
  24. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_discovery.py +0 -0
  25. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_device_tasks.py +0 -0
  26. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_metric_tasks.py +0 -0
  27. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_execute_metrics_and_map_alarms.py +0 -0
  28. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_extract_battery_info.py +0 -0
  29. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_and_process_alarms.py +0 -0
  30. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_device_list_for_plant.py +0 -0
  31. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_plants.py +0 -0
  32. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_state.py +0 -0
  33. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fetch_sub_device_list.py +0 -0
  34. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_filter_metrics.py +0 -0
  35. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_fuzz_parser.py +0 -0
  36. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_get_f.py +0 -0
  37. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_handle_back_discovery_alarm.py +0 -0
  38. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_info_errors.py +0 -0
  39. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_mask_id.py +0 -0
  40. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parse_data_list.py +0 -0
  41. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parse_ems_kv.py +0 -0
  42. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_parser.py +0 -0
  43. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_process_alarms_and_back_discovery.py +0 -0
  44. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_process_devices_for_plant.py +0 -0
  45. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_sanitize_dict.py +0 -0
  46. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_sanitize_list.py +0 -0
  47. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_security_fix.py +0 -0
  48. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_token_errors.py +0 -0
  49. {hyxi_cloud_api-1.2.4 → hyxi_cloud_api-1.2.6}/tests/test_token_handling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyxi-cloud-api
3
- Version: 1.2.4
3
+ Version: 1.2.6
4
4
  Summary: An async API client for HYXi Cloud.
5
5
  Author-email: Veldkornet <Veldkornet@users.noreply.github.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hyxi-cloud-api"
7
- version = "1.2.4"
7
+ version = "1.2.6"
8
8
  authors = [
9
9
  { name="Veldkornet", email="Veldkornet@users.noreply.github.com" },
10
10
  ]
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyxi-cloud-api
3
- Version: 1.2.4
3
+ Version: 1.2.6
4
4
  Summary: An async API client for HYXi Cloud.
5
5
  Author-email: Veldkornet <Veldkornet@users.noreply.github.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -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'] is updated
290
- assert entry["metrics"] == {"existing_metric": "value", "new_metric": "new_value"}
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 entry['metrics'] is unchanged
313
- assert entry["metrics"] == {"existing_metric": "value"}
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
- # Ensure entry was not updated with EMS metrics
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
- assert not entry["metrics"]
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
 
@@ -1,6 +0,0 @@
1
- """Initialization module for HYXi Cloud API."""
2
-
3
- from .api import HyxiApiClient
4
-
5
- __version__ = "1.2.4"
6
- __all__ = ["HyxiApiClient"]
File without changes
File without changes
File without changes