python-qube-heatpump 1.8.0__tar.gz → 1.10.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.
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/PKG-INFO +1 -1
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/pyproject.toml +1 -1
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/__init__.py +2 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/client.py +8 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/const.py +28 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/models.py +10 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/test_const.py +28 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/workflows/ci.yml +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/workflows/python-publish.yml +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.gitignore +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/AGENTS.md +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/CLAUDE.md +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/LICENSE +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/README.md +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/docs/modbus-lijst-qube-totaal.pdf +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/pytest.ini +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/__init__.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/base.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/binary_sensors.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/sensors.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/switches.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/network.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/py.typed +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/conftest.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/test_client.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/test_entities.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/test_models.py +0 -0
- {python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/tests/test_network.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-qube-heatpump
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: Async Modbus client for Qube Heat Pumps
|
|
5
5
|
Project-URL: Homepage, https://github.com/MattieGit/python-qube-heatpump
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/MattieGit/python-qube-heatpump/issues
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/__init__.py
RENAMED
|
@@ -7,6 +7,7 @@ from .const import (
|
|
|
7
7
|
StatusCode,
|
|
8
8
|
STATUS_CODE_MAP,
|
|
9
9
|
get_status_code,
|
|
10
|
+
resolve_status,
|
|
10
11
|
)
|
|
11
12
|
from .entities import (
|
|
12
13
|
BINARY_SENSORS,
|
|
@@ -37,6 +38,7 @@ __all__ = [
|
|
|
37
38
|
"StatusCode",
|
|
38
39
|
"STATUS_CODE_MAP",
|
|
39
40
|
"get_status_code",
|
|
41
|
+
"resolve_status",
|
|
40
42
|
# Network
|
|
41
43
|
"async_get_mac_address",
|
|
42
44
|
]
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/client.py
RENAMED
|
@@ -119,6 +119,11 @@ class QubeClient:
|
|
|
119
119
|
state.setpoint_room_cool_day = await _read(const.SETPOINT_COOL_DAY)
|
|
120
120
|
state.setpoint_room_cool_night = await _read(const.SETPOINT_COOL_NIGHT)
|
|
121
121
|
state.setpoint_dhw = await _read(const.USER_DHW_SETPOINT)
|
|
122
|
+
state.usr_pid_heatsetp = await _read(const.USER_HEAT_SETPOINT)
|
|
123
|
+
state.usr_pid_coolsetp = await _read(const.USER_COOL_SETPOINT)
|
|
124
|
+
|
|
125
|
+
# LinQ thermostat room temperature (optional)
|
|
126
|
+
state.modbus_roomtemp = await _read(const.TEMP_ROOM_MODBUS)
|
|
122
127
|
|
|
123
128
|
self._apply_monotonic_clamping(state)
|
|
124
129
|
|
|
@@ -128,6 +133,9 @@ class QubeClient:
|
|
|
128
133
|
if hasattr(state, key):
|
|
129
134
|
setattr(state, key, value)
|
|
130
135
|
|
|
136
|
+
# Compute unified status (status_code + anti-legionella override)
|
|
137
|
+
state.status = const.resolve_status(state.status_code, state.req_antileg_1)
|
|
138
|
+
|
|
131
139
|
return state
|
|
132
140
|
|
|
133
141
|
@property
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/const.py
RENAMED
|
@@ -32,6 +32,7 @@ class StatusCode(str, Enum):
|
|
|
32
32
|
HEATING = "heating"
|
|
33
33
|
START_FAIL = "start_fail"
|
|
34
34
|
HEATING_DHW = "heating_dhw"
|
|
35
|
+
ANTI_LEGIONELLA = "anti_legionella"
|
|
35
36
|
UNKNOWN = "unknown"
|
|
36
37
|
|
|
37
38
|
|
|
@@ -56,6 +57,33 @@ def get_status_code(code: int) -> StatusCode:
|
|
|
56
57
|
return STATUS_CODE_MAP.get(code, StatusCode.UNKNOWN)
|
|
57
58
|
|
|
58
59
|
|
|
60
|
+
def resolve_status(code: int | None, antileg_active: bool | None) -> StatusCode:
|
|
61
|
+
"""Resolve the unified heat pump status.
|
|
62
|
+
|
|
63
|
+
Combines the raw status code with the anti-legionella signal so consumers
|
|
64
|
+
receive a single status value. When anti-legionella is active the heat
|
|
65
|
+
pump is running a special DHW cycle that is otherwise hidden behind the
|
|
66
|
+
regular ``HEATING_DHW`` code; surfacing it as its own status makes the
|
|
67
|
+
device state visible without a separate binary sensor.
|
|
68
|
+
|
|
69
|
+
``ALARM`` always takes precedence over anti-legionella: safety signals
|
|
70
|
+
must not be masked by normal operational modes.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
code: Raw numeric status code from register 38, or ``None`` when
|
|
74
|
+
unavailable.
|
|
75
|
+
antileg_active: Current value of the anti-legionella discrete input
|
|
76
|
+
(``req_antileg_1``), or ``None`` when unavailable.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The resolved :class:`StatusCode` value.
|
|
80
|
+
"""
|
|
81
|
+
status = get_status_code(code) if code is not None else StatusCode.UNKNOWN
|
|
82
|
+
if antileg_active and status is not StatusCode.ALARM:
|
|
83
|
+
return StatusCode.ANTI_LEGIONELLA
|
|
84
|
+
return status
|
|
85
|
+
|
|
86
|
+
|
|
59
87
|
# Register definitions (Address, Type, Data Type, Scale, Offset)
|
|
60
88
|
# Scale/Offset are None if not used.
|
|
61
89
|
# Format: KEY = (Address, ModbusType, DataType, Scale, Offset)
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/models.py
RENAMED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
+
from .const import StatusCode
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
@dataclass
|
|
8
10
|
class QubeState:
|
|
@@ -30,6 +32,9 @@ class QubeState:
|
|
|
30
32
|
|
|
31
33
|
# Operation
|
|
32
34
|
status_code: int | None = None
|
|
35
|
+
# Unified status combining status_code with req_antileg_1 override.
|
|
36
|
+
# Computed by QubeClient.get_all_data(); see const.resolve_status().
|
|
37
|
+
status: StatusCode | None = None
|
|
33
38
|
compressor_speed: float | None = None
|
|
34
39
|
flow_rate: float | None = None
|
|
35
40
|
|
|
@@ -39,6 +44,11 @@ class QubeState:
|
|
|
39
44
|
setpoint_room_cool_day: float | None = None
|
|
40
45
|
setpoint_room_cool_night: float | None = None
|
|
41
46
|
setpoint_dhw: float | None = None
|
|
47
|
+
usr_pid_heatsetp: float | None = None
|
|
48
|
+
usr_pid_coolsetp: float | None = None
|
|
49
|
+
|
|
50
|
+
# LinQ thermostat room temperature (optional, not all installations have LinQ)
|
|
51
|
+
modbus_roomtemp: float | None = None
|
|
42
52
|
|
|
43
53
|
# Binary sensors - Outputs
|
|
44
54
|
dout_srcpmp_val: bool | None = None
|
|
@@ -4,6 +4,7 @@ from python_qube_heatpump.const import (
|
|
|
4
4
|
StatusCode,
|
|
5
5
|
STATUS_CODE_MAP,
|
|
6
6
|
get_status_code,
|
|
7
|
+
resolve_status,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
@@ -55,3 +56,30 @@ def test_get_status_code_unknown():
|
|
|
55
56
|
assert get_status_code(0) == StatusCode.UNKNOWN
|
|
56
57
|
assert get_status_code(99) == StatusCode.UNKNOWN
|
|
57
58
|
assert get_status_code(-1) == StatusCode.UNKNOWN
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_resolve_status_without_antileg():
|
|
62
|
+
"""Without anti-legionella active, resolve_status follows the code map."""
|
|
63
|
+
assert resolve_status(16, False) == StatusCode.HEATING
|
|
64
|
+
assert resolve_status(22, False) == StatusCode.HEATING_DHW
|
|
65
|
+
assert resolve_status(1, None) == StatusCode.STANDBY
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_resolve_status_antileg_overrides_normal_statuses():
|
|
69
|
+
"""Anti-legionella overrides every non-alarm status."""
|
|
70
|
+
assert resolve_status(16, True) == StatusCode.ANTI_LEGIONELLA
|
|
71
|
+
assert resolve_status(22, True) == StatusCode.ANTI_LEGIONELLA
|
|
72
|
+
assert resolve_status(1, True) == StatusCode.ANTI_LEGIONELLA
|
|
73
|
+
assert resolve_status(99, True) == StatusCode.ANTI_LEGIONELLA
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_resolve_status_alarm_not_masked_by_antileg():
|
|
77
|
+
"""ALARM must not be masked by anti-legionella."""
|
|
78
|
+
assert resolve_status(2, True) == StatusCode.ALARM
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_resolve_status_none_code():
|
|
82
|
+
"""None code resolves to UNKNOWN (or ANTI_LEGIONELLA if active)."""
|
|
83
|
+
assert resolve_status(None, False) == StatusCode.UNKNOWN
|
|
84
|
+
assert resolve_status(None, None) == StatusCode.UNKNOWN
|
|
85
|
+
assert resolve_status(None, True) == StatusCode.ANTI_LEGIONELLA
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml
RENAMED
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/ISSUE_TEMPLATE/config.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/.github/workflows/python-publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/docs/modbus-lijst-qube-totaal.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/entities/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/network.py
RENAMED
|
File without changes
|
{python_qube_heatpump-1.8.0 → python_qube_heatpump-1.10.0}/src/python_qube_heatpump/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|