python-pooldose 0.7.0__tar.gz → 0.7.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 (39) hide show
  1. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/PKG-INFO +57 -19
  2. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/README.md +56 -18
  3. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/__init__.py +1 -1
  4. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/__main__.py +52 -10
  5. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/client.py +55 -17
  6. python_pooldose-0.7.6/src/pooldose/mappings/model_PDHC1H1HAR1V1_FW539224.json +620 -0
  7. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/model_PDPR1H1HAR1V0_FW539224.json +87 -25
  8. python_pooldose-0.7.6/src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +169 -0
  9. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mock_client.py +118 -26
  10. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/request_handler.py +21 -28
  11. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/type_definitions.py +1 -1
  12. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/instant_values.py +124 -122
  13. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/PKG-INFO +57 -19
  14. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/SOURCES.txt +2 -0
  15. python_pooldose-0.7.6/tests/test_mock_client_set_value.py +216 -0
  16. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_request_handler.py +40 -30
  17. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_ssl_support.py +4 -2
  18. python_pooldose-0.7.0/src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +0 -161
  19. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/LICENSE +0 -0
  20. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/pyproject.toml +0 -0
  21. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/setup.cfg +0 -0
  22. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/constants.py +0 -0
  23. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/device_analyzer.py +0 -0
  24. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/__init__.py +0 -0
  25. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/mapping_info.py +0 -0
  26. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/py.typed +0 -0
  27. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/request_status.py +0 -0
  28. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/__init__.py +0 -0
  29. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/static_values.py +0 -0
  30. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/dependency_links.txt +0 -0
  31. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/entry_points.txt +0 -0
  32. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/requires.txt +0 -0
  33. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/top_level.txt +0 -0
  34. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_client.py +0 -0
  35. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_device_analyzer.py +0 -0
  36. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_device_analyzer_integration.py +0 -0
  37. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_instant_values.py +0 -0
  38. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_mapping_info.py +0 -0
  39. {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_static_values.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-pooldose
3
- Version: 0.7.0
3
+ Version: 0.7.6
4
4
  Summary: Unoffical async Python client for SEKO PoolDose devices
5
5
  Author-email: Lukas Maertin <pypi@lukas-maertin.de>
6
6
  License-Expression: MIT
@@ -19,10 +19,12 @@ Dynamic: license-file
19
19
 
20
20
  # python-pooldose
21
21
 
22
- Unofficial async Python client for [SEKO](https://www.seko.com/) Pooldosing systems. SEKO is a manufacturer of various monitoring and control devices for pools and spas.
22
+ Unofficial async Python client for [SEKO](https://www.seko.com/) Pooldosing systems. SEKO is a manufacturer of various monitoring and control devices for pools and spas. Some devices from [VÁGNER POOL](https://www.vagnerpool.com/web/en/) are compatible as well.
23
23
 
24
24
  This client uses an undocumented local HTTP API. It provides live readings for pool sensors such as temperature, pH, ORP/Redox, as well as status information and control over the dosing logic.
25
25
 
26
+ > **Disclaimer:** Use at your own risk. No liability for damages or malfunctions.
27
+
26
28
  ## Features
27
29
 
28
30
  - **Async/await support** for non-blocking operations
@@ -47,7 +49,7 @@ This client uses an undocumented local HTTP API. It provides live readings for p
47
49
  │ ├── WiFi Station Info (optional)
48
50
  │ ├── Access Point Info (optional)
49
51
  │ └── Network Info
50
- └── Load Mapping JSON (based on MODEL_ID + FW_CODE)
52
+ └── Load mapping JSON (based on model_id + fw_code)
51
53
 
52
54
  2. Get Static Values
53
55
  └── Device information and configuration
@@ -251,8 +253,8 @@ The analyzer provides comprehensive information about your device:
251
253
  ```
252
254
  === DEVICE ANALYSIS ===
253
255
  Device: 01234567890A_DEVICE
254
- Model: PDPR1H1HAW***
255
- Firmware: FW53****
256
+ Model ID: PDZZ1H1HATEST1V1
257
+ Firmware Code: 654321
256
258
 
257
259
  === WIDGETS (Visible UI Elements) ===
258
260
 
@@ -296,8 +298,8 @@ pooldose --host 192.168.1.100 --analyze
296
298
 
297
299
  # Output shows:
298
300
  # Device: 01987654321B_DEVICE
299
- # Model: PDPR2H2XYZ*** ← New model not yet supported
300
- # Firmware: FW54**** ← New firmware version
301
+ # Model ID: PDZZ1H1HATEST1V1 ← New model not yet supported
302
+ # Firmware Code: 654321 ← New firmware version
301
303
  #
302
304
  # Widgets discovered: 15 sensors, 8 controls, 12 settings
303
305
  ```
@@ -307,7 +309,40 @@ With this information, you can:
307
309
  - Share the widget structure for mapping development
308
310
  - Help expand device support for the community
309
311
 
310
- The device analyzer makes python-pooldose extensible and helps build support for the growing ecosystem of SEKO PoolDose devices.
312
+ The device analyzer makes python-pooldose extensible and helps build support for the growing ecosystem of SEKO PoolDose or VÁGNER POOL devices.
313
+
314
+ ### How to request support for a new device
315
+
316
+ If your device is not yet supported, please help us by creating a GitHub issue and providing the following information:
317
+
318
+ 1. **Run low-level analysis and share the output files:**
319
+ - Use the following curl commands.
320
+ - Replace the IP address and DeviceId (get the id from the header of the instantvalues.json file, e.g., '012345679_DEVICE') as needed:
321
+
322
+ - Download debug config info:
323
+ ```bash
324
+ curl http://<YOUR_DEVICE_IP>/api/v1/debug/config/info -o debuginfo.json
325
+ ```
326
+ **Important:** Before uploading, open `debuginfo.json` and remove any WiFi credentials.
327
+ - Download instant values
328
+ ```bash
329
+ curl --location --request POST http://<YOUR_DEVICE_IP>/api/v1/DWI/getInstantValues -o instantvalues.json
330
+ ```
331
+ - Download device language strings
332
+ ```bash
333
+ curl --location http://<YOUR_DEVICE_IP>/api/v1/DWI/getDeviceLanguage --data-raw '{"DeviceId":"YOUR_DEVICE_ID","LANG":"en"}' -o strings.json
334
+ ```
335
+ 2. **Optional: Run the analyzer and share the output:**
336
+ - Run this command if you set up python-pooldose already:
337
+ ```bash
338
+ pooldose --host <YOUR_DEVICE_IP> --analyze
339
+ ```
340
+ - Copy and paste the full output into your issue (remove any sensitive data).
341
+
342
+ 3. **Create a GitHub issue:**
343
+ - Attach the the 3 JSON files from above.
344
+ - Optionally attach the analyzer output if available.
345
+ - This will help us add support for your device faster!
311
346
 
312
347
  ## Examples
313
348
 
@@ -393,8 +428,13 @@ You can use the mock client with custom JSON files via the command line:
393
428
  # Use mock client with JSON file
394
429
  pooldose --mock path/to/your/data.json
395
430
 
431
+
432
+ # Use mock client with model and firmware code (Beispiel mit Fantasiewerten)
433
+ pooldose --mock path/to/your/data.json --model-id PDZZ1H1HATEST1V1 --fw-code 654321
434
+
396
435
  # Or as Python module
397
436
  python -m pooldose --mock path/to/your/data.json
437
+ python -m pooldose --mock path/to/your/data.json --model-id PDZZ1H1HATEST1V1 --fw-code 654321
398
438
  ```
399
439
 
400
440
  ### JSON Data Format
@@ -737,13 +777,13 @@ Mapping Discovery Process:
737
777
 
738
778
 
739
779
  ┌─────────────────┐
740
- │ Get MODEL_ID │ ──────► PDPR1H1HAW***
741
- │ Get FW_CODE │ ──────► 53****
780
+ │ Get Model ID │ ──────► PDZZ1H1HATEST1V1
781
+ │ Get Firmware Code │ ──────► 654321
742
782
  └─────────────────┘
743
783
 
744
784
 
745
785
  ┌─────────────────┐
746
- │ Load JSON File │ ──────► model_PDPR1H1HAW***_FW53****.json
786
+ │ Load JSON file │ ──────► model_PDZZ1H1HATEST1V1_FW654321.json
747
787
  └─────────────────┘
748
788
 
749
789
 
@@ -862,10 +902,11 @@ The `instant_values_structured()` method returns data organized by type:
862
902
 
863
903
  This client has been tested with:
864
904
 
865
- - **SEKO PoolDose Double/Dual WiFi** (Model: PDPR1H1HAW***, FW: 53****)
866
- - **VA dos BASIC Chlor - pH/ORP Wi-Fi** (Model: PDPR1H1HAR***, FW: 53****)
905
+ - **SEKO PoolDose Double** (Model: PDPR1H1HAW100, FW: 539187)
906
+ - **VÁGNER POOL VA DOS BASIC** (Model: PDHC1H1HAR1V0, FW: 539224)
907
+ - **VÁGNER POOL VA DOS EXACT** (Model: PDHC1H1HAR1V1, FW: 539224)
867
908
 
868
- Other SEKO PoolDose models may work but are untested. The client uses JSON mapping files to adapt to different device models and firmware versions (see e.g. `src/pooldose/mappings/model_PDPR1H1HAW***_FW53****.json`).
909
+ Other SEKO or VÁGNER POOL models may work but are untested. The client uses JSON mapping files to adapt to different device models and firmware versions (see e.g. `src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json`).
869
910
 
870
911
  > **Note:** The JSON files in the mappings directory define the device-specific data keys and their human-readable names for different PoolDose models and firmware versions.
871
912
 
@@ -944,9 +985,6 @@ Data Classification:
944
985
 
945
986
  For detailed release notes and version history, please see [CHANGELOG.md](CHANGELOG.md).
946
987
 
947
- ### Latest Release (0.7.0)
988
+ ### Latest Release (0.7.6)
948
989
 
949
- - **Connection Handling**: Improved session management for more reliable connections
950
- - **RequestHandler**: Centralized session management with internal _get_session method
951
- - **Performance**: Reduced connection overhead for multiple consecutive API calls
952
- - **Error Handling**: Better cleanup of HTTP sessions in error cases
990
+ - Added `--print-payload` option for debugging HTTP payloads
@@ -1,9 +1,11 @@
1
1
  # python-pooldose
2
2
 
3
- Unofficial async Python client for [SEKO](https://www.seko.com/) Pooldosing systems. SEKO is a manufacturer of various monitoring and control devices for pools and spas.
3
+ Unofficial async Python client for [SEKO](https://www.seko.com/) Pooldosing systems. SEKO is a manufacturer of various monitoring and control devices for pools and spas. Some devices from [VÁGNER POOL](https://www.vagnerpool.com/web/en/) are compatible as well.
4
4
 
5
5
  This client uses an undocumented local HTTP API. It provides live readings for pool sensors such as temperature, pH, ORP/Redox, as well as status information and control over the dosing logic.
6
6
 
7
+ > **Disclaimer:** Use at your own risk. No liability for damages or malfunctions.
8
+
7
9
  ## Features
8
10
 
9
11
  - **Async/await support** for non-blocking operations
@@ -28,7 +30,7 @@ This client uses an undocumented local HTTP API. It provides live readings for p
28
30
  │ ├── WiFi Station Info (optional)
29
31
  │ ├── Access Point Info (optional)
30
32
  │ └── Network Info
31
- └── Load Mapping JSON (based on MODEL_ID + FW_CODE)
33
+ └── Load mapping JSON (based on model_id + fw_code)
32
34
 
33
35
  2. Get Static Values
34
36
  └── Device information and configuration
@@ -232,8 +234,8 @@ The analyzer provides comprehensive information about your device:
232
234
  ```
233
235
  === DEVICE ANALYSIS ===
234
236
  Device: 01234567890A_DEVICE
235
- Model: PDPR1H1HAW***
236
- Firmware: FW53****
237
+ Model ID: PDZZ1H1HATEST1V1
238
+ Firmware Code: 654321
237
239
 
238
240
  === WIDGETS (Visible UI Elements) ===
239
241
 
@@ -277,8 +279,8 @@ pooldose --host 192.168.1.100 --analyze
277
279
 
278
280
  # Output shows:
279
281
  # Device: 01987654321B_DEVICE
280
- # Model: PDPR2H2XYZ*** ← New model not yet supported
281
- # Firmware: FW54**** ← New firmware version
282
+ # Model ID: PDZZ1H1HATEST1V1 ← New model not yet supported
283
+ # Firmware Code: 654321 ← New firmware version
282
284
  #
283
285
  # Widgets discovered: 15 sensors, 8 controls, 12 settings
284
286
  ```
@@ -288,7 +290,40 @@ With this information, you can:
288
290
  - Share the widget structure for mapping development
289
291
  - Help expand device support for the community
290
292
 
291
- The device analyzer makes python-pooldose extensible and helps build support for the growing ecosystem of SEKO PoolDose devices.
293
+ The device analyzer makes python-pooldose extensible and helps build support for the growing ecosystem of SEKO PoolDose or VÁGNER POOL devices.
294
+
295
+ ### How to request support for a new device
296
+
297
+ If your device is not yet supported, please help us by creating a GitHub issue and providing the following information:
298
+
299
+ 1. **Run low-level analysis and share the output files:**
300
+ - Use the following curl commands.
301
+ - Replace the IP address and DeviceId (get the id from the header of the instantvalues.json file, e.g., '012345679_DEVICE') as needed:
302
+
303
+ - Download debug config info:
304
+ ```bash
305
+ curl http://<YOUR_DEVICE_IP>/api/v1/debug/config/info -o debuginfo.json
306
+ ```
307
+ **Important:** Before uploading, open `debuginfo.json` and remove any WiFi credentials.
308
+ - Download instant values
309
+ ```bash
310
+ curl --location --request POST http://<YOUR_DEVICE_IP>/api/v1/DWI/getInstantValues -o instantvalues.json
311
+ ```
312
+ - Download device language strings
313
+ ```bash
314
+ curl --location http://<YOUR_DEVICE_IP>/api/v1/DWI/getDeviceLanguage --data-raw '{"DeviceId":"YOUR_DEVICE_ID","LANG":"en"}' -o strings.json
315
+ ```
316
+ 2. **Optional: Run the analyzer and share the output:**
317
+ - Run this command if you set up python-pooldose already:
318
+ ```bash
319
+ pooldose --host <YOUR_DEVICE_IP> --analyze
320
+ ```
321
+ - Copy and paste the full output into your issue (remove any sensitive data).
322
+
323
+ 3. **Create a GitHub issue:**
324
+ - Attach the the 3 JSON files from above.
325
+ - Optionally attach the analyzer output if available.
326
+ - This will help us add support for your device faster!
292
327
 
293
328
  ## Examples
294
329
 
@@ -374,8 +409,13 @@ You can use the mock client with custom JSON files via the command line:
374
409
  # Use mock client with JSON file
375
410
  pooldose --mock path/to/your/data.json
376
411
 
412
+
413
+ # Use mock client with model and firmware code (Beispiel mit Fantasiewerten)
414
+ pooldose --mock path/to/your/data.json --model-id PDZZ1H1HATEST1V1 --fw-code 654321
415
+
377
416
  # Or as Python module
378
417
  python -m pooldose --mock path/to/your/data.json
418
+ python -m pooldose --mock path/to/your/data.json --model-id PDZZ1H1HATEST1V1 --fw-code 654321
379
419
  ```
380
420
 
381
421
  ### JSON Data Format
@@ -718,13 +758,13 @@ Mapping Discovery Process:
718
758
 
719
759
 
720
760
  ┌─────────────────┐
721
- │ Get MODEL_ID │ ──────► PDPR1H1HAW***
722
- │ Get FW_CODE │ ──────► 53****
761
+ │ Get Model ID │ ──────► PDZZ1H1HATEST1V1
762
+ │ Get Firmware Code │ ──────► 654321
723
763
  └─────────────────┘
724
764
 
725
765
 
726
766
  ┌─────────────────┐
727
- │ Load JSON File │ ──────► model_PDPR1H1HAW***_FW53****.json
767
+ │ Load JSON file │ ──────► model_PDZZ1H1HATEST1V1_FW654321.json
728
768
  └─────────────────┘
729
769
 
730
770
 
@@ -843,10 +883,11 @@ The `instant_values_structured()` method returns data organized by type:
843
883
 
844
884
  This client has been tested with:
845
885
 
846
- - **SEKO PoolDose Double/Dual WiFi** (Model: PDPR1H1HAW***, FW: 53****)
847
- - **VA dos BASIC Chlor - pH/ORP Wi-Fi** (Model: PDPR1H1HAR***, FW: 53****)
886
+ - **SEKO PoolDose Double** (Model: PDPR1H1HAW100, FW: 539187)
887
+ - **VÁGNER POOL VA DOS BASIC** (Model: PDHC1H1HAR1V0, FW: 539224)
888
+ - **VÁGNER POOL VA DOS EXACT** (Model: PDHC1H1HAR1V1, FW: 539224)
848
889
 
849
- Other SEKO PoolDose models may work but are untested. The client uses JSON mapping files to adapt to different device models and firmware versions (see e.g. `src/pooldose/mappings/model_PDPR1H1HAW***_FW53****.json`).
890
+ Other SEKO or VÁGNER POOL models may work but are untested. The client uses JSON mapping files to adapt to different device models and firmware versions (see e.g. `src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json`).
850
891
 
851
892
  > **Note:** The JSON files in the mappings directory define the device-specific data keys and their human-readable names for different PoolDose models and firmware versions.
852
893
 
@@ -925,9 +966,6 @@ Data Classification:
925
966
 
926
967
  For detailed release notes and version history, please see [CHANGELOG.md](CHANGELOG.md).
927
968
 
928
- ### Latest Release (0.7.0)
969
+ ### Latest Release (0.7.6)
929
970
 
930
- - **Connection Handling**: Improved session management for more reliable connections
931
- - **RequestHandler**: Centralized session management with internal _get_session method
932
- - **Performance**: Reduced connection overhead for multiple consecutive API calls
933
- - **Error Handling**: Better cleanup of HTTP sessions in error cases
971
+ - Added `--print-payload` option for debugging HTTP payloads
@@ -1,5 +1,5 @@
1
1
  """Async API client for SEKO Pooldose."""
2
2
  from .client import PooldoseClient
3
3
 
4
- __version__ = "0.7.0"
4
+ __version__ = "0.7.6"
5
5
  __all__ = ["PooldoseClient"]
@@ -5,6 +5,7 @@ import argparse
5
5
  import asyncio
6
6
  import sys
7
7
  from pathlib import Path
8
+ from typing import Optional
8
9
 
9
10
  from pooldose import __version__
10
11
  from pooldose.client import PooldoseClient, RequestStatus
@@ -12,7 +13,7 @@ from pooldose.device_analyzer import DeviceAnalyzer
12
13
  from pooldose.mock_client import MockPooldoseClient
13
14
  from pooldose.request_handler import RequestHandler
14
15
 
15
- # pylint: disable=line-too-long
16
+ # pylint: disable=line-too-long, too-many-locals)
16
17
 
17
18
  # Import demo utilities if available
18
19
  try:
@@ -109,7 +110,7 @@ async def run_device_analyzer(host: str, use_ssl: bool, port: int, show_all: boo
109
110
  print(f"Error during analysis: {e}")
110
111
 
111
112
 
112
- async def run_real_client(host: str, use_ssl: bool, port: int) -> None:
113
+ async def run_real_client(host: str, use_ssl: bool, port: int, print_payload: bool = False) -> None:
113
114
  """Run the real PooldoseClient."""
114
115
  print(f"Connecting to PoolDose device at {host}")
115
116
  if use_ssl:
@@ -117,12 +118,14 @@ async def run_real_client(host: str, use_ssl: bool, port: int) -> None:
117
118
  else:
118
119
  print(f"Using HTTP on port {port}")
119
120
 
121
+ # pylint: disable=no-value-for-parameter
120
122
  client = PooldoseClient(
121
123
  host=host,
122
124
  include_mac_lookup=True,
123
125
  use_ssl=use_ssl,
124
126
  port=port if port != 0 else None,
125
- timeout=30
127
+ timeout=30,
128
+ debug_payload=print_payload
126
129
  )
127
130
 
128
131
  try:
@@ -152,7 +155,7 @@ async def run_real_client(host: str, use_ssl: bool, port: int) -> None:
152
155
  print(f"Error during connection: {e}")
153
156
 
154
157
 
155
- async def run_mock_client(json_file: str) -> None:
158
+ async def run_mock_client(json_file: str, model_id: Optional[str] = None, fw_code: Optional[str] = None, print_payload: bool = False) -> None:
156
159
  """Run the MockPooldoseClient."""
157
160
  json_path = Path(json_file)
158
161
  if not json_path.exists():
@@ -161,9 +164,18 @@ async def run_mock_client(json_file: str) -> None:
161
164
 
162
165
  print(f"Loading mock data from: {json_file}")
163
166
 
167
+ # Create explicit arguments for MockPooldoseClient
168
+ json_file_path = json_path
169
+ include_sensitive_data = True
170
+ model_id_val = model_id if model_id is not None else "MOCK_MODEL"
171
+ fw_code_val = fw_code if fw_code is not None else "MOCK_FW"
172
+
164
173
  client = MockPooldoseClient(
165
- json_file_path=json_path,
166
- include_sensitive_data=True
174
+ json_file_path=json_file_path,
175
+ model_id=model_id_val,
176
+ fw_code=fw_code_val,
177
+ include_sensitive_data=include_sensitive_data,
178
+ inspect_payload=print_payload,
167
179
  )
168
180
 
169
181
  try:
@@ -193,8 +205,8 @@ async def run_mock_client(json_file: str) -> None:
193
205
  print(f"Error during mock demo: {e}")
194
206
 
195
207
 
196
- def main() -> None:
197
- """Main entry point for command-line interface."""
208
+ def main(): # pylint: disable=too-many-locals
209
+ """Main CLI function."""
198
210
  parser = argparse.ArgumentParser(
199
211
  description="Python PoolDose Client - Connect to SEKO PoolDose devices",
200
212
  epilog="""
@@ -213,6 +225,12 @@ Examples:
213
225
 
214
226
  # Use mock client with JSON file
215
227
  python -m pooldose --mock path/to/your/data.json
228
+
229
+ # Use mock client with payload inspection
230
+ python -m pooldose --mock path/to/your/data.json --print-payload
231
+
232
+ # Connect to real device with payload inspection
233
+ python -m pooldose --host 192.168.1.100 --print-payload
216
234
  """,
217
235
  formatter_class=argparse.RawDescriptionHelpFormatter
218
236
  )
@@ -231,6 +249,20 @@ Examples:
231
249
  help="Path to JSON file for mock mode"
232
250
  )
233
251
 
252
+ # Additional mock parameters
253
+ parser.add_argument(
254
+ "--model-id",
255
+ type=str,
256
+ default=None,
257
+ help="Optional: Model ID for mock client (overrides JSON)"
258
+ )
259
+ parser.add_argument(
260
+ "--fw-code",
261
+ type=str,
262
+ default=None,
263
+ help="Optional: Firmware code for mock client (overrides JSON)"
264
+ )
265
+
234
266
  # Connection options
235
267
  parser.add_argument(
236
268
  "--ssl",
@@ -253,6 +285,11 @@ Examples:
253
285
  action="store_true",
254
286
  help="Analyze unknown device including hidden widgets (implies --analyze)"
255
287
  )
288
+ parser.add_argument(
289
+ "--print-payload",
290
+ action="store_true",
291
+ help="Enable payload debugging logging (for development/debugging only)"
292
+ )
256
293
  parser.add_argument(
257
294
  "--version",
258
295
  action="version",
@@ -286,10 +323,15 @@ Examples:
286
323
  args.host, args.ssl, port, show_all=args.analyze_all))
287
324
  else:
288
325
  # Normal client mode
289
- asyncio.run(run_real_client(args.host, args.ssl, port))
326
+ asyncio.run(run_real_client(args.host, args.ssl, port, args.print_payload))
290
327
  elif args.mock:
291
328
  # Mock mode
292
- asyncio.run(run_mock_client(args.mock))
329
+ asyncio.run(run_mock_client(
330
+ args.mock,
331
+ model_id=args.model_id,
332
+ fw_code=args.fw_code,
333
+ print_payload=args.print_payload
334
+ ))
293
335
 
294
336
  except KeyboardInterrupt:
295
337
  print("\nOperation cancelled by user.")
@@ -4,16 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import logging
7
- from typing import Optional, Tuple
7
+ from typing import Optional, Tuple, Any, Dict
8
8
 
9
9
  import aiohttp
10
10
  from getmac import get_mac_address
11
11
 
12
- from pooldose.type_definitions import (
13
- APIVersionResponse,
14
- DeviceInfoDict,
15
- StructuredValuesDict,
16
- )
17
12
 
18
13
  from pooldose.constants import get_default_device_info
19
14
  from pooldose.mappings.mapping_info import MappingInfo
@@ -34,7 +29,7 @@ class PooldoseClient:
34
29
  All getter methods return (status, data) and log errors.
35
30
  """
36
31
 
37
- def __init__(self, host: str, timeout: int = 30, *, websession: Optional[aiohttp.ClientSession] = None, include_sensitive_data: bool = False, include_mac_lookup: bool = False, use_ssl: bool = False, port: Optional[int] = None, ssl_verify: bool = True) -> None: # pylint: disable=too-many-arguments
32
+ def __init__(self, host: str, timeout: int = 30, *, websession: Optional[aiohttp.ClientSession] = None, include_sensitive_data: bool = False, include_mac_lookup: bool = False, use_ssl: bool = False, port: Optional[int] = None, ssl_verify: bool = True, debug_payload: bool = False) -> None: # pylint: disable=too-many-arguments
38
33
  """
39
34
  Initialize the Pooldose client.
40
35
 
@@ -48,6 +43,7 @@ class PooldoseClient:
48
43
  use_ssl (bool): If True, use HTTPS instead of HTTP.
49
44
  port (Optional[int]): Custom port for connections. Defaults to 80 for HTTP, 443 for HTTPS.
50
45
  ssl_verify (bool): If True, verify SSL certificates. Only used when use_ssl=True.
46
+ debug_payload (bool): If True, log and store payloads sent to device for debugging.
51
47
  """
52
48
  self._host = host
53
49
  self._timeout = timeout
@@ -56,12 +52,13 @@ class PooldoseClient:
56
52
  self._use_ssl = use_ssl
57
53
  self._port = port
58
54
  self._ssl_verify = ssl_verify
55
+ self._debug_payload = debug_payload
59
56
  self._last_data = None
60
57
  self._websession = websession
61
58
  self._request_handler: RequestHandler | None = None
62
59
 
63
60
  # Initialize device info with default or placeholder values
64
- self.device_info: DeviceInfoDict = get_default_device_info()
61
+ self.device_info: Dict[str, Any] = get_default_device_info()
65
62
 
66
63
  # Mapping-Status und Mapping-Cache
67
64
  self._mapping_status = None
@@ -81,7 +78,8 @@ class PooldoseClient:
81
78
  websession=self._websession if hasattr(self, '_websession') else None,
82
79
  use_ssl=self._use_ssl,
83
80
  port=self._port,
84
- ssl_verify=self._ssl_verify
81
+ ssl_verify=self._ssl_verify,
82
+ debug_payload=self._debug_payload
85
83
  )
86
84
  status = await self._request_handler.connect()
87
85
  if status != RequestStatus.SUCCESS:
@@ -105,7 +103,7 @@ class PooldoseClient:
105
103
  raise RuntimeError("Client not connected. Call connect() first.")
106
104
  return self._request_handler
107
105
 
108
- def check_apiversion_supported(self) -> Tuple[RequestStatus, APIVersionResponse]:
106
+ def check_apiversion_supported(self) -> Tuple[RequestStatus, Dict[str, Optional[str]]]:
109
107
  """
110
108
  Check if the loaded API version matches the supported version.
111
109
 
@@ -155,12 +153,16 @@ class PooldoseClient:
155
153
  self.device_info["SERIAL_NUMBER"] = gateway.get("DID")
156
154
  self.device_info["NAME"] = gateway.get("NAME")
157
155
  self.device_info["SW_VERSION"] = gateway.get("FW_REL")
158
- if (device := debug_config.get("DEVICES")[0]) is not None:
159
- self.device_info["DEVICE_ID"] = device.get("DID")
160
- self.device_info["MODEL"] = device.get("NAME")
161
- self.device_info["MODEL_ID"] = device.get("PRODUCT_CODE")
162
- self.device_info["FW_VERSION"] = device.get("FW_REL")
163
- self.device_info["FW_CODE"] = device.get("FW_CODE")
156
+
157
+ devices = debug_config.get("DEVICES")
158
+ if devices and len(devices) > 0:
159
+ device = devices[0]
160
+ if device is not None:
161
+ self.device_info["DEVICE_ID"] = device.get("DID")
162
+ self.device_info["MODEL"] = device.get("NAME")
163
+ self.device_info["MODEL_ID"] = device.get("PRODUCT_CODE")
164
+ self.device_info["FW_VERSION"] = device.get("FW_REL")
165
+ self.device_info["FW_CODE"] = device.get("FW_CODE")
164
166
  await asyncio.sleep(0.5)
165
167
 
166
168
  # Load mapping information
@@ -231,6 +233,7 @@ class PooldoseClient:
231
233
  tuple: (RequestStatus, StaticValues|None) - Status and static values object.
232
234
  """
233
235
  try:
236
+ # Device info is already Dict[str, Any]
234
237
  return RequestStatus.SUCCESS, StaticValues(self.device_info)
235
238
  except (ValueError, TypeError, KeyError) as err:
236
239
  _LOGGER.warning("Error creating StaticValues: %s", err)
@@ -258,13 +261,16 @@ class PooldoseClient:
258
261
  device_raw_data = raw_data.get("devicedata", {}).get(device_id, {})
259
262
  model_id = str(self.device_info.get("MODEL_ID", ""))
260
263
  fw_code = str(self.device_info.get("FW_CODE", ""))
264
+ if model_id == 'PDHC1H1HAR1V1' and fw_code == '539224':
265
+ #due to identifier issue in device firmware, use mapping prefix of PDPR1H1HAR1V0
266
+ model_id = 'PDPR1H1HAR1V0'
261
267
  prefix = f"{model_id}_FW{fw_code}_"
262
268
  return RequestStatus.SUCCESS, InstantValues(device_raw_data, mapping, prefix, device_id, self._request_handler)
263
269
  except (KeyError, TypeError, ValueError) as err:
264
270
  _LOGGER.warning("Error creating InstantValues: %s", err)
265
271
  return RequestStatus.UNKNOWN_ERROR, None
266
272
 
267
- async def instant_values_structured(self) -> Tuple[RequestStatus, StructuredValuesDict]:
273
+ async def instant_values_structured(self) -> Tuple[RequestStatus, Dict[str, Any]]:
268
274
  """
269
275
  Get instant values in structured JSON format with types as top-level keys.
270
276
 
@@ -284,3 +290,35 @@ class PooldoseClient:
284
290
  except (KeyError, TypeError, ValueError) as err:
285
291
  _LOGGER.error("Error creating structured instant values: %s", err)
286
292
  return RequestStatus.UNKNOWN_ERROR, {}
293
+
294
+ # Convenience setters -------------------------------------------------
295
+ async def set_switch(self, key: str, value: bool) -> bool:
296
+ """Set a mapped switch value without manually fetching InstantValues.
297
+
298
+ This convenience wrapper fetches the current InstantValues and
299
+ delegates the operation to its typed setter. Returns True on success.
300
+ """
301
+ status, iv = await self.instant_values()
302
+ if status != RequestStatus.SUCCESS or iv is None:
303
+ return False
304
+ return await iv.set_switch(key, value)
305
+
306
+ async def set_number(self, key: str, value: Any) -> bool:
307
+ """Set a mapped numeric value without manually fetching InstantValues."""
308
+ status, iv = await self.instant_values()
309
+ if status != RequestStatus.SUCCESS or iv is None:
310
+ return False
311
+ return await iv.set_number(key, value)
312
+
313
+ async def set_select(self, key: str, value: Any) -> bool:
314
+ """Set a mapped select option without manually fetching InstantValues."""
315
+ status, iv = await self.instant_values()
316
+ if status != RequestStatus.SUCCESS or iv is None:
317
+ return False
318
+ return await iv.set_select(key, value)
319
+
320
+ def get_last_payload(self) -> Optional[str]:
321
+ """Get the last payload sent to the device (if debug_payload is enabled)."""
322
+ if self._request_handler:
323
+ return self._request_handler.get_last_payload()
324
+ return None