proconip 2.2.1__tar.gz → 2.3.1__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.
- {proconip-2.2.1 → proconip-2.3.1}/CHANGELOG.md +14 -0
- {proconip-2.2.1 → proconip-2.3.1}/PKG-INFO +6 -2
- {proconip-2.2.1 → proconip-2.3.1}/README.md +5 -1
- {proconip-2.2.1 → proconip-2.3.1}/src/proconip/_version.py +2 -2
- {proconip-2.2.1 → proconip-2.3.1}/src/proconip/api.py +34 -11
- {proconip-2.2.1 → proconip-2.3.1}/src/proconip/definitions.py +74 -11
- {proconip-2.2.1 → proconip-2.3.1}/tests/conftest.py +17 -0
- proconip-2.3.1/tests/fixtures/get_state_electrolysis.csv +6 -0
- {proconip-2.2.1 → proconip-2.3.1}/tests/test_definitions.py +45 -0
- {proconip-2.2.1 → proconip-2.3.1}/.gitignore +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/LICENSE +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/pyproject.toml +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/src/proconip/__init__.py +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/src/proconip/py.typed +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/tests/fixtures/get_dmx.csv +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/tests/fixtures/get_state.csv +0 -0
- {proconip-2.2.1 → proconip-2.3.1}/tests/test_api.py +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.1](https://github.com/ylabonte/proconip-pypi/compare/v2.3.0...v2.3.1) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
* confirm electrolysis bit polarity and document manual-dosage units/limits ([#102](https://github.com/ylabonte/proconip-pypi/issues/102)) ([cb44a11](https://github.com/ylabonte/proconip-pypi/commit/cb44a11336fc73bc9715f9ec12ecffeec8b8d16f))
|
|
9
|
+
|
|
10
|
+
## [2.3.0](https://github.com/ylabonte/proconip-pypi/compare/v2.2.1...v2.3.0) (2026-10-12)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* flag the chlorine canister columns as cell data under electrolysis ([#100](https://github.com/ylabonte/proconip-pypi/issues/100)) ([27ca22a](https://github.com/ylabonte/proconip-pypi/commit/27ca22acb2e42e47647318713670ddd9f3938daf))
|
|
16
|
+
|
|
3
17
|
## [2.2.1](https://github.com/ylabonte/proconip-pypi/compare/v2.2.0...v2.2.1) (2026-10-08)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: proconip
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Async Python library for interacting with the ProCon.IP pool controller.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ylabonte/proconip-pypi
|
|
6
6
|
Project-URL: Documentation, https://ylabonte.github.io/proconip-pypi/
|
|
@@ -165,6 +165,10 @@ asyncio.run(relay_switching_example())
|
|
|
165
165
|
|
|
166
166
|
Manual dosage depends on the same factors as if started from the web interface
|
|
167
167
|
of the pool control itself.
|
|
168
|
+
Durations are always in seconds. The controller's web interface allows up to
|
|
169
|
+
59:59 (3599 s) for liquid dosing and up to 23:59 hours (86340 s, whole minutes)
|
|
170
|
+
for chlorine when a salt electrolysis cell is configured; the library passes
|
|
171
|
+
the value through unchecked.
|
|
168
172
|
|
|
169
173
|
```python
|
|
170
174
|
import asyncio
|
|
@@ -176,7 +180,7 @@ async def manual_dosage_example():
|
|
|
176
180
|
client_session = aiohttp.ClientSession()
|
|
177
181
|
config = ConfigObject("http://192.168.2.3", "admin", "admin")
|
|
178
182
|
dosage_control = DosageControl(client_session, config)
|
|
179
|
-
await dosage_control.async_chlorine_dosage(
|
|
183
|
+
await dosage_control.async_chlorine_dosage(600) # start for 10 minutes
|
|
180
184
|
await dosage_control.async_ph_minus_dosage(60) # start for 1 minute
|
|
181
185
|
await client_session.close()
|
|
182
186
|
|
|
@@ -137,6 +137,10 @@ asyncio.run(relay_switching_example())
|
|
|
137
137
|
|
|
138
138
|
Manual dosage depends on the same factors as if started from the web interface
|
|
139
139
|
of the pool control itself.
|
|
140
|
+
Durations are always in seconds. The controller's web interface allows up to
|
|
141
|
+
59:59 (3599 s) for liquid dosing and up to 23:59 hours (86340 s, whole minutes)
|
|
142
|
+
for chlorine when a salt electrolysis cell is configured; the library passes
|
|
143
|
+
the value through unchecked.
|
|
140
144
|
|
|
141
145
|
```python
|
|
142
146
|
import asyncio
|
|
@@ -148,7 +152,7 @@ async def manual_dosage_example():
|
|
|
148
152
|
client_session = aiohttp.ClientSession()
|
|
149
153
|
config = ConfigObject("http://192.168.2.3", "admin", "admin")
|
|
150
154
|
dosage_control = DosageControl(client_session, config)
|
|
151
|
-
await dosage_control.async_chlorine_dosage(
|
|
155
|
+
await dosage_control.async_chlorine_dosage(600) # start for 10 minutes
|
|
152
156
|
await dosage_control.async_ph_minus_dosage(60) # start for 1 minute
|
|
153
157
|
await client_session.close()
|
|
154
158
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -595,18 +595,37 @@ async def async_start_dosage(
|
|
|
595
595
|
"""Trigger a manual, time-limited dosage on the controller.
|
|
596
596
|
|
|
597
597
|
Manual dosage is the safe alternative to switching a dosage relay on
|
|
598
|
-
directly:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
598
|
+
directly: it goes through the controller's own dosage logic, which the
|
|
599
|
+
TRM ties to preconditions such as the dosage channel being enabled and
|
|
600
|
+
the assigned filter pump running (a running dosage also stops when the
|
|
601
|
+
filter pump switches off). The TRM's daily maximum dosing amount may also
|
|
602
|
+
stop it: once the channel's daily amount is reached, the controller does
|
|
603
|
+
not dose any further. The protocol has no "dosage refused" response,
|
|
604
|
+
so a successful HTTP exchange does not prove that dosing started; check
|
|
605
|
+
the dosage relay state in `/GetState.csv` if you need to know.
|
|
606
|
+
|
|
607
|
+
The duration is always sent in seconds, as the controller's web UI does
|
|
608
|
+
(``/Command.htm?MAN_DOSAGE=<target>,<seconds>``). That UI limits the
|
|
609
|
+
input to ``mm:ss`` up to 59:59 (3599 s) for liquid dosing (chlorine,
|
|
610
|
+
pH-, pH+) and to ``hh:mm`` up to 23:59 (86340 s, whole minutes) for
|
|
611
|
+
chlorine when a salt electrolysis cell is configured (see
|
|
612
|
+
`GetStateData.is_electrolysis_enabled`). This function passes the value
|
|
613
|
+
through unchecked; how the controller treats values outside those
|
|
614
|
+
limits is not documented.
|
|
615
|
+
|
|
616
|
+
The web UI also stores the duration as the channel's default before it
|
|
617
|
+
sends the command: it rewrites ``MDOSTIME`` in the channel's ``.ini``
|
|
618
|
+
(``usr/rdxcntrl.ini``, ``usr/phcntrl.ini`` or ``usr/phpcntrl.ini``) via
|
|
619
|
+
``/usrcfg.cgi`` and pre-fills that value next time. This function sends
|
|
620
|
+
only the command and leaves that default untouched.
|
|
602
621
|
|
|
603
622
|
Args:
|
|
604
623
|
client_session: An open `aiohttp.ClientSession`.
|
|
605
624
|
config: Controller configuration.
|
|
606
625
|
dosage_target: Which dosing pump to engage (chlorine, pH-, or pH+).
|
|
607
|
-
dosage_duration: Run time in **seconds
|
|
608
|
-
|
|
609
|
-
|
|
626
|
+
dosage_duration: Run time in **seconds**, in both liquid and
|
|
627
|
+
electrolysis mode. The web UI allows 0–3599 s for liquid dosing
|
|
628
|
+
and 0–86340 s (whole minutes) for chlorine electrolysis.
|
|
610
629
|
timeout: Per-request timeout in seconds.
|
|
611
630
|
|
|
612
631
|
Returns:
|
|
@@ -662,10 +681,12 @@ class DosageControl:
|
|
|
662
681
|
async def async_chlorine_dosage(
|
|
663
682
|
self, dosage_duration: int, timeout: float | None = None
|
|
664
683
|
) -> str:
|
|
665
|
-
"""Run the chlorine dosage pump for ``dosage_duration`` seconds.
|
|
684
|
+
"""Run the chlorine dosage pump (or electrolysis cell) for ``dosage_duration`` seconds.
|
|
666
685
|
|
|
667
686
|
Args:
|
|
668
|
-
dosage_duration: Run time in seconds.
|
|
687
|
+
dosage_duration: Run time in seconds. The controller's web UI
|
|
688
|
+
allows up to 3599 s for liquid chlorine and up to 86340 s
|
|
689
|
+
(whole minutes) for a salt electrolysis cell.
|
|
669
690
|
timeout: Override for this call only. If ``None``, the timeout
|
|
670
691
|
bound in `__init__` is used.
|
|
671
692
|
|
|
@@ -687,7 +708,8 @@ class DosageControl:
|
|
|
687
708
|
"""Run the pH- dosage pump for ``dosage_duration`` seconds.
|
|
688
709
|
|
|
689
710
|
Args:
|
|
690
|
-
dosage_duration: Run time in seconds.
|
|
711
|
+
dosage_duration: Run time in seconds. The controller's web UI
|
|
712
|
+
allows up to 3599 s (59:59).
|
|
691
713
|
timeout: Override for this call only. If ``None``, the timeout
|
|
692
714
|
bound in `__init__` is used.
|
|
693
715
|
|
|
@@ -707,7 +729,8 @@ class DosageControl:
|
|
|
707
729
|
"""Run the pH+ dosage pump for ``dosage_duration`` seconds.
|
|
708
730
|
|
|
709
731
|
Args:
|
|
710
|
-
dosage_duration: Run time in seconds.
|
|
732
|
+
dosage_duration: Run time in seconds. The controller's web UI
|
|
733
|
+
allows up to 3599 s (59:59).
|
|
711
734
|
timeout: Override for this call only. If ``None``, the timeout
|
|
712
735
|
bound in `__init__` is used.
|
|
713
736
|
|
|
@@ -187,7 +187,10 @@ class DataObject:
|
|
|
187
187
|
temperature, ``16–23`` → relay, ``24–27`` → digital input,
|
|
188
188
|
``28–35`` → external relay, ``36–38`` → canister, ``39–41`` →
|
|
189
189
|
consumption. Anything outside this range falls into a sentinel
|
|
190
|
-
"uncategorized" bucket.
|
|
190
|
+
"uncategorized" bucket. The category is purely positional: with
|
|
191
|
+
a salt-electrolysis cell, columns 36 and 39 still land in the
|
|
192
|
+
canister/consumption categories although they describe the
|
|
193
|
+
cell (see `GetStateData.is_electrolysis_object`).
|
|
191
194
|
name: Sensor name as reported by the controller (e.g. ``"Redox"``).
|
|
192
195
|
unit: Unit string (``"mV"``, ``"°C"``, ``"%"``, ``"--"``, …).
|
|
193
196
|
offset: Calibration offset applied to ``value``.
|
|
@@ -620,7 +623,23 @@ class GetStateData:
|
|
|
620
623
|
return self._dosage_control & 1 == 1
|
|
621
624
|
|
|
622
625
|
def is_electrolysis_enabled(self) -> bool:
|
|
623
|
-
"""True if
|
|
626
|
+
"""True if the chlorine supply is a salt-electrolysis cell (bit 4).
|
|
627
|
+
|
|
628
|
+
When set, the chlorine channel is not fed from a canister: the
|
|
629
|
+
chlorine "canister" reading (column 36) is the cell's remaining
|
|
630
|
+
lifetime / maintenance interval in percent, and the chlorine
|
|
631
|
+
"consumption" reading (column 39) is today's *calculated* chlorine
|
|
632
|
+
production. Use `is_electrolysis_object` to tell those two objects
|
|
633
|
+
apart from real canister readings.
|
|
634
|
+
|
|
635
|
+
Polarity: the TRM labels bit 4 "Cl liquid || Elektrolysis" without
|
|
636
|
+
saying which value is which. The controller's own web UI settles it
|
|
637
|
+
(``topleft.htm``, firmware 1.7.6): it decodes
|
|
638
|
+
``rdx_canister_salt = (dos_cntr >> 4) & 0x01`` and shows
|
|
639
|
+
``"Flüssig NaClO"`` for 0 and ``"Salz Elektrolyse"`` for 1. A live
|
|
640
|
+
liquid-chlorine controller (``rdxcntrl.ini`` ``DTYPE=0``) reports
|
|
641
|
+
DOSAGE_CNTRL 257 with bit 4 clear, consistent with that.
|
|
642
|
+
"""
|
|
624
643
|
return self._dosage_control & 16 == 16
|
|
625
644
|
|
|
626
645
|
def is_ph_minus_dosage_enabled(self) -> bool:
|
|
@@ -651,6 +670,33 @@ class GetStateData:
|
|
|
651
670
|
return self.is_ph_plus_dosage_enabled()
|
|
652
671
|
return False
|
|
653
672
|
|
|
673
|
+
def is_electrolysis_object(self, data_entity: DataObject) -> bool:
|
|
674
|
+
"""Does this canister/consumption object describe a salt-electrolysis cell?
|
|
675
|
+
|
|
676
|
+
The controller reuses the chlorine canister columns for a salt
|
|
677
|
+
electrolysis setup instead of adding new ones. With electrolysis
|
|
678
|
+
enabled (see `is_electrolysis_enabled`):
|
|
679
|
+
|
|
680
|
+
- column 36 (`chlorine_canister`) is the cell's **remaining lifetime**
|
|
681
|
+
(or maintenance interval) in percent — the share of the configured
|
|
682
|
+
operating hours that is left, not a canister fill level;
|
|
683
|
+
- column 39 (`chlorine_consumption`) is **today's chlorine production**
|
|
684
|
+
calculated from the configured production rate, not a liquid
|
|
685
|
+
volume drawn from a canister.
|
|
686
|
+
|
|
687
|
+
Callers should label and present these objects accordingly (e.g. no
|
|
688
|
+
canister/volume wording). The pH- and pH+ columns are always real
|
|
689
|
+
canisters.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
data_entity: Any `DataObject` from this state.
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
True only for the chlorine canister (column 36) or chlorine
|
|
696
|
+
consumption (column 39) object while electrolysis is enabled.
|
|
697
|
+
"""
|
|
698
|
+
return data_entity.column in (36, 39) and self.is_electrolysis_enabled()
|
|
699
|
+
|
|
654
700
|
def get_dosage_relay(self, data_entity: DataObject) -> int | None:
|
|
655
701
|
"""Aggregated relay ID that handles the dosing for this canister/consumption entity.
|
|
656
702
|
|
|
@@ -912,20 +958,28 @@ class GetStateData:
|
|
|
912
958
|
|
|
913
959
|
@property
|
|
914
960
|
def canister_objects(self) -> list[DataObject]:
|
|
915
|
-
"""The three canister
|
|
961
|
+
"""The three canister remaining-content readings (columns 36–38), in percent.
|
|
916
962
|
|
|
917
963
|
Order: chlorine, pH-, pH+. Convenience properties `chlorine_canister`,
|
|
918
964
|
`ph_minus_canister`, and `ph_plus_canister` return individual entries.
|
|
965
|
+
The values are estimates the controller derives from pump runtime and
|
|
966
|
+
configured delivery rate. With a salt-electrolysis cell the chlorine
|
|
967
|
+
entry is the cell's remaining lifetime instead — check
|
|
968
|
+
`is_electrolysis_object`.
|
|
919
969
|
"""
|
|
920
970
|
return self._canister_objects
|
|
921
971
|
|
|
922
972
|
@property
|
|
923
973
|
def consumption_objects(self) -> list[DataObject]:
|
|
924
|
-
"""The three dosage
|
|
974
|
+
"""The three daily dosage amounts (columns 39–41).
|
|
925
975
|
|
|
926
976
|
Order: chlorine, pH-, pH+. Convenience properties
|
|
927
977
|
`chlorine_consumption`, `ph_minus_consumption`, and
|
|
928
|
-
`ph_plus_consumption` return individual entries.
|
|
978
|
+
`ph_plus_consumption` return individual entries. Each is the amount
|
|
979
|
+
dosed *today* (the controller resets it daily), in the unit the
|
|
980
|
+
controller reports (``ml``; the chlorine column may report ``g``). With a
|
|
981
|
+
salt-electrolysis cell the chlorine entry is today's calculated
|
|
982
|
+
chlorine production — check `is_electrolysis_object`.
|
|
929
983
|
"""
|
|
930
984
|
return self._consumption_objects
|
|
931
985
|
|
|
@@ -941,32 +995,41 @@ class GetStateData:
|
|
|
941
995
|
|
|
942
996
|
@property
|
|
943
997
|
def chlorine_canister(self) -> DataObject:
|
|
944
|
-
"""Chlorine canister
|
|
998
|
+
"""Chlorine canister remaining content in percent (column 36).
|
|
999
|
+
|
|
1000
|
+
With a salt-electrolysis cell (`is_electrolysis_enabled`) this is the
|
|
1001
|
+
cell's remaining lifetime / maintenance interval in percent instead.
|
|
1002
|
+
"""
|
|
945
1003
|
return self._canister_objects[0]
|
|
946
1004
|
|
|
947
1005
|
@property
|
|
948
1006
|
def ph_minus_canister(self) -> DataObject:
|
|
949
|
-
"""pH- canister
|
|
1007
|
+
"""pH- canister remaining content in percent (column 37)."""
|
|
950
1008
|
return self._canister_objects[1]
|
|
951
1009
|
|
|
952
1010
|
@property
|
|
953
1011
|
def ph_plus_canister(self) -> DataObject:
|
|
954
|
-
"""pH+ canister
|
|
1012
|
+
"""pH+ canister remaining content in percent (column 38)."""
|
|
955
1013
|
return self._canister_objects[2]
|
|
956
1014
|
|
|
957
1015
|
@property
|
|
958
1016
|
def chlorine_consumption(self) -> DataObject:
|
|
959
|
-
"""
|
|
1017
|
+
"""Chlorine dosed today (column 39), in ``ml`` or ``g``.
|
|
1018
|
+
|
|
1019
|
+
With a salt-electrolysis cell (`is_electrolysis_enabled`) this is
|
|
1020
|
+
today's chlorine production calculated from the configured
|
|
1021
|
+
production rate instead.
|
|
1022
|
+
"""
|
|
960
1023
|
return self._consumption_objects[0]
|
|
961
1024
|
|
|
962
1025
|
@property
|
|
963
1026
|
def ph_minus_consumption(self) -> DataObject:
|
|
964
|
-
"""
|
|
1027
|
+
"""pH- dosed today (column 40), in ``ml``."""
|
|
965
1028
|
return self._consumption_objects[1]
|
|
966
1029
|
|
|
967
1030
|
@property
|
|
968
1031
|
def ph_plus_consumption(self) -> DataObject:
|
|
969
|
-
"""
|
|
1032
|
+
"""pH+ dosed today (column 41), in ``ml``."""
|
|
970
1033
|
return self._consumption_objects[2]
|
|
971
1034
|
|
|
972
1035
|
@property
|
|
@@ -36,3 +36,20 @@ def get_state_data(get_state_csv: str) -> GetStateData:
|
|
|
36
36
|
@pytest.fixture
|
|
37
37
|
def get_dmx_data(get_dmx_csv: str) -> GetDmxData:
|
|
38
38
|
return GetDmxData(get_dmx_csv)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def get_state_electrolysis_csv() -> str:
|
|
43
|
+
"""`get_state.csv` reconfigured for a salt-electrolysis chlorine supply.
|
|
44
|
+
|
|
45
|
+
Derived from the real capture: DOSAGE_CNTRL 273 (Cl enabled + bit 4
|
|
46
|
+
electrolysis + pH- enabled), chlorine daily amount in ``g``, and non-zero
|
|
47
|
+
values in the chlorine "canister" (cell lifetime %) and "consumption"
|
|
48
|
+
(calculated production) columns, per the vendor's TRM column table.
|
|
49
|
+
"""
|
|
50
|
+
return (FIXTURES_DIR / "get_state_electrolysis.csv").read_text()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def get_state_electrolysis_data(get_state_electrolysis_csv: str) -> GetStateData:
|
|
55
|
+
return GetStateData(get_state_electrolysis_csv)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
SYSINFO,1.7.3,9559698,1,3,0,273,4,4,5
|
|
2
|
+
Time,n.a.,n.a.,n.a.,n.a.,CPU Temp,Redox,pH,Pumpe,n.a.,n.a.,n.a.,n.a.,n.a.,n.a.,n.a.,Terassenlicht,n.a.,n.a.,Gartenlicht,pH minus,Chlor,Bachlauf,Poolpumpe,TASTER1,TASTER2,TASTER3,TASTER4,n.a.,n.a.,n.a.,n.a.,n.a.,n.a.,n.a.,n.a.,Cl Rest,pH- Rest,pH+ Rest,Cl consumption,pH- consumption,pH+ consumption
|
|
3
|
+
h,mV,mV,Bar,ppm,C,mV,pH,C,C,C,C,C,C,C,C,--,--,--,--,--,--,--,--,cm/s,--,--,--,--,--,--,--,--,--,--,--,%,%,%,g,ml,ml
|
|
4
|
+
0,0,0,-0.400,6.00000,147.5,0.0,0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
5
|
+
1,0.0625,0.0625,0.0000125,0.000156,-0.00468750,0.0625,0.0078125,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.1,0.1,0.1,1,1,1
|
|
6
|
+
529,-44,-44,-44,-44,24878,14000,932,111,0,0,0,0,0,0,0,2,0,0,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,873,615,1000,412,0,0
|
|
@@ -266,6 +266,51 @@ def test_get_dosage_relay(get_state_data: GetStateData) -> None:
|
|
|
266
266
|
assert get_state_data.get_dosage_relay(get_state_data.analog_objects[0]) is None
|
|
267
267
|
|
|
268
268
|
|
|
269
|
+
def test_is_electrolysis_object_false_for_liquid_chlorine(get_state_data: GetStateData) -> None:
|
|
270
|
+
assert not get_state_data.is_electrolysis_enabled()
|
|
271
|
+
for obj in get_state_data.canister_objects + get_state_data.consumption_objects:
|
|
272
|
+
assert not get_state_data.is_electrolysis_object(obj)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_is_electrolysis_object_marks_chlorine_columns_only(
|
|
276
|
+
get_state_electrolysis_data: GetStateData,
|
|
277
|
+
) -> None:
|
|
278
|
+
state = get_state_electrolysis_data
|
|
279
|
+
assert state.is_electrolysis_enabled()
|
|
280
|
+
assert state.is_chlorine_dosage_enabled()
|
|
281
|
+
assert state.is_electrolysis_object(state.chlorine_canister)
|
|
282
|
+
assert state.is_electrolysis_object(state.chlorine_consumption)
|
|
283
|
+
# pH canisters stay real canisters
|
|
284
|
+
for obj in (
|
|
285
|
+
state.ph_minus_canister,
|
|
286
|
+
state.ph_plus_canister,
|
|
287
|
+
state.ph_minus_consumption,
|
|
288
|
+
state.ph_plus_consumption,
|
|
289
|
+
):
|
|
290
|
+
assert not state.is_electrolysis_object(obj)
|
|
291
|
+
# unrelated columns never qualify
|
|
292
|
+
assert not state.is_electrolysis_object(state.redox_electrode)
|
|
293
|
+
assert not state.is_electrolysis_object(state.analog_objects[0])
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_electrolysis_keeps_chlorine_columns_positional(
|
|
297
|
+
get_state_electrolysis_data: GetStateData,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Under electrolysis, columns 36/39 stay in the canister/consumption categories.
|
|
300
|
+
|
|
301
|
+
The ``%``/``g`` units and values below come from the synthesised
|
|
302
|
+
electrolysis fixture, not from observed controller output; the category
|
|
303
|
+
asserts are what this test pins.
|
|
304
|
+
"""
|
|
305
|
+
state = get_state_electrolysis_data
|
|
306
|
+
assert state.chlorine_canister.unit == "%"
|
|
307
|
+
assert state.chlorine_canister.value == pytest.approx(87.3)
|
|
308
|
+
assert state.chlorine_canister.category == CATEGORY_CANISTER
|
|
309
|
+
assert state.chlorine_consumption.unit == "g"
|
|
310
|
+
assert state.chlorine_consumption.value == pytest.approx(412)
|
|
311
|
+
assert state.chlorine_consumption.category == CATEGORY_CONSUMPTION
|
|
312
|
+
|
|
313
|
+
|
|
269
314
|
def test_is_dosage_relay_by_relay_object(get_state_data: GetStateData) -> None:
|
|
270
315
|
dosage_relay = get_state_data.get_relay(5) # chlorine dosage relay_id
|
|
271
316
|
assert get_state_data.is_dosage_relay(relay_object=dosage_relay)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|