python-pooldose 0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Lukas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-pooldose
3
+ Version: 0.3.1
4
+ Summary: Unoffical async Python client for SEKO PoolDose devices
5
+ Author-email: Lukas Maertin <pypi@lukas-maertin.de>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/lmaertin/python-pooldose
8
+ Project-URL: Repository, https://github.com/lmaertin/python-pooldose
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: aiohttp
13
+ Requires-Dist: aiofiles
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: pytest-asyncio; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # python-pooldose
20
+ 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.
21
+ 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.
22
+
23
+ ## Features
24
+ - **Async/await support** for non-blocking operations
25
+ - **Dynamic sensor discovery** based on device model and firmware
26
+ - **Dictionary-style access** to instant values
27
+ - **Type-specific getters** for sensors, switches, numbers, selects
28
+ - **Secure by default** - WiFi passwords excluded unless explicitly requested
29
+ - **Comprehensive error handling** with detailed logging
30
+
31
+ ## API Overview
32
+
33
+ ### Program Flow
34
+
35
+ ```
36
+ 1. Create PooldoseClient
37
+ ├── Fetch Device Info
38
+ │ ├── Debug Config
39
+ │ ├── WiFi Station Info (optional)
40
+ │ ├── Access Point Info (optional)
41
+ │ └── Network Info
42
+ ├── Load Mapping JSON (based on MODEL_ID + FW_CODE)
43
+ └── Query Available Types
44
+ ├── Sensors
45
+ ├── Binary Sensors
46
+ ├── Numbers
47
+ ├── Switches
48
+ └── Selects
49
+
50
+ 2. Get Instant Values
51
+ └── Access Values via Dictionary Interface
52
+ ├── instant_values['temperature']
53
+ ├── instant_values.get('ph', default)
54
+ └── 'sensor_name' in instant_values
55
+
56
+ 3. Set Values via Type Methods
57
+ ├── set_number()
58
+ ├── set_switch()
59
+ └── set_select()
60
+ ```
61
+
62
+ ### API Architecture
63
+
64
+ ```
65
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
66
+ │ PooldoseClient │────│ RequestHandler │────│ HTTP Device │
67
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
68
+ │ │
69
+ │ ▼
70
+ │ ┌─────────────────┐
71
+ │ │ API Endpoints │
72
+ │ │ • get_debug │
73
+ │ │ • get_wifi │
74
+ │ │ • get_values │
75
+ │ │ • set_value │
76
+ │ └─────────────────┘
77
+
78
+
79
+ ┌─────────────────┐ ┌─────────────────┐
80
+ │ MappingInfo │────│ JSON Files │
81
+ └─────────────────┘ └─────────────────┘
82
+
83
+
84
+ ┌─────────────────┐
85
+ │ Type Discovery │
86
+ │ • Sensors │
87
+ │ • Switches │
88
+ │ • Numbers │
89
+ │ • Selects │
90
+ └─────────────────┘
91
+
92
+
93
+ ┌─────────────────┐ ┌─────────────────┐
94
+ │ InstantValues │────│ Dictionary API │
95
+ └─────────────────┘ └─────────────────┘
96
+
97
+
98
+ ┌─────────────────┐
99
+ │ Type Methods │
100
+ │ • set_number() │
101
+ │ • set_switch() │
102
+ │ • set_select() │
103
+ └─────────────────┘
104
+ ```
105
+
106
+ ## Prerequisites
107
+ 1. Install and set-up the PoolDose devices according to the user manual.
108
+ 1. In particular, connect the device to your WiFi network.
109
+ 2. Identify the IP address or hostname of the device.
110
+ 2. Browse to the IP address or hostname (default port: 80).
111
+ 1. Try to log in to the web interface with the default password (0000).
112
+ 2. Check availability of data in the web interface.
113
+ 3. Optionally: Block the device from internet access to ensure cloudless-only operation.
114
+
115
+ ## Installation
116
+
117
+ ```bash
118
+ pip install python-pooldose
119
+ ```
120
+
121
+ ## Example Usage
122
+
123
+ ### Basic Example
124
+ ```python
125
+ import asyncio
126
+ import json
127
+ from pooldose.client import PooldoseClient
128
+ from pooldose.request_handler import RequestStatus
129
+
130
+ HOST = "192.168.1.100" # Change this to your device's host or IP address
131
+ TIMEOUT = 30
132
+
133
+ async def main() -> None:
134
+ """Demonstrate PooldoseClient usage with new dictionary-based API."""
135
+
136
+ # Create client (excludes WiFi passwords by default)
137
+ status, client = await PooldoseClient.create(host=HOST, timeout=TIMEOUT)
138
+
139
+ # Optional: Include sensitive data like WiFi passwords
140
+ # status, client = await PooldoseClient.create(host=HOST, timeout=TIMEOUT, include_sensitive_data=True)
141
+
142
+ if status != RequestStatus.SUCCESS:
143
+ print(f"Error creating client: {status}")
144
+ return
145
+
146
+ print(f"Connected to {HOST}")
147
+ print("Device Info:", json.dumps(client.device_info, indent=2))
148
+
149
+ # --- Query available types dynamically ---
150
+ print("\nAvailable types:")
151
+ for typ, keys in client.available_types().items():
152
+ print(f" {typ}: {keys}")
153
+
154
+ # --- Query available sensors ---
155
+ print("\nAvailable sensors:")
156
+ for name, sensor in client.available_sensors().items():
157
+ print(f" {name}: key={sensor.key}, type={sensor.type}")
158
+ if sensor.conversion is not None:
159
+ print(f" conversion: {sensor.conversion}")
160
+
161
+ # --- Get static values ---
162
+ status, static_values = client.static_values()
163
+ if status == RequestStatus.SUCCESS:
164
+ print(f"Device Name: {static_values.sensor_name}")
165
+ print(f"Serial Number: {static_values.sensor_serial_number}")
166
+ print(f"Firmware Version: {static_values.sensor_fw_version}")
167
+
168
+ # --- Get instant values ---
169
+ status, instant_values = await client.instant_values()
170
+ if status != RequestStatus.SUCCESS:
171
+ print(f"Error getting instant values: {status}")
172
+ return
173
+
174
+ # --- Dictionary-style access ---
175
+
176
+ # Get all sensors at once
177
+ print("\nAll sensor values:")
178
+ sensors = instant_values.get_sensors()
179
+ for key, value in sensors.items():
180
+ if isinstance(value, tuple) and len(value) >= 2:
181
+ print(f" {key}: {value[0]} {value[1]}")
182
+
183
+ # Dictionary-style individual access
184
+ if "temperature" in instant_values:
185
+ temp = instant_values["temperature"]
186
+ print(f"Temperature: {temp[0]} {temp[1]}")
187
+
188
+ # Get with default
189
+ ph_value = instant_values.get("ph", "Not available")
190
+ print(f"pH: {ph_value}")
191
+
192
+ # --- Setting values ---
193
+
194
+ # Set number values
195
+ if "ph_target" in instant_values.get_numbers():
196
+ result = await instant_values.set_number("ph_target", 7.2)
197
+ print(f"Set pH target to 7.2: {result}")
198
+
199
+ # Set switch values
200
+ if "stop_pool_dosing" in instant_values.get_switches():
201
+ result = await instant_values.set_switch("stop_pool_dosing", True)
202
+ print(f"Set stop pool dosing: {result}")
203
+
204
+ if __name__ == "__main__":
205
+ asyncio.run(main())
206
+ ```
207
+
208
+ ### Advanced Usage
209
+
210
+ #### Type-specific Access
211
+ ```python
212
+ # Get all values by type
213
+ sensors = instant_values.get_sensors() # All sensor readings
214
+ binary_sensors = instant_values.get_binary_sensors() # All boolean states
215
+ numbers = instant_values.get_numbers() # All configurable numbers
216
+ switches = instant_values.get_switches() # All switch states
217
+ selects = instant_values.get_selects() # All select options
218
+
219
+ # Check available types dynamically
220
+ available_types = instant_values.available_types()
221
+ print("Available types:", list(available_types.keys()))
222
+ ```
223
+
224
+ #### Error Handling
225
+ ```python
226
+ from pooldose.request_handler import RequestStatus
227
+
228
+ status, client = await PooldoseClient.create("192.168.1.100")
229
+ if status == RequestStatus.SUCCESS:
230
+ print("Connected successfully")
231
+ elif status == RequestStatus.CONNECTION_ERROR:
232
+ print("Could not connect to device")
233
+ elif status == RequestStatus.API_VERSION_UNSUPPORTED:
234
+ print("Unsupported API version")
235
+ else:
236
+ print(f"Other error: {status}")
237
+ ```
238
+
239
+ ## API Reference
240
+
241
+ ### PooldoseClient
242
+
243
+ #### Methods
244
+ - `create(host, timeout=10, include_sensitive_data=False)` - Factory method to create and initialize client
245
+ - `static_values()` - Get static device information
246
+ - `instant_values()` - Get current sensor readings and device state
247
+ - `available_types()` - Get all available entity types
248
+ - `available_sensors()` - Get available sensor configurations
249
+ - `available_binary_sensors()` - Get available binary sensor configurations
250
+ - `available_numbers()` - Get available number configurations
251
+ - `available_switches()` - Get available switch configurations
252
+ - `available_selects()` - Get available select configurations
253
+
254
+ ### InstantValues
255
+
256
+ #### Dictionary Interface
257
+ ```python
258
+ # Reading
259
+ value = instant_values["sensor_name"]
260
+ value = instant_values.get("sensor_name", default)
261
+ exists = "sensor_name" in instant_values
262
+
263
+ # Writing (async)
264
+ await instant_values.__setitem__("switch_name", True)
265
+ ```
266
+
267
+ #### Type-specific Methods
268
+ ```python
269
+ # Getters
270
+ sensors = instant_values.get_sensors()
271
+ binary_sensors = instant_values.get_binary_sensors()
272
+ numbers = instant_values.get_numbers()
273
+ switches = instant_values.get_switches()
274
+ selects = instant_values.get_selects()
275
+
276
+ # Setters (async, with validation)
277
+ await instant_values.set_number("ph_target", 7.2)
278
+ await instant_values.set_switch("stop_dosing", True)
279
+ await instant_values.set_select("water_meter_unit", 1)
280
+ ```
281
+
282
+ ## Supported Devices
283
+
284
+ This client has been tested with:
285
+ - **PoolDose Double/Dual WiFi** (Model: PDPR1H1HAW100, FW: 539187)
286
+
287
+ 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_PDPR1H1HAW100_FW539187.json`).
288
+
289
+ > **Note:** The other JSON files in the `docs/` directory define the default English names for the data keys of the PoolDose devices. These mappings are used for display and documentation purposes.
290
+
291
+ ## Security
292
+
293
+ By default, the client excludes sensitive information like WiFi passwords from device info. To include sensitive data:
294
+
295
+ ```python
296
+ status, client = await PooldoseClient.create(
297
+ host="192.168.1.100",
298
+ include_sensitive_data=True
299
+ )
300
+ ```
301
+
302
+ ## Changelog
303
+
304
+ ### [0.3.1] - 2025-07-04
305
+ - First official release, published on PyPi
306
+ - Install with ```pip install python-pooldose```
307
+
308
+ ### [0.3.0] - 2025-07-02
309
+ - **BREAKING**: Changed from dataclass properties to dictionary-based access for instant values
310
+ - Added dynamic sensor discovery based on device mapping files
311
+ - Added type-specific getter methods (get_sensors, get_switches, etc.)
312
+ - Added type-specific setter methods with validation (set_number, set_switch, etc.)
313
+ - Added dictionary-style access (__getitem__, __setitem__, get, __contains__)
314
+ - Added configurable sensitive data handling (excludes WiFi passwords by default)
315
+ - Improved async file loading to prevent event loop blocking
316
+ - Enhanced error handling and logging
317
+ - Added comprehensive type annotations
318
+
319
+ ### [0.2.0] - 2024-06-25
320
+ - Added query feature to list all available sensors and actuators
321
+
322
+ ### [0.1.5] - 2024-06-24
323
+ - First working prototype for PoolDose Double/Dual WiFi supported
324
+ - All sensors and actuators for PoolDose Double/Dual WiFi supported
@@ -0,0 +1,306 @@
1
+ # python-pooldose
2
+ 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
+ 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.
4
+
5
+ ## Features
6
+ - **Async/await support** for non-blocking operations
7
+ - **Dynamic sensor discovery** based on device model and firmware
8
+ - **Dictionary-style access** to instant values
9
+ - **Type-specific getters** for sensors, switches, numbers, selects
10
+ - **Secure by default** - WiFi passwords excluded unless explicitly requested
11
+ - **Comprehensive error handling** with detailed logging
12
+
13
+ ## API Overview
14
+
15
+ ### Program Flow
16
+
17
+ ```
18
+ 1. Create PooldoseClient
19
+ ├── Fetch Device Info
20
+ │ ├── Debug Config
21
+ │ ├── WiFi Station Info (optional)
22
+ │ ├── Access Point Info (optional)
23
+ │ └── Network Info
24
+ ├── Load Mapping JSON (based on MODEL_ID + FW_CODE)
25
+ └── Query Available Types
26
+ ├── Sensors
27
+ ├── Binary Sensors
28
+ ├── Numbers
29
+ ├── Switches
30
+ └── Selects
31
+
32
+ 2. Get Instant Values
33
+ └── Access Values via Dictionary Interface
34
+ ├── instant_values['temperature']
35
+ ├── instant_values.get('ph', default)
36
+ └── 'sensor_name' in instant_values
37
+
38
+ 3. Set Values via Type Methods
39
+ ├── set_number()
40
+ ├── set_switch()
41
+ └── set_select()
42
+ ```
43
+
44
+ ### API Architecture
45
+
46
+ ```
47
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
48
+ │ PooldoseClient │────│ RequestHandler │────│ HTTP Device │
49
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
50
+ │ │
51
+ │ ▼
52
+ │ ┌─────────────────┐
53
+ │ │ API Endpoints │
54
+ │ │ • get_debug │
55
+ │ │ • get_wifi │
56
+ │ │ • get_values │
57
+ │ │ • set_value │
58
+ │ └─────────────────┘
59
+
60
+
61
+ ┌─────────────────┐ ┌─────────────────┐
62
+ │ MappingInfo │────│ JSON Files │
63
+ └─────────────────┘ └─────────────────┘
64
+
65
+
66
+ ┌─────────────────┐
67
+ │ Type Discovery │
68
+ │ • Sensors │
69
+ │ • Switches │
70
+ │ • Numbers │
71
+ │ • Selects │
72
+ └─────────────────┘
73
+
74
+
75
+ ┌─────────────────┐ ┌─────────────────┐
76
+ │ InstantValues │────│ Dictionary API │
77
+ └─────────────────┘ └─────────────────┘
78
+
79
+
80
+ ┌─────────────────┐
81
+ │ Type Methods │
82
+ │ • set_number() │
83
+ │ • set_switch() │
84
+ │ • set_select() │
85
+ └─────────────────┘
86
+ ```
87
+
88
+ ## Prerequisites
89
+ 1. Install and set-up the PoolDose devices according to the user manual.
90
+ 1. In particular, connect the device to your WiFi network.
91
+ 2. Identify the IP address or hostname of the device.
92
+ 2. Browse to the IP address or hostname (default port: 80).
93
+ 1. Try to log in to the web interface with the default password (0000).
94
+ 2. Check availability of data in the web interface.
95
+ 3. Optionally: Block the device from internet access to ensure cloudless-only operation.
96
+
97
+ ## Installation
98
+
99
+ ```bash
100
+ pip install python-pooldose
101
+ ```
102
+
103
+ ## Example Usage
104
+
105
+ ### Basic Example
106
+ ```python
107
+ import asyncio
108
+ import json
109
+ from pooldose.client import PooldoseClient
110
+ from pooldose.request_handler import RequestStatus
111
+
112
+ HOST = "192.168.1.100" # Change this to your device's host or IP address
113
+ TIMEOUT = 30
114
+
115
+ async def main() -> None:
116
+ """Demonstrate PooldoseClient usage with new dictionary-based API."""
117
+
118
+ # Create client (excludes WiFi passwords by default)
119
+ status, client = await PooldoseClient.create(host=HOST, timeout=TIMEOUT)
120
+
121
+ # Optional: Include sensitive data like WiFi passwords
122
+ # status, client = await PooldoseClient.create(host=HOST, timeout=TIMEOUT, include_sensitive_data=True)
123
+
124
+ if status != RequestStatus.SUCCESS:
125
+ print(f"Error creating client: {status}")
126
+ return
127
+
128
+ print(f"Connected to {HOST}")
129
+ print("Device Info:", json.dumps(client.device_info, indent=2))
130
+
131
+ # --- Query available types dynamically ---
132
+ print("\nAvailable types:")
133
+ for typ, keys in client.available_types().items():
134
+ print(f" {typ}: {keys}")
135
+
136
+ # --- Query available sensors ---
137
+ print("\nAvailable sensors:")
138
+ for name, sensor in client.available_sensors().items():
139
+ print(f" {name}: key={sensor.key}, type={sensor.type}")
140
+ if sensor.conversion is not None:
141
+ print(f" conversion: {sensor.conversion}")
142
+
143
+ # --- Get static values ---
144
+ status, static_values = client.static_values()
145
+ if status == RequestStatus.SUCCESS:
146
+ print(f"Device Name: {static_values.sensor_name}")
147
+ print(f"Serial Number: {static_values.sensor_serial_number}")
148
+ print(f"Firmware Version: {static_values.sensor_fw_version}")
149
+
150
+ # --- Get instant values ---
151
+ status, instant_values = await client.instant_values()
152
+ if status != RequestStatus.SUCCESS:
153
+ print(f"Error getting instant values: {status}")
154
+ return
155
+
156
+ # --- Dictionary-style access ---
157
+
158
+ # Get all sensors at once
159
+ print("\nAll sensor values:")
160
+ sensors = instant_values.get_sensors()
161
+ for key, value in sensors.items():
162
+ if isinstance(value, tuple) and len(value) >= 2:
163
+ print(f" {key}: {value[0]} {value[1]}")
164
+
165
+ # Dictionary-style individual access
166
+ if "temperature" in instant_values:
167
+ temp = instant_values["temperature"]
168
+ print(f"Temperature: {temp[0]} {temp[1]}")
169
+
170
+ # Get with default
171
+ ph_value = instant_values.get("ph", "Not available")
172
+ print(f"pH: {ph_value}")
173
+
174
+ # --- Setting values ---
175
+
176
+ # Set number values
177
+ if "ph_target" in instant_values.get_numbers():
178
+ result = await instant_values.set_number("ph_target", 7.2)
179
+ print(f"Set pH target to 7.2: {result}")
180
+
181
+ # Set switch values
182
+ if "stop_pool_dosing" in instant_values.get_switches():
183
+ result = await instant_values.set_switch("stop_pool_dosing", True)
184
+ print(f"Set stop pool dosing: {result}")
185
+
186
+ if __name__ == "__main__":
187
+ asyncio.run(main())
188
+ ```
189
+
190
+ ### Advanced Usage
191
+
192
+ #### Type-specific Access
193
+ ```python
194
+ # Get all values by type
195
+ sensors = instant_values.get_sensors() # All sensor readings
196
+ binary_sensors = instant_values.get_binary_sensors() # All boolean states
197
+ numbers = instant_values.get_numbers() # All configurable numbers
198
+ switches = instant_values.get_switches() # All switch states
199
+ selects = instant_values.get_selects() # All select options
200
+
201
+ # Check available types dynamically
202
+ available_types = instant_values.available_types()
203
+ print("Available types:", list(available_types.keys()))
204
+ ```
205
+
206
+ #### Error Handling
207
+ ```python
208
+ from pooldose.request_handler import RequestStatus
209
+
210
+ status, client = await PooldoseClient.create("192.168.1.100")
211
+ if status == RequestStatus.SUCCESS:
212
+ print("Connected successfully")
213
+ elif status == RequestStatus.CONNECTION_ERROR:
214
+ print("Could not connect to device")
215
+ elif status == RequestStatus.API_VERSION_UNSUPPORTED:
216
+ print("Unsupported API version")
217
+ else:
218
+ print(f"Other error: {status}")
219
+ ```
220
+
221
+ ## API Reference
222
+
223
+ ### PooldoseClient
224
+
225
+ #### Methods
226
+ - `create(host, timeout=10, include_sensitive_data=False)` - Factory method to create and initialize client
227
+ - `static_values()` - Get static device information
228
+ - `instant_values()` - Get current sensor readings and device state
229
+ - `available_types()` - Get all available entity types
230
+ - `available_sensors()` - Get available sensor configurations
231
+ - `available_binary_sensors()` - Get available binary sensor configurations
232
+ - `available_numbers()` - Get available number configurations
233
+ - `available_switches()` - Get available switch configurations
234
+ - `available_selects()` - Get available select configurations
235
+
236
+ ### InstantValues
237
+
238
+ #### Dictionary Interface
239
+ ```python
240
+ # Reading
241
+ value = instant_values["sensor_name"]
242
+ value = instant_values.get("sensor_name", default)
243
+ exists = "sensor_name" in instant_values
244
+
245
+ # Writing (async)
246
+ await instant_values.__setitem__("switch_name", True)
247
+ ```
248
+
249
+ #### Type-specific Methods
250
+ ```python
251
+ # Getters
252
+ sensors = instant_values.get_sensors()
253
+ binary_sensors = instant_values.get_binary_sensors()
254
+ numbers = instant_values.get_numbers()
255
+ switches = instant_values.get_switches()
256
+ selects = instant_values.get_selects()
257
+
258
+ # Setters (async, with validation)
259
+ await instant_values.set_number("ph_target", 7.2)
260
+ await instant_values.set_switch("stop_dosing", True)
261
+ await instant_values.set_select("water_meter_unit", 1)
262
+ ```
263
+
264
+ ## Supported Devices
265
+
266
+ This client has been tested with:
267
+ - **PoolDose Double/Dual WiFi** (Model: PDPR1H1HAW100, FW: 539187)
268
+
269
+ 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_PDPR1H1HAW100_FW539187.json`).
270
+
271
+ > **Note:** The other JSON files in the `docs/` directory define the default English names for the data keys of the PoolDose devices. These mappings are used for display and documentation purposes.
272
+
273
+ ## Security
274
+
275
+ By default, the client excludes sensitive information like WiFi passwords from device info. To include sensitive data:
276
+
277
+ ```python
278
+ status, client = await PooldoseClient.create(
279
+ host="192.168.1.100",
280
+ include_sensitive_data=True
281
+ )
282
+ ```
283
+
284
+ ## Changelog
285
+
286
+ ### [0.3.1] - 2025-07-04
287
+ - First official release, published on PyPi
288
+ - Install with ```pip install python-pooldose```
289
+
290
+ ### [0.3.0] - 2025-07-02
291
+ - **BREAKING**: Changed from dataclass properties to dictionary-based access for instant values
292
+ - Added dynamic sensor discovery based on device mapping files
293
+ - Added type-specific getter methods (get_sensors, get_switches, etc.)
294
+ - Added type-specific setter methods with validation (set_number, set_switch, etc.)
295
+ - Added dictionary-style access (__getitem__, __setitem__, get, __contains__)
296
+ - Added configurable sensitive data handling (excludes WiFi passwords by default)
297
+ - Improved async file loading to prevent event loop blocking
298
+ - Enhanced error handling and logging
299
+ - Added comprehensive type annotations
300
+
301
+ ### [0.2.0] - 2024-06-25
302
+ - Added query feature to list all available sensors and actuators
303
+
304
+ ### [0.1.5] - 2024-06-24
305
+ - First working prototype for PoolDose Double/Dual WiFi supported
306
+ - All sensors and actuators for PoolDose Double/Dual WiFi supported
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "python-pooldose"
3
+ version = "0.3.1"
4
+ description = "Unoffical async Python client for SEKO PoolDose devices"
5
+ authors = [
6
+ { name = "Lukas Maertin", email = "pypi@lukas-maertin.de" }
7
+ ]
8
+ license = "MIT"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "aiohttp",
13
+ "aiofiles"
14
+ ]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/lmaertin/python-pooldose"
18
+ Repository = "https://github.com/lmaertin/python-pooldose"
19
+
20
+ [build-system]
21
+ requires = ["setuptools>=61.0", "wheel"]
22
+ build-backend = "setuptools.build_meta"
23
+
24
+ [project.optional-dependencies]
25
+ dev = ["pytest", "pytest-asyncio"]
26
+
27
+ [tool.setuptools]
28
+ package-dir = {"" = "src"}
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.setuptools.package-data]
34
+ "pooldose.mappings" = ["*.json"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+