hyxi-cloud-api 1.3.4__tar.gz → 1.3.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 (58) hide show
  1. {hyxi_cloud_api-1.3.4/src/hyxi_cloud_api.egg-info → hyxi_cloud_api-1.3.6}/PKG-INFO +2 -2
  2. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/pyproject.toml +2 -2
  3. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api/__init__.py +1 -1
  4. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api/api.py +26 -1
  5. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6/src/hyxi_cloud_api.egg-info}/PKG-INFO +2 -2
  6. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api.egg-info/requires.txt +1 -1
  7. hyxi_cloud_api-1.3.6/tests/test_compute_grid_metrics.py +79 -0
  8. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_push.py +14 -0
  9. hyxi_cloud_api-1.3.4/tests/test_compute_grid_metrics.py +0 -39
  10. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/LICENSE +0 -0
  11. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/README.md +0 -0
  12. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/setup.cfg +0 -0
  13. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api.egg-info/SOURCES.txt +0 -0
  14. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api.egg-info/dependency_links.txt +0 -0
  15. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/src/hyxi_cloud_api.egg-info/top_level.txt +0 -0
  16. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_additional_coverage.py +0 -0
  17. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_alarm_push.py +0 -0
  18. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_all_in_one.py +0 -0
  19. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_api.py +0 -0
  20. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_build_plant_tasks.py +0 -0
  21. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_caching.py +0 -0
  22. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_compute_battery_metrics.py +0 -0
  23. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_compute_derived_metrics.py +0 -0
  24. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_compute_load_metrics.py +0 -0
  25. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_device_control.py +0 -0
  26. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_device_entry.py +0 -0
  27. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_devices_errors.py +0 -0
  28. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_discovery.py +0 -0
  29. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_execute_device_tasks.py +0 -0
  30. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_execute_metric_tasks.py +0 -0
  31. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_execute_metrics_and_map_alarms.py +0 -0
  32. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_extract_battery_info.py +0 -0
  33. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_extract_device_info_metadata.py +0 -0
  34. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fetch_and_process_alarms.py +0 -0
  35. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fetch_device_list_for_plant.py +0 -0
  36. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fetch_plants.py +0 -0
  37. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fetch_state.py +0 -0
  38. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fetch_sub_device_list.py +0 -0
  39. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_filter_metrics.py +0 -0
  40. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_fuzz_parser.py +0 -0
  41. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_get_f.py +0 -0
  42. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_handle_back_discovery_alarm.py +0 -0
  43. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_info_errors.py +0 -0
  44. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_mask_id.py +0 -0
  45. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_metrics_errors.py +0 -0
  46. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_parse_data_list.py +0 -0
  47. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_parse_ems_kv.py +0 -0
  48. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_parser.py +0 -0
  49. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_process_alarms_and_back_discovery.py +0 -0
  50. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_process_devices_for_plant.py +0 -0
  51. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_sanitize_dict.py +0 -0
  52. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_sanitize_list.py +0 -0
  53. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_security_fix.py +0 -0
  54. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_subscriptions.py +0 -0
  55. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_token_errors.py +0 -0
  56. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_token_handling.py +0 -0
  57. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_validate_post_rate_ms.py +0 -0
  58. {hyxi_cloud_api-1.3.4 → hyxi_cloud_api-1.3.6}/tests/test_validate_subscription_device_sns.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyxi-cloud-api
3
- Version: 1.3.4
3
+ Version: 1.3.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
@@ -14,7 +14,7 @@ Classifier: Topic :: Home Automation
14
14
  Requires-Python: >=3.14
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: aiohttp>=3.14.0
17
+ Requires-Dist: aiohttp>=3.13.5
18
18
  Provides-Extra: test
19
19
  Requires-Dist: pytest>=9.0.3; extra == "test"
20
20
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hyxi-cloud-api"
7
- version = "1.3.4"
7
+ version = "1.3.6"
8
8
  authors = [
9
9
  { name="Veldkornet", email="Veldkornet@users.noreply.github.com" },
10
10
  ]
@@ -12,7 +12,7 @@ description = "An async API client for HYXi Cloud."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.14"
14
14
  dependencies = [
15
- "aiohttp>=3.14.0",
15
+ "aiohttp>=3.13.5",
16
16
  ]
17
17
  classifiers = [
18
18
  "Programming Language :: Python :: 3",
@@ -5,5 +5,5 @@ from .api import HyxiApiClient
5
5
  # Module-level alias so callers can do: from hyxi_cloud_api import VPP_ACTIVE_MODES
6
6
  VPP_ACTIVE_MODES: frozenset[str] = HyxiApiClient.VPP_ACTIVE_MODES
7
7
 
8
- __version__ = "1.3.4"
8
+ __version__ = "1.3.6"
9
9
  __all__ = ["VPP_ACTIVE_MODES", "HyxiApiClient"]
@@ -600,8 +600,16 @@ def _compute_load_metrics(m_raw: dict, derived: dict[str, float]) -> None:
600
600
 
601
601
  def _compute_grid_metrics(m_raw: dict, derived: dict[str, float]) -> None:
602
602
  """Calculate grid import/export metrics."""
603
- if "gridP" in m_raw:
603
+ grid = None
604
+ if "gridP" in m_raw and m_raw["gridP"] is not None and m_raw["gridP"] != "":
604
605
  grid = _get_f("gridP", m_raw, 1000.0)
606
+ elif "ph1p" in m_raw or "ph2p" in m_raw or "ph3p" in m_raw:
607
+ # Sum up individual phase powers (which are in Watts) to compute grid power fallback
608
+ grid = _get_f("ph1p", m_raw) + _get_f("ph2p", m_raw) + _get_f("ph3p", m_raw)
609
+ # Store the derived gridP back in metrics (in kW to align with API standard format)
610
+ derived["gridP"] = round(grid / 1000.0, 4)
611
+
612
+ if grid is not None:
605
613
  derived["grid_import"] = abs(grid) if grid < 0 else 0.0
606
614
  derived["grid_export"] = grid if grid > 0 else 0.0
607
615
 
@@ -820,6 +828,23 @@ def _flatten_nested_push_device(device: dict) -> dict: # pylint: disable=too-ma
820
828
  if "epsPowerW" in phi:
821
829
  flat[f"ph{i}Loadp"] = phi["epsPowerW"]
822
830
 
831
+ # 9. grid
832
+ if "grid" in device and isinstance(device["grid"], dict):
833
+ grd = device["grid"]
834
+ if "powerW" in grd and grd["powerW"] is not None:
835
+ try:
836
+ flat["gridP"] = float(grd["powerW"]) / 1000.0
837
+ except ValueError, TypeError:
838
+ flat["gridP"] = grd["powerW"]
839
+ if "frequencyHz" in grd:
840
+ flat["gridF"] = grd["frequencyHz"]
841
+ if "powerFactor" in grd:
842
+ flat["gridPfd"] = grd["powerFactor"]
843
+ if "energyInKwh" in grd:
844
+ flat["gridEIn"] = grd["energyInKwh"]
845
+ if "energyOutKwh" in grd:
846
+ flat["gridEOut"] = grd["energyOutKwh"]
847
+
823
848
  # Copy any other keys at the root that aren't dictionaries
824
849
  for k, v in device.items():
825
850
  if k not in flat and not isinstance(v, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyxi-cloud-api
3
- Version: 1.3.4
3
+ Version: 1.3.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
@@ -14,7 +14,7 @@ Classifier: Topic :: Home Automation
14
14
  Requires-Python: >=3.14
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: aiohttp>=3.14.0
17
+ Requires-Dist: aiohttp>=3.13.5
18
18
  Provides-Extra: test
19
19
  Requires-Dist: pytest>=9.0.3; extra == "test"
20
20
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -1,4 +1,4 @@
1
- aiohttp>=3.14.0
1
+ aiohttp>=3.13.5
2
2
 
3
3
  [test]
4
4
  pytest>=9.0.3
@@ -0,0 +1,79 @@
1
+ """Tests for the _compute_grid_metrics helper function in api.py."""
2
+
3
+ from src.hyxi_cloud_api.api import _compute_grid_metrics
4
+
5
+
6
+ class TestComputeGridMetrics:
7
+ """Tests for _compute_grid_metrics."""
8
+
9
+ def test_grid_import(self):
10
+ """Test grid_import when gridP is negative."""
11
+ m_raw = {"gridP": -1.5}
12
+ derived: dict[str, float] = {}
13
+ _compute_grid_metrics(m_raw, derived)
14
+ assert derived["grid_import"] == 1500.0
15
+ assert derived["grid_export"] == 0.0
16
+
17
+ def test_grid_export(self):
18
+ """Test grid_export when gridP is positive."""
19
+ m_raw = {"gridP": 2.25}
20
+ derived: dict[str, float] = {}
21
+ _compute_grid_metrics(m_raw, derived)
22
+ assert derived["grid_import"] == 0.0
23
+ assert derived["grid_export"] == 2250.0
24
+
25
+ def test_grid_zero(self):
26
+ """Test grid import/export when gridP is zero."""
27
+ m_raw = {"gridP": 0.0}
28
+ derived: dict[str, float] = {}
29
+ _compute_grid_metrics(m_raw, derived)
30
+ assert derived["grid_import"] == 0.0
31
+ assert derived["grid_export"] == 0.0
32
+
33
+ def test_grid_missing(self):
34
+ """Test missing gridP."""
35
+ m_raw = {}
36
+ derived: dict[str, float] = {}
37
+ _compute_grid_metrics(m_raw, derived)
38
+ assert "grid_import" not in derived
39
+ assert "grid_export" not in derived
40
+
41
+ def test_grid_fallback_phases_positive(self):
42
+ """Test grid fallback to phase active powers when gridP is missing."""
43
+ m_raw = {
44
+ "ph1p": 200.0,
45
+ "ph2p": 150.0,
46
+ "ph3p": 50.0,
47
+ }
48
+ derived: dict[str, float] = {}
49
+ _compute_grid_metrics(m_raw, derived)
50
+ assert derived["gridP"] == 0.4 # (200 + 150 + 50) / 1000 = 0.4 kW
51
+ assert derived["grid_import"] == 0.0
52
+ assert derived["grid_export"] == 400.0
53
+
54
+ def test_grid_fallback_phases_negative(self):
55
+ """Test grid fallback to phase active powers when importing (negative)."""
56
+ m_raw = {
57
+ "ph1p": -300.0,
58
+ "ph2p": -200.0,
59
+ "ph3p": -100.0,
60
+ }
61
+ derived: dict[str, float] = {}
62
+ _compute_grid_metrics(m_raw, derived)
63
+ assert derived["gridP"] == -0.6 # -600W / 1000 = -0.6 kW
64
+ assert derived["grid_import"] == 600.0
65
+ assert derived["grid_export"] == 0.0
66
+
67
+ def test_grid_priority(self):
68
+ """Test that gridP takes priority over phase fallback if present."""
69
+ m_raw = {
70
+ "gridP": 1.5,
71
+ "ph1p": -300.0,
72
+ "ph2p": -200.0,
73
+ "ph3p": -100.0,
74
+ }
75
+ derived: dict[str, float] = {}
76
+ _compute_grid_metrics(m_raw, derived)
77
+ assert "gridP" not in derived # gridP was in m_raw, not computed/derived
78
+ assert derived["grid_import"] == 0.0
79
+ assert derived["grid_export"] == 1500.0
@@ -218,6 +218,13 @@ def test_process_push_data_nested_format():
218
218
  "epsPowerW": 269.0,
219
219
  }
220
220
  },
221
+ "grid": {
222
+ "powerW": -1500.0,
223
+ "frequencyHz": 50.02,
224
+ "powerFactor": 0.98,
225
+ "energyInKwh": 1023.4,
226
+ "energyOutKwh": 504.2,
227
+ },
221
228
  }
222
229
  ]
223
230
  }
@@ -257,6 +264,13 @@ def test_process_push_data_nested_format():
257
264
  assert metrics["ph1i"] == 1.88
258
265
  assert metrics["ph1p"] == 450.0
259
266
  assert metrics["ph1Loadp"] == 269.0
267
+ assert metrics["gridP"] == -1.5
268
+ assert metrics["gridF"] == 50.02
269
+ assert metrics["gridPfd"] == 0.98
270
+ assert metrics["gridEIn"] == 1023.4
271
+ assert metrics["gridEOut"] == 504.2
272
+ assert metrics["grid_import"] == 1500.0
273
+ assert metrics["grid_export"] == 0.0
260
274
 
261
275
 
262
276
  def test_process_push_data_flat_ms_collect_time():
@@ -1,39 +0,0 @@
1
- """Tests for the _compute_grid_metrics helper function in api.py."""
2
-
3
- from src.hyxi_cloud_api.api import _compute_grid_metrics
4
-
5
-
6
- class TestComputeGridMetrics:
7
- """Tests for _compute_grid_metrics."""
8
-
9
- def test_grid_import(self):
10
- """Test grid_import when gridP is negative."""
11
- m_raw = {"gridP": -1.5}
12
- derived: dict[str, float] = {}
13
- _compute_grid_metrics(m_raw, derived)
14
- assert derived["grid_import"] == 1500.0
15
- assert derived["grid_export"] == 0.0
16
-
17
- def test_grid_export(self):
18
- """Test grid_export when gridP is positive."""
19
- m_raw = {"gridP": 2.25}
20
- derived: dict[str, float] = {}
21
- _compute_grid_metrics(m_raw, derived)
22
- assert derived["grid_import"] == 0.0
23
- assert derived["grid_export"] == 2250.0
24
-
25
- def test_grid_zero(self):
26
- """Test grid import/export when gridP is zero."""
27
- m_raw = {"gridP": 0.0}
28
- derived: dict[str, float] = {}
29
- _compute_grid_metrics(m_raw, derived)
30
- assert derived["grid_import"] == 0.0
31
- assert derived["grid_export"] == 0.0
32
-
33
- def test_grid_missing(self):
34
- """Test missing gridP."""
35
- m_raw = {}
36
- derived: dict[str, float] = {}
37
- _compute_grid_metrics(m_raw, derived)
38
- assert "grid_import" not in derived
39
- assert "grid_export" not in derived
File without changes
File without changes
File without changes