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.
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/PKG-INFO +57 -19
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/README.md +56 -18
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/__init__.py +1 -1
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/__main__.py +52 -10
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/client.py +55 -17
- python_pooldose-0.7.6/src/pooldose/mappings/model_PDHC1H1HAR1V1_FW539224.json +620 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/model_PDPR1H1HAR1V0_FW539224.json +87 -25
- python_pooldose-0.7.6/src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +169 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mock_client.py +118 -26
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/request_handler.py +21 -28
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/type_definitions.py +1 -1
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/instant_values.py +124 -122
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/PKG-INFO +57 -19
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/SOURCES.txt +2 -0
- python_pooldose-0.7.6/tests/test_mock_client_set_value.py +216 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_request_handler.py +40 -30
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_ssl_support.py +4 -2
- python_pooldose-0.7.0/src/pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +0 -161
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/LICENSE +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/pyproject.toml +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/setup.cfg +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/constants.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/device_analyzer.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/__init__.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/mappings/mapping_info.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/py.typed +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/request_status.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/__init__.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/pooldose/values/static_values.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/dependency_links.txt +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/entry_points.txt +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/requires.txt +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/src/python_pooldose.egg-info/top_level.txt +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_client.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_device_analyzer.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_device_analyzer_integration.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_instant_values.py +0 -0
- {python_pooldose-0.7.0 → python_pooldose-0.7.6}/tests/test_mapping_info.py +0 -0
- {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.
|
|
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
|
-
|
|
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:
|
|
255
|
-
Firmware:
|
|
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:
|
|
300
|
-
# Firmware:
|
|
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
|
|
741
|
-
│ Get
|
|
780
|
+
│ Get Model ID │ ──────► PDZZ1H1HATEST1V1
|
|
781
|
+
│ Get Firmware Code │ ──────► 654321
|
|
742
782
|
└─────────────────┘
|
|
743
783
|
│
|
|
744
784
|
▼
|
|
745
785
|
┌─────────────────┐
|
|
746
|
-
│ Load 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
|
|
866
|
-
- **VA
|
|
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
|
|
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.
|
|
988
|
+
### Latest Release (0.7.6)
|
|
948
989
|
|
|
949
|
-
-
|
|
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
|
-
|
|
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:
|
|
236
|
-
Firmware:
|
|
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:
|
|
281
|
-
# Firmware:
|
|
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
|
|
722
|
-
│ Get
|
|
761
|
+
│ Get Model ID │ ──────► PDZZ1H1HATEST1V1
|
|
762
|
+
│ Get Firmware Code │ ──────► 654321
|
|
723
763
|
└─────────────────┘
|
|
724
764
|
│
|
|
725
765
|
▼
|
|
726
766
|
┌─────────────────┐
|
|
727
|
-
│ Load 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
|
|
847
|
-
- **VA
|
|
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
|
|
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.
|
|
969
|
+
### Latest Release (0.7.6)
|
|
929
970
|
|
|
930
|
-
-
|
|
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
|
|
@@ -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=
|
|
166
|
-
|
|
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()
|
|
197
|
-
"""Main
|
|
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(
|
|
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:
|
|
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,
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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,
|
|
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
|