python-pooldose 0.3.1__py3-none-any.whl
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.
- pooldose/__init__.py +4 -0
- pooldose/client.py +228 -0
- pooldose/mappings/__init__.py +1 -0
- pooldose/mappings/mapping_info.py +222 -0
- pooldose/mappings/model_PDPR1H1HAW100_FW539187.json +153 -0
- pooldose/request_handler.py +395 -0
- pooldose/values/__init__.py +1 -0
- pooldose/values/instant_values.py +270 -0
- pooldose/values/static_values.py +180 -0
- python_pooldose-0.3.1.dist-info/METADATA +324 -0
- python_pooldose-0.3.1.dist-info/RECORD +14 -0
- python_pooldose-0.3.1.dist-info/WHEEL +5 -0
- python_pooldose-0.3.1.dist-info/licenses/LICENSE +21 -0
- python_pooldose-0.3.1.dist-info/top_level.txt +1 -0
pooldose/__init__.py
ADDED
pooldose/client.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Client for async API client for SEKO Pooldose."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from pooldose.values.instant_values import InstantValues
|
|
9
|
+
from pooldose.request_handler import RequestHandler, RequestStatus
|
|
10
|
+
from pooldose.values.static_values import StaticValues
|
|
11
|
+
from pooldose.mappings.mapping_info import (
|
|
12
|
+
MappingInfo,
|
|
13
|
+
SensorMapping,
|
|
14
|
+
BinarySensorMapping,
|
|
15
|
+
NumberMapping,
|
|
16
|
+
SwitchMapping,
|
|
17
|
+
SelectMapping,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# pylint: disable=line-too-long
|
|
21
|
+
|
|
22
|
+
_LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
class PooldoseClient:
|
|
25
|
+
"""
|
|
26
|
+
Async client for SEKO Pooldose API.
|
|
27
|
+
All getter methods return (status, data) and log errors.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, host: str, timeout: int = 10) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Initialize the Pooldose client.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
host (str): The host address of the Pooldose device.
|
|
36
|
+
timeout (int): Timeout for API requests in seconds.
|
|
37
|
+
"""
|
|
38
|
+
self._host = host
|
|
39
|
+
self._timeout = timeout
|
|
40
|
+
self._last_data = None
|
|
41
|
+
self._request_handler = None
|
|
42
|
+
|
|
43
|
+
# Initialize device info with default or placeholder values
|
|
44
|
+
self.device_info = {
|
|
45
|
+
"NAME": None, # Device name
|
|
46
|
+
"SERIAL_NUMBER": None, # Serial number
|
|
47
|
+
"DEVICE_ID": "01220000095B_DEVICE", # Device ID, i.e., SERIAL_NUMBER + "_DEVICE"
|
|
48
|
+
"MODEL": None, # Device model
|
|
49
|
+
"MODEL_ID": "PDPR1H1HAW100", # Model ID
|
|
50
|
+
"OWNERID": None, # Owner ID
|
|
51
|
+
"GROUPNAME": None, # Group name
|
|
52
|
+
"FW_VERSION": None, # Firmware version
|
|
53
|
+
"SW_VERSION": None, # Software version
|
|
54
|
+
"API_VERSION": None, # API version
|
|
55
|
+
"FW_CODE": "539187", # Firmware code
|
|
56
|
+
"MAC": None, # MAC address
|
|
57
|
+
"IP": None, # IP address
|
|
58
|
+
"WIFI_SSID": None, # WiFi SSID
|
|
59
|
+
"WIFI_KEY": None, # WiFi key
|
|
60
|
+
"AP_SSID": None, # Access Point SSID
|
|
61
|
+
"AP_KEY": None, # Access Point key
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Mapping-Status und Mapping-Cache
|
|
65
|
+
self._mapping_status = None
|
|
66
|
+
self._mapping_info: Optional[MappingInfo] = None
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
async def create(cls, host: str, timeout: int = 10, include_sensitive_data: bool = False) -> tuple[RequestStatus, "PooldoseClient" | None]:
|
|
70
|
+
"""
|
|
71
|
+
Asynchronous factory method to initialize the Pooldose client.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
host (str): The host address of the Pooldose device.
|
|
75
|
+
timeout (int): Timeout for API requests in seconds.
|
|
76
|
+
include_sensitive_data (bool): If True, fetch WiFi and AP keys.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
tuple: (RequestStatus, PooldoseClient|None) - Status and client instance.
|
|
80
|
+
"""
|
|
81
|
+
self = cls(host, timeout)
|
|
82
|
+
status, handler = await RequestHandler.create(host, timeout)
|
|
83
|
+
if status != RequestStatus.SUCCESS:
|
|
84
|
+
_LOGGER.error("Failed to create RequestHandler: %s", status)
|
|
85
|
+
return status, None
|
|
86
|
+
self._request_handler = handler
|
|
87
|
+
|
|
88
|
+
# Fetch core parameters and device info
|
|
89
|
+
self.device_info["API_VERSION"] = self._request_handler.api_version
|
|
90
|
+
|
|
91
|
+
status, debug_config = await self._request_handler.get_debug_config()
|
|
92
|
+
if status != RequestStatus.SUCCESS or not debug_config:
|
|
93
|
+
_LOGGER.error("Failed to fetch debug config: %s", status)
|
|
94
|
+
return status, None
|
|
95
|
+
if (gateway := debug_config.get("GATEWAY")) is not None:
|
|
96
|
+
self.device_info["SERIAL_NUMBER"] = gateway.get("DID")
|
|
97
|
+
self.device_info["NAME"] = gateway.get("NAME")
|
|
98
|
+
self.device_info["SW_VERSION"] = gateway.get("FW_REL")
|
|
99
|
+
if (device := debug_config.get("DEVICES")[0]) is not None:
|
|
100
|
+
self.device_info["DEVICE_ID"] = device.get("DID")
|
|
101
|
+
self.device_info["MODEL"] = device.get("NAME")
|
|
102
|
+
self.device_info["MODEL_ID"] = device.get("PRODUCT_CODE")
|
|
103
|
+
self.device_info["FW_VERSION"] = device.get("FW_REL")
|
|
104
|
+
self.device_info["FW_CODE"] = device.get("FW_CODE")
|
|
105
|
+
await asyncio.sleep(0.5)
|
|
106
|
+
|
|
107
|
+
# Mapping laden, sobald MODEL_ID und FW_CODE verfügbar sind (asynchron!)
|
|
108
|
+
self._mapping_info = await MappingInfo.load(
|
|
109
|
+
self.device_info.get("MODEL_ID"),
|
|
110
|
+
self.device_info.get("FW_CODE")
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# WiFi station info
|
|
114
|
+
status, wifi_station = await self._request_handler.get_wifi_station()
|
|
115
|
+
if status != RequestStatus.SUCCESS or not wifi_station:
|
|
116
|
+
_LOGGER.warning("Failed to fetch WiFi station info: %s", status)
|
|
117
|
+
else:
|
|
118
|
+
self.device_info["WIFI_SSID"] = wifi_station.get("SSID")
|
|
119
|
+
self.device_info["MAC"] = wifi_station.get("MAC")
|
|
120
|
+
self.device_info["IP"] = wifi_station.get("IP")
|
|
121
|
+
# Only include WiFi key if explicitly requested
|
|
122
|
+
if include_sensitive_data:
|
|
123
|
+
self.device_info["WIFI_KEY"] = wifi_station.get("KEY")
|
|
124
|
+
await asyncio.sleep(0.5)
|
|
125
|
+
|
|
126
|
+
# Access point info
|
|
127
|
+
status, access_point = await self._request_handler.get_access_point()
|
|
128
|
+
if status != RequestStatus.SUCCESS or not access_point:
|
|
129
|
+
_LOGGER.warning("Failed to fetch access point info: %s", status)
|
|
130
|
+
else:
|
|
131
|
+
self.device_info["AP_SSID"] = access_point.get("SSID")
|
|
132
|
+
# Only include AP key if explicitly requested
|
|
133
|
+
if include_sensitive_data:
|
|
134
|
+
self.device_info["AP_KEY"] = access_point.get("KEY")
|
|
135
|
+
await asyncio.sleep(0.5)
|
|
136
|
+
|
|
137
|
+
# Network info
|
|
138
|
+
status, network_info = await self._request_handler.get_network_info()
|
|
139
|
+
if status != RequestStatus.SUCCESS or not network_info:
|
|
140
|
+
_LOGGER.error("Failed to fetch network info: %s", status)
|
|
141
|
+
return status, None
|
|
142
|
+
self.device_info["OWNERID"] = network_info.get("OWNERID")
|
|
143
|
+
self.device_info["GROUPNAME"] = network_info.get("GROUPNAME")
|
|
144
|
+
|
|
145
|
+
if not include_sensitive_data:
|
|
146
|
+
_LOGGER.info("Excluded WiFi and AP keys (use include_sensitive_data=True to include)")
|
|
147
|
+
|
|
148
|
+
_LOGGER.debug("Initialized Pooldose client with device info: %s", self.device_info)
|
|
149
|
+
return RequestStatus.SUCCESS, self
|
|
150
|
+
|
|
151
|
+
def static_values(self) -> tuple[RequestStatus, StaticValues | None]:
|
|
152
|
+
"""
|
|
153
|
+
Get the static device values as a StaticValues object.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
tuple: (RequestStatus, StaticValues|None) - Status and static values object.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return RequestStatus.SUCCESS, StaticValues(self.device_info)
|
|
160
|
+
except (ValueError, TypeError, KeyError) as err:
|
|
161
|
+
_LOGGER.warning("Error creating StaticValues: %s", err)
|
|
162
|
+
return RequestStatus.UNKNOWN_ERROR, None
|
|
163
|
+
|
|
164
|
+
def available_types(self) -> dict[str, list[str]]:
|
|
165
|
+
"""
|
|
166
|
+
Returns a dictionary mapping type categories to lists of available type names.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
dict[str, list[str]]: A dictionary where each key is a type category (as a string),
|
|
170
|
+
and each value is a list of available type names (as strings). If no mapping information
|
|
171
|
+
is available, returns an empty dictionary.
|
|
172
|
+
"""
|
|
173
|
+
return self._mapping_info.available_types() if self._mapping_info else {}
|
|
174
|
+
|
|
175
|
+
def available_sensors(self) -> dict[str, SensorMapping]:
|
|
176
|
+
"""
|
|
177
|
+
Returns all available sensors from the mapping as SensorMapping objects.
|
|
178
|
+
"""
|
|
179
|
+
return self._mapping_info.available_sensors() if self._mapping_info else {}
|
|
180
|
+
|
|
181
|
+
def available_binary_sensors(self) -> dict[str, BinarySensorMapping]:
|
|
182
|
+
"""
|
|
183
|
+
Returns all available binary sensors from the mapping as BinarySensorMapping objects.
|
|
184
|
+
"""
|
|
185
|
+
return self._mapping_info.available_binary_sensors() if self._mapping_info else {}
|
|
186
|
+
|
|
187
|
+
def available_numbers(self) -> dict[str, NumberMapping]:
|
|
188
|
+
"""
|
|
189
|
+
Returns all available numbers from the mapping as NumberMapping objects.
|
|
190
|
+
"""
|
|
191
|
+
return self._mapping_info.available_numbers() if self._mapping_info else {}
|
|
192
|
+
|
|
193
|
+
def available_switches(self) -> dict[str, SwitchMapping]:
|
|
194
|
+
"""
|
|
195
|
+
Returns all available switches from the mapping as SwitchMapping objects.
|
|
196
|
+
"""
|
|
197
|
+
return self._mapping_info.available_switches() if self._mapping_info else {}
|
|
198
|
+
|
|
199
|
+
def available_selects(self) -> dict[str, SelectMapping]:
|
|
200
|
+
"""
|
|
201
|
+
Returns all available selects from the mapping as SelectMapping objects.
|
|
202
|
+
"""
|
|
203
|
+
return self._mapping_info.available_selects() if self._mapping_info else {}
|
|
204
|
+
|
|
205
|
+
async def instant_values(self) -> tuple[RequestStatus, InstantValues | None]:
|
|
206
|
+
"""
|
|
207
|
+
Fetch the current instant values from the Pooldose device.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
tuple: (RequestStatus, InstantValues|None) - Status and instant values object.
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
status, raw_data = await self._request_handler.get_values_raw()
|
|
214
|
+
if status != RequestStatus.SUCCESS or raw_data is None:
|
|
215
|
+
return status, None
|
|
216
|
+
# Mapping aus Cache verwenden
|
|
217
|
+
mapping = self._mapping_info.mapping if self._mapping_info else None
|
|
218
|
+
if mapping is None:
|
|
219
|
+
return RequestStatus.UNKNOWN_ERROR, None
|
|
220
|
+
device_id = self.device_info["DEVICE_ID"]
|
|
221
|
+
device_raw_data = raw_data.get("devicedata", {}).get(device_id, {})
|
|
222
|
+
model_id = self.device_info["MODEL_ID"]
|
|
223
|
+
fw_code = self.device_info["FW_CODE"]
|
|
224
|
+
prefix = f"{model_id}_FW{fw_code}_"
|
|
225
|
+
return RequestStatus.SUCCESS, InstantValues(device_raw_data, mapping, prefix, device_id, self._request_handler)
|
|
226
|
+
except (KeyError, TypeError, ValueError) as err:
|
|
227
|
+
_LOGGER.warning("Error creating InstantValues: %s", err)
|
|
228
|
+
return RequestStatus.UNKNOWN_ERROR, None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mappings for async API client for SEKO Pooldose."""
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Mapping Parser for async API client for SEKO Pooldose."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
import importlib.resources
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import aiofiles
|
|
9
|
+
from pooldose.request_handler import RequestStatus
|
|
10
|
+
|
|
11
|
+
# pylint: disable=line-too-long
|
|
12
|
+
|
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SensorMapping:
|
|
17
|
+
"""
|
|
18
|
+
Represents a sensor mapping entry.
|
|
19
|
+
Attributes:
|
|
20
|
+
key (str): The key for the sensor.
|
|
21
|
+
type (str): The type, always "sensor".
|
|
22
|
+
conversion (Optional[dict]): Optional conversion mapping.
|
|
23
|
+
"""
|
|
24
|
+
key: str
|
|
25
|
+
type: str
|
|
26
|
+
conversion: Optional[dict] = None
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class BinarySensorMapping:
|
|
30
|
+
"""
|
|
31
|
+
Represents a binary sensor mapping entry.
|
|
32
|
+
Attributes:
|
|
33
|
+
key (str): The key for the binary sensor.
|
|
34
|
+
type (str): The type, always "binary_sensor".
|
|
35
|
+
"""
|
|
36
|
+
key: str
|
|
37
|
+
type: str
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class NumberMapping:
|
|
41
|
+
"""
|
|
42
|
+
Represents a number mapping entry.
|
|
43
|
+
Attributes:
|
|
44
|
+
key (str): The key for the number.
|
|
45
|
+
type (str): The type, always "number".
|
|
46
|
+
"""
|
|
47
|
+
key: str
|
|
48
|
+
type: str
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class SwitchMapping:
|
|
52
|
+
"""
|
|
53
|
+
Represents a switch mapping entry.
|
|
54
|
+
Attributes:
|
|
55
|
+
key (str): The key for the switch.
|
|
56
|
+
type (str): The type, always "switch".
|
|
57
|
+
"""
|
|
58
|
+
key: str
|
|
59
|
+
type: str
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class SelectMapping:
|
|
63
|
+
"""
|
|
64
|
+
Represents a select mapping entry.
|
|
65
|
+
Attributes:
|
|
66
|
+
key (str): The key for the select.
|
|
67
|
+
type (str): The type, always "select".
|
|
68
|
+
conversion (dict): Mandatory conversion mapping.
|
|
69
|
+
options (dict): Mandatory options mapping.
|
|
70
|
+
"""
|
|
71
|
+
key: str
|
|
72
|
+
type: str
|
|
73
|
+
conversion: dict
|
|
74
|
+
options: dict
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class MappingInfo:
|
|
78
|
+
"""
|
|
79
|
+
Provides utilities to load and query mapping configurations for different models and firmware codes.
|
|
80
|
+
|
|
81
|
+
Attributes:
|
|
82
|
+
mapping (Optional[Dict[str, Any]]): The loaded mapping configuration, or None if not loaded.
|
|
83
|
+
status (Optional[RequestStatus]): The status of the mapping load operation.
|
|
84
|
+
"""
|
|
85
|
+
mapping: Optional[Dict[str, Any]] = None
|
|
86
|
+
status: Optional[RequestStatus] = None
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
async def load(cls, model_id: str, fw_code: str) -> "MappingInfo":
|
|
90
|
+
"""
|
|
91
|
+
Asynchronously load the model-specific mapping configuration from a JSON file.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
model_id (str): The model ID.
|
|
95
|
+
fw_code (str): The firmware code.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
MappingInfo: The loaded mapping info object.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
if not model_id or not fw_code:
|
|
102
|
+
_LOGGER.error("MODEL_ID or FW_CODE not set!")
|
|
103
|
+
return cls(mapping=None, status=RequestStatus.NO_DATA)
|
|
104
|
+
filename = f"model_{model_id}_FW{fw_code}.json"
|
|
105
|
+
path = importlib.resources.files("pooldose.mappings").joinpath(filename)
|
|
106
|
+
async with aiofiles.open(path, "r", encoding="utf-8") as f:
|
|
107
|
+
content = await f.read()
|
|
108
|
+
mapping = json.loads(content)
|
|
109
|
+
return cls(mapping=mapping, status=RequestStatus.SUCCESS)
|
|
110
|
+
except (OSError, json.JSONDecodeError, ModuleNotFoundError, FileNotFoundError) as err:
|
|
111
|
+
_LOGGER.warning("Error loading model mapping: %s", err)
|
|
112
|
+
return cls(mapping=None, status=RequestStatus.UNKNOWN_ERROR)
|
|
113
|
+
|
|
114
|
+
def available_types(self) -> dict[str, list[str]]:
|
|
115
|
+
"""
|
|
116
|
+
Returns all available types and their keys for the current model/firmware.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
dict[str, list[str]]: Mapping from type to list of keys.
|
|
120
|
+
"""
|
|
121
|
+
if not self.mapping:
|
|
122
|
+
return {}
|
|
123
|
+
result = {}
|
|
124
|
+
for key, entry in self.mapping.items():
|
|
125
|
+
typ = entry.get("type", "unknown")
|
|
126
|
+
result.setdefault(typ, []).append(key)
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
def available_sensors(self) -> Dict[str, SensorMapping]:
|
|
130
|
+
"""
|
|
131
|
+
Returns all available sensors from the mapping as SensorMapping objects.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Dict[str, SensorMapping]: Mapping from name to SensorMapping.
|
|
135
|
+
"""
|
|
136
|
+
if not self.mapping:
|
|
137
|
+
return {}
|
|
138
|
+
result = {}
|
|
139
|
+
for name, entry in self.mapping.items():
|
|
140
|
+
if entry.get("type") == "sensor":
|
|
141
|
+
result[name] = SensorMapping(
|
|
142
|
+
key=entry["key"],
|
|
143
|
+
type=entry["type"],
|
|
144
|
+
conversion=entry.get("conversion"),
|
|
145
|
+
)
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
def available_binary_sensors(self) -> Dict[str, BinarySensorMapping]:
|
|
149
|
+
"""
|
|
150
|
+
Returns all available binary sensors from the mapping as BinarySensorMapping objects.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dict[str, BinarySensorMapping]: Mapping from name to BinarySensorMapping.
|
|
154
|
+
"""
|
|
155
|
+
if not self.mapping:
|
|
156
|
+
return {}
|
|
157
|
+
result = {}
|
|
158
|
+
for name, entry in self.mapping.items():
|
|
159
|
+
if entry.get("type") == "binary_sensor":
|
|
160
|
+
result[name] = BinarySensorMapping(
|
|
161
|
+
key=entry["key"],
|
|
162
|
+
type=entry["type"],
|
|
163
|
+
)
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
def available_numbers(self) -> Dict[str, NumberMapping]:
|
|
167
|
+
"""
|
|
168
|
+
Returns all available numbers from the mapping as NumberMapping objects.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Dict[str, NumberMapping]: Mapping from name to NumberMapping.
|
|
172
|
+
"""
|
|
173
|
+
if not self.mapping:
|
|
174
|
+
return {}
|
|
175
|
+
result = {}
|
|
176
|
+
for name, entry in self.mapping.items():
|
|
177
|
+
if entry.get("type") == "number":
|
|
178
|
+
result[name] = NumberMapping(
|
|
179
|
+
key=entry["key"],
|
|
180
|
+
type=entry["type"],
|
|
181
|
+
)
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
def available_switches(self) -> Dict[str, SwitchMapping]:
|
|
185
|
+
"""
|
|
186
|
+
Returns all available switches from the mapping as SwitchMapping objects.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Dict[str, SwitchMapping]: Mapping from name to SwitchMapping.
|
|
190
|
+
"""
|
|
191
|
+
if not self.mapping:
|
|
192
|
+
return {}
|
|
193
|
+
result = {}
|
|
194
|
+
for name, entry in self.mapping.items():
|
|
195
|
+
if entry.get("type") == "switch":
|
|
196
|
+
result[name] = SwitchMapping(
|
|
197
|
+
key=entry["key"],
|
|
198
|
+
type=entry["type"],
|
|
199
|
+
)
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
def available_selects(self) -> Dict[str, SelectMapping]:
|
|
203
|
+
"""
|
|
204
|
+
Returns all available selects from the mapping as SelectMapping objects.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dict[str, SelectMapping]: Mapping from name to SelectMapping.
|
|
208
|
+
Raises:
|
|
209
|
+
KeyError: If a select entry does not contain 'conversion' or 'options'.
|
|
210
|
+
"""
|
|
211
|
+
if not self.mapping:
|
|
212
|
+
return {}
|
|
213
|
+
result = {}
|
|
214
|
+
for name, entry in self.mapping.items():
|
|
215
|
+
if entry.get("type") == "select":
|
|
216
|
+
result[name] = SelectMapping(
|
|
217
|
+
key=entry["key"],
|
|
218
|
+
type=entry["type"],
|
|
219
|
+
conversion=entry["conversion"],
|
|
220
|
+
options=entry["options"],
|
|
221
|
+
)
|
|
222
|
+
return result
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"temperature": {
|
|
3
|
+
"key": "w_1eommf39k",
|
|
4
|
+
"type": "sensor"
|
|
5
|
+
},
|
|
6
|
+
"ph": {
|
|
7
|
+
"key": "w_1ekeigkin",
|
|
8
|
+
"type": "sensor"
|
|
9
|
+
},
|
|
10
|
+
"orp": {
|
|
11
|
+
"key": "w_1eklenb23",
|
|
12
|
+
"type": "sensor"
|
|
13
|
+
},
|
|
14
|
+
"ph_type_dosing": {
|
|
15
|
+
"key": "w_1eklg44ro",
|
|
16
|
+
"type": "sensor",
|
|
17
|
+
"conversion": {
|
|
18
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklg44ro_ALCALYNE|": "alcalyne",
|
|
19
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklg44ro_ACID|" : "acid"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"peristaltic_ph_dosing": {
|
|
23
|
+
"key": "w_1eklj6euj",
|
|
24
|
+
"type": "sensor",
|
|
25
|
+
"conversion": {
|
|
26
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklj6euj_OFF|": "off",
|
|
27
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklj6euj_PROPORTIONAL|": "proportional",
|
|
28
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklj6euj_ON_OFF|": "on_off",
|
|
29
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklj6euj_TIMED|": "timed"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"ofa_ph_value": {
|
|
33
|
+
"key": "w_1eo1ttmft",
|
|
34
|
+
"type": "sensor"
|
|
35
|
+
},
|
|
36
|
+
"orp_type_dosing": {
|
|
37
|
+
"key": "w_1eklgnolb",
|
|
38
|
+
"type": "sensor",
|
|
39
|
+
"conversion": {
|
|
40
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklgnolb_LOW|": "low",
|
|
41
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklgnolb_HIGH|": "high"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"peristaltic_orp_dosing": {
|
|
45
|
+
"key": "w_1eo1s18s8",
|
|
46
|
+
"type": "sensor",
|
|
47
|
+
"conversion": {
|
|
48
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eo1s18s8_OFF|": "off",
|
|
49
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eo1s18s8_PROPORTIONAL|": "proportional",
|
|
50
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eo1s18s8_ON_OFF|": "on_off",
|
|
51
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eo1s18s8_TIMED|": "timed"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"ofa_orp_value": {
|
|
55
|
+
"key": "w_1eo1tui1d",
|
|
56
|
+
"type": "sensor"
|
|
57
|
+
},
|
|
58
|
+
"ph_calibration_type": {
|
|
59
|
+
"key": "w_1eklh8gb7",
|
|
60
|
+
"type": "sensor",
|
|
61
|
+
"conversion": {
|
|
62
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8gb7_OFF|": "off",
|
|
63
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8gb7_REFERENCE|": "reference",
|
|
64
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8gb7_1_POINT|": "1_point",
|
|
65
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8gb7_2_POINTS|": "2_points"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"ph_calibration_offset": {
|
|
69
|
+
"key": "w_1eklhs3b4",
|
|
70
|
+
"type": "sensor"
|
|
71
|
+
},
|
|
72
|
+
"ph_calibration_slope": {
|
|
73
|
+
"key": "w_1eklhs65u",
|
|
74
|
+
"type": "sensor"
|
|
75
|
+
},
|
|
76
|
+
"orp_calibration_type": {
|
|
77
|
+
"key": "w_1eklh8i5t",
|
|
78
|
+
"type": "sensor",
|
|
79
|
+
"conversion": {
|
|
80
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8i5t_OFF|": "off",
|
|
81
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8i5t_REFERENCE|": "reference",
|
|
82
|
+
"|PDPR1H1HAW100_FW539187_LABEL_w_1eklh8i5t_1_POINT|": "1_point"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"orp_calibration_offset": {
|
|
86
|
+
"key": "w_1eklhs8r3",
|
|
87
|
+
"type": "sensor"
|
|
88
|
+
},
|
|
89
|
+
"orp_calibration_slope": {
|
|
90
|
+
"key": "w_1eklhsase",
|
|
91
|
+
"type": "sensor"
|
|
92
|
+
},
|
|
93
|
+
"pump_running": {
|
|
94
|
+
"key": "w_1ekga097n",
|
|
95
|
+
"type": "binary_sensor"
|
|
96
|
+
},
|
|
97
|
+
"ph_level_ok": {
|
|
98
|
+
"key": "w_1eklf77pm",
|
|
99
|
+
"type": "binary_sensor"
|
|
100
|
+
},
|
|
101
|
+
"orp_level_ok": {
|
|
102
|
+
"key": "w_1eo04bcr2",
|
|
103
|
+
"type": "binary_sensor"
|
|
104
|
+
},
|
|
105
|
+
"flow_rate_ok": {
|
|
106
|
+
"key": "w_1eo04nc5n",
|
|
107
|
+
"type": "binary_sensor"
|
|
108
|
+
},
|
|
109
|
+
"alarm_relay": {
|
|
110
|
+
"key": "w_1eklffdl0",
|
|
111
|
+
"type": "binary_sensor"
|
|
112
|
+
},
|
|
113
|
+
"relay_aux1_ph": {
|
|
114
|
+
"key": "w_1eoi2rv4h",
|
|
115
|
+
"type": "binary_sensor"
|
|
116
|
+
},
|
|
117
|
+
"relay_aux2_orpcl": {
|
|
118
|
+
"key": "w_1eoi2s16b",
|
|
119
|
+
"type": "binary_sensor"
|
|
120
|
+
},
|
|
121
|
+
"ph_target": {
|
|
122
|
+
"key": "w_1ekeiqfat",
|
|
123
|
+
"type": "number"
|
|
124
|
+
},
|
|
125
|
+
"orp_target": {
|
|
126
|
+
"key": "w_1eklgnjk2",
|
|
127
|
+
"type": "number"
|
|
128
|
+
},
|
|
129
|
+
"stop_pool_dosing": {
|
|
130
|
+
"key": "w_1emtltkel",
|
|
131
|
+
"type": "switch"
|
|
132
|
+
},
|
|
133
|
+
"pump_detection": {
|
|
134
|
+
"key": "w_1eklft47q",
|
|
135
|
+
"type": "switch"
|
|
136
|
+
},
|
|
137
|
+
"frequency_input": {
|
|
138
|
+
"key": "w_1eklft5qt",
|
|
139
|
+
"type": "switch"
|
|
140
|
+
},
|
|
141
|
+
"water_meter_unit": {
|
|
142
|
+
"key": "w_1eklinki6",
|
|
143
|
+
"type": "select",
|
|
144
|
+
"options": {
|
|
145
|
+
"0": "PDPR1H1HAW100_FW539187_COMBO_w_1eklinki6_M_",
|
|
146
|
+
"1": "PDPR1H1HAW100_FW539187_COMBO_w_1eklinki6_LITER"
|
|
147
|
+
},
|
|
148
|
+
"conversion": {
|
|
149
|
+
"PDPR1H1HAW100_FW539187_COMBO_w_1eklinki6_M_": "m³",
|
|
150
|
+
"PDPR1H1HAW100_FW539187_COMBO_w_1eklinki6_LITER": "L"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|