pymiele 0.2.0__py3-none-any.whl → 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.
pymiele/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Library for Miele integration with Home Assistant."""
2
2
 
3
- from .const import * # noqa: F401, F403
3
+ from .const import * # noqa: F403
4
4
  from .const import VERSION as __version__ # noqa: F401
5
- from .pymiele import * # noqa: F401, F403
5
+ from .model import * # noqa: F403
6
+ from .pymiele import * # noqa: F403
pymiele/const.py CHANGED
@@ -1,4 +1,6 @@
1
- VERSION = "0.2.0"
1
+ """Constants for pymiele."""
2
+
3
+ VERSION = "0.3.1"
2
4
 
3
5
  MIELE_API = "https://api.mcs3.miele.com/v1"
4
6
  OAUTH2_AUTHORIZE = "https://api.mcs3.miele.com/thirdparty/login"
pymiele/model.py ADDED
@@ -0,0 +1,295 @@
1
+ """Data models for Miele API."""
2
+ # Todo: Move to pymiele when complete and stable
3
+
4
+ from __future__ import annotations
5
+
6
+
7
+ class MieleDevices:
8
+ """Data for all devices from API."""
9
+
10
+ def __init__(self, raw_data: dict) -> None:
11
+ """Initialize MieleDevices."""
12
+ self.raw_data = raw_data
13
+
14
+ @property
15
+ def devices(self) -> list[str]:
16
+ """Return list of all devices."""
17
+
18
+ return list(self.raw_data.keys())
19
+
20
+
21
+ class MieleDevice:
22
+ """Data for a single device from API."""
23
+
24
+ def __init__(self, raw_data: dict) -> None:
25
+ """Initialize MieleDevice."""
26
+ self.raw_data = raw_data
27
+
28
+ @property
29
+ def raw(self) -> dict:
30
+ """Return raw data."""
31
+ return self.raw_data
32
+
33
+ @property
34
+ def fab_number(self) -> str:
35
+ """Return the ID of the device."""
36
+ return str(self.raw_data["ident"]["deviceIdentLabel"]["fabNumber"])
37
+
38
+ @property
39
+ def device_type(self) -> int:
40
+ """Return the type of the device."""
41
+ return self.raw_data["ident"]["type"]["value_raw"]
42
+
43
+ @property
44
+ def device_type_localized(self) -> str:
45
+ """Return the type of the device."""
46
+ return self.raw_data["ident"]["type"]["value_localized"]
47
+
48
+ @property
49
+ def device_name(self) -> str:
50
+ """Return the name of the device."""
51
+ return self.raw_data["ident"]["deviceName"]
52
+
53
+ @property
54
+ def tech_type(self) -> str:
55
+ """Return the tech type of the device."""
56
+ return self.raw_data["ident"]["deviceIdentLabel"]["techType"]
57
+
58
+ @property
59
+ def xkm_tech_type(self) -> str:
60
+ """Return the xkm tech type of the device."""
61
+ return self.raw_data["ident"]["xkmIdentLabel"]["techType"]
62
+
63
+ @property
64
+ def xkm_release_version(self) -> str:
65
+ """Return the xkm release version of the device."""
66
+ return self.raw_data["ident"]["xkmIdentLabel"]["releaseVersion"]
67
+
68
+ @property
69
+ def state_program_id(self) -> int:
70
+ """Return the program ID of the device."""
71
+ return self.raw_data["state"]["ProgramID"]["value_raw"]
72
+
73
+ @property
74
+ def state_status(self) -> int:
75
+ """Return the status of the device."""
76
+ return self.raw_data["state"]["status"]["value_raw"]
77
+
78
+ @property
79
+ def state_program_type(self) -> int:
80
+ """Return the program type of the device."""
81
+ return self.raw_data["state"]["programType"]["value_raw"]
82
+
83
+ @property
84
+ def state_program_phase(self) -> int:
85
+ """Return the program phase of the device."""
86
+ return self.raw_data["state"]["programPhase"]["value_raw"]
87
+
88
+ @property
89
+ def state_remaining_time(self) -> list[int]:
90
+ """Return the remaining time of the device."""
91
+ return self.raw_data["state"]["remainingTime"]
92
+
93
+ @property
94
+ def state_start_time(self) -> list[int]:
95
+ """Return the start time of the device."""
96
+ return self.raw_data["state"]["startTime"]
97
+
98
+ @property
99
+ def state_target_temperature(self) -> list[dict]:
100
+ """Return the target temperature of the device."""
101
+ return self.raw_data["state"]["targetTemperature"]
102
+
103
+ @property
104
+ def state_core_target_temperature(self) -> list[dict]:
105
+ """Return the core target temperature of the device."""
106
+ return self.raw_data["state"]["coreTargetTemperature"]
107
+
108
+ @property
109
+ def state_temperature(self) -> list[int]:
110
+ """Return the temperature of the device."""
111
+ return [temp["value_raw"] for temp in self.raw_data["state"]["temperature"]]
112
+
113
+ @property
114
+ def state_temperature_1(self) -> int:
115
+ """Return the temperature in zone 1 of the device."""
116
+ return self.raw_data["state"]["temperature"][0]["value_raw"]
117
+
118
+ @property
119
+ def state_temperature_2(self) -> int:
120
+ """Return the temperature in zone 2 of the device."""
121
+ return self.raw_data["state"]["temperature"][1]["value_raw"]
122
+
123
+ @property
124
+ def state_temperature_3(self) -> int:
125
+ """Return the temperature in zone 3 of the device."""
126
+ return self.raw_data["state"]["temperature"][2]["value_raw"]
127
+
128
+ @property
129
+ def state_core_temperature(self) -> list[dict]:
130
+ """Return the core temperature of the device."""
131
+ return self.raw_data["state"]["coreTemperature"]
132
+
133
+ @property
134
+ def state_core_temperature_1(self) -> list[dict]:
135
+ """Return the core temperature in zone 1 of the device."""
136
+ return self.raw_data["state"]["coreTemperature"][0]["value_raw"]
137
+
138
+ @property
139
+ def state_core_temperature_2(self) -> list[dict]:
140
+ """Return the core temperature in zone 2 of the device."""
141
+ return self.raw_data["state"]["coreTemperature"][1]["value_raw"]
142
+
143
+ @property
144
+ def state_signal_info(self) -> bool:
145
+ """Return the signal info of the device."""
146
+ return self.raw_data["state"]["signalInfo"]
147
+
148
+ @property
149
+ def state_signal_failure(self) -> bool:
150
+ """Return the signal failure of the device."""
151
+ return self.raw_data["state"]["signalFailure"]
152
+
153
+ @property
154
+ def state_signal_door(self) -> bool:
155
+ """Return the signal door of the device."""
156
+ return self.raw_data["state"]["signalDoor"]
157
+
158
+ @property
159
+ def state_full_remote_control(self) -> bool:
160
+ """Return the remote control enable of the device."""
161
+ return self.raw_data["state"]["remoteEnable"]["fullRemoteControl"]
162
+
163
+ @property
164
+ def state_smart_grid(self) -> bool:
165
+ """Return the smart grid of the device."""
166
+ return self.raw_data["state"]["remoteEnable"]["smartGrid"]
167
+
168
+ @property
169
+ def state_mobile_start(self) -> bool:
170
+ """Return the mobile start of the device."""
171
+ return self.raw_data["state"]["remoteEnable"]["mobileStart"]
172
+
173
+ @property
174
+ def state_ambient_light(self) -> bool:
175
+ """Return the ambient light of the device."""
176
+ return self.raw_data["state"]["ambientLight"]
177
+
178
+ @property
179
+ def state_light(self) -> bool:
180
+ """Return the light of the device."""
181
+ return self.raw_data["state"]["light"]
182
+
183
+ @property
184
+ def state_elapsed_time(self) -> list[int]:
185
+ """Return the elapsed time of the device."""
186
+ return self.raw_data["state"]["elapsedTime"]
187
+
188
+ @property
189
+ def state_spinning_speed(self) -> int | None:
190
+ """Return the spinning speed of the device."""
191
+ return self.raw_data["state"]["spinningSpeed"]
192
+
193
+ @property
194
+ def state_drying_step(self) -> int | None:
195
+ """Return the drying step of the device."""
196
+ return self.raw_data["state"]["dryingStep"]
197
+
198
+ @property
199
+ def state_ventilation_step(self) -> int | None:
200
+ """Return the ventilation step of the device."""
201
+ return self.raw_data["state"]["ventilationStep"]
202
+
203
+ @property
204
+ def state_plate_step(self) -> list[dict]:
205
+ """Return the plate step of the device."""
206
+ return self.raw_data["state"]["plateStep"]
207
+
208
+ @property
209
+ def state_eco_feedback(self) -> dict | None:
210
+ """Return the eco feedback of the device."""
211
+ return self.raw_data["state"]["ecoFeedback"]
212
+
213
+ @property
214
+ def state_battery_level(self) -> int | None:
215
+ """Return the battery level of the device."""
216
+ return self.raw_data["state"]["batteryLevel"]
217
+
218
+
219
+ class MieleAction:
220
+ """Actions for Miele devices."""
221
+
222
+ def __init__(self, raw_data: dict) -> None:
223
+ """Initialize MieleAction."""
224
+ self.raw_data = raw_data
225
+
226
+ # Todo : Add process actions
227
+ @property
228
+ def raw(self) -> dict:
229
+ """Return raw data."""
230
+ return self.raw_data
231
+
232
+ @property
233
+ def actions(self) -> list[str]:
234
+ """Return list of all actions."""
235
+ return list(self.raw_data.keys())
236
+
237
+ @property
238
+ def modes(self) -> list[int]:
239
+ """Return list of modes."""
240
+ return list(self.raw_data["modes"])
241
+
242
+ @property
243
+ def process_actions(self) -> list[int]:
244
+ """Return list of process actions."""
245
+ return list(self.raw_data["processAction"])
246
+
247
+ @property
248
+ def light(self) -> list[int]:
249
+ """Return list of light actions."""
250
+ return list(self.raw_data["light"])
251
+
252
+ @property
253
+ def ambient_light(self) -> list[int]:
254
+ """Return list of ambient light actions."""
255
+ return list(self.raw_data["ambientLight"])
256
+
257
+ @property
258
+ def start_time(self) -> list[int]:
259
+ """Return list of start time actions."""
260
+ return list(self.raw_data["start_time"])
261
+
262
+ @property
263
+ def ventilation_setp(self) -> list[int]:
264
+ """Return list of ventilation step actions."""
265
+ return list(self.raw_data["ventilationStep"])
266
+
267
+ @property
268
+ def program_id(self) -> list[int]:
269
+ """Return list of program id actions."""
270
+ return list(self.raw_data["programId"])
271
+
272
+ @property
273
+ def runOnTime(self) -> list[int]:
274
+ """Return list of run on time actions."""
275
+ return list(self.raw_data["runOnTime"])
276
+
277
+ @property
278
+ def target_temperature(self) -> list[dict]:
279
+ """Return list of target temperature actions."""
280
+ return list(self.raw_data["targetTemperature"])
281
+
282
+ @property
283
+ def power_on_enabled(self) -> bool:
284
+ """Return powerOn enabled."""
285
+ return self.raw_data["powerOn"]
286
+
287
+ @property
288
+ def power_off_enabled(self) -> bool:
289
+ """Return powerOff enabled."""
290
+ return self.raw_data["powerOff"]
291
+
292
+ @property
293
+ def device_name_enabled(self) -> bool:
294
+ """Return deviceName enabled."""
295
+ return self.raw_data["deviceName"]
pymiele/py.typed ADDED
File without changes
pymiele/pymiele.py CHANGED
@@ -1,17 +1,16 @@
1
1
  """Library for Miele API."""
2
2
 
3
- # TODO
4
- # Should be moved to pypi.org when reasonably stable
5
3
  from __future__ import annotations
6
4
 
5
+ from abc import ABC, abstractmethod
7
6
  import asyncio
7
+ from collections.abc import Callable, Coroutine
8
8
  import json
9
- import logging
10
- from abc import ABC, abstractmethod
11
9
  from json.decoder import JSONDecodeError
12
- from typing import Any, Callable, Coroutine
10
+ import logging
11
+ from typing import Any
13
12
 
14
- from aiohttp import ClientError, ClientResponse, ClientSession, ClientTimeout
13
+ from aiohttp import ClientResponse, ClientResponseError, ClientSession, ClientTimeout
15
14
 
16
15
  from .const import MIELE_API, VERSION
17
16
 
@@ -24,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
24
23
  class AbstractAuth(ABC):
25
24
  """Abstract class to make authenticated requests."""
26
25
 
27
- def __init__(self, websession: ClientSession, host: str):
26
+ def __init__(self, websession: ClientSession, host: str) -> None:
28
27
  """Initialize the auth."""
29
28
  self.websession = websession
30
29
  self.host = host
@@ -33,19 +32,12 @@ class AbstractAuth(ABC):
33
32
  async def async_get_access_token(self) -> str:
34
33
  """Return a valid access token."""
35
34
 
36
- async def request(self, method, url, **kwargs) -> ClientResponse:
35
+ async def request(self, method: str, url: str, **kwargs: Any) -> ClientResponse:
37
36
  """Make a request."""
38
- headers = kwargs.get("headers")
39
-
40
- if headers is None:
41
- headers = {}
42
- else:
37
+ if headers := kwargs.pop("headers", {}):
43
38
  headers = dict(headers)
44
- kwargs.pop("headers")
45
39
 
46
- agent_suffix = kwargs.get("agent_suffix")
47
- if "agent_suffix" in kwargs:
48
- kwargs.pop("agent_suffix")
40
+ agent_suffix = kwargs.pop("agent_suffix", None)
49
41
  user_agent = (
50
42
  USER_AGENT_BASE
51
43
  if agent_suffix is None
@@ -65,9 +57,51 @@ class AbstractAuth(ABC):
65
57
  headers=headers,
66
58
  )
67
59
 
60
+ async def get_devices(self) -> dict:
61
+ """Get all devices."""
62
+ async with asyncio.timeout(10):
63
+ res = await self.request(
64
+ "GET", "/devices", headers={"Accept": "application/json"}
65
+ )
66
+ res.raise_for_status()
67
+ return await res.json()
68
+
69
+ async def get_actions(self, serial: str) -> dict:
70
+ """Get actions for a device."""
71
+ async with asyncio.timeout(10):
72
+ res = await self.request(
73
+ "GET",
74
+ f"/devices/{serial}/actions",
75
+ headers={"Accept": "application/json"},
76
+ )
77
+ res.raise_for_status()
78
+ return await res.json()
79
+
80
+ async def get_programs(self, serial: str) -> dict:
81
+ """Get programs for a device."""
82
+ async with asyncio.timeout(10):
83
+ res = await self.request(
84
+ "GET",
85
+ f"/devices/{serial}/programs",
86
+ headers={"Accept": "application/json"},
87
+ )
88
+ res.raise_for_status()
89
+ return await res.json()
90
+
91
+ async def get_rooms(self, serial: str) -> dict:
92
+ """Get rooms for a device."""
93
+ async with asyncio.timeout(10):
94
+ res = await self.request(
95
+ "GET",
96
+ f"/devices/{serial}/rooms",
97
+ headers={"Accept": "application/json"},
98
+ )
99
+ res.raise_for_status()
100
+ return await res.json()
101
+
68
102
  async def set_target_temperature(
69
103
  self, serial: str, temperature: float, zone: int = 1
70
- ):
104
+ ) -> ClientResponse:
71
105
  """Set target temperature."""
72
106
  temp = round(temperature)
73
107
  async with asyncio.timeout(10):
@@ -85,7 +119,9 @@ class AbstractAuth(ABC):
85
119
  _LOGGER.debug("set_target res: %s", res.status)
86
120
  return res
87
121
 
88
- async def send_action(self, serial: str, data):
122
+ async def send_action(
123
+ self, serial: str, data: dict[str, str | int | bool]
124
+ ) -> ClientResponse:
89
125
  """Send action command."""
90
126
 
91
127
  _LOGGER.debug("send_action serial: %s, data: %s", serial, data)
@@ -103,7 +139,9 @@ class AbstractAuth(ABC):
103
139
  _LOGGER.debug("send_action res: %s", res.status)
104
140
  return res
105
141
 
106
- async def set_program(self, serial: str, data):
142
+ async def set_program(
143
+ self, serial: str, data: dict[str, int | list[int]]
144
+ ) -> ClientResponse:
107
145
  """Send start program command."""
108
146
 
109
147
  _LOGGER.debug("set_program serial: %s, data: %s", serial, data)
@@ -161,18 +199,18 @@ class AbstractAuth(ABC):
161
199
  if event_type == "event: devices":
162
200
  data = json.loads(data_line[6:])
163
201
  if data_callback is not None:
164
- asyncio.create_task(data_callback(data))
202
+ asyncio.create_task(data_callback(data)) # noqa: RUF006
165
203
  elif event_type == "event: actions":
166
204
  data = json.loads(data_line[6:])
167
205
  if actions_callback is not None:
168
- asyncio.create_task(actions_callback(data))
206
+ asyncio.create_task(actions_callback(data)) # noqa: RUF006
169
207
  elif event_type == "event: ping":
170
208
  # _LOGGER.debug("Ping SSE")
171
209
  pass
172
210
  else:
173
211
  _LOGGER.error("Unknown event type: %s", event_type)
174
212
 
175
- except ClientError as ex:
213
+ except ClientResponseError as ex:
176
214
  _LOGGER.error("SSE: %s - %s", ex.status, ex.message)
177
215
  await asyncio.sleep(5)
178
216
  except JSONDecodeError as ex:
@@ -180,8 +218,8 @@ class AbstractAuth(ABC):
180
218
  "JSON decode error: %s, Pos: %s, Doc: %s", ex.msg, ex.pos, ex.doc
181
219
  )
182
220
  await asyncio.sleep(5)
183
- except Exception as ex:
184
- _LOGGER.error("Listen_event: %s - %s", ex.status, ex.message)
221
+ except Exception as ex: # pylint: disable=broad-except
222
+ _LOGGER.error("Listen_event: %s", ex)
185
223
  await asyncio.sleep(5)
186
224
 
187
225
 
@@ -1,18 +1,18 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pymiele
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Python library for Miele integration with Home Assistant
5
- Home-page: https://github.com/astrandb/pymiele
6
- Author: Ake Strandberg
7
- Author-email: ake@strandberg.eu
5
+ Author-email: Ake Strandberg <ake@strandberg.eu>
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/astrandb/pymiele
8
8
  Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
9
  Classifier: Operating System :: OS Independent
11
- Classifier: Development Status :: 5 - Production/Stable
12
- Requires-Python: >=3.12
10
+ Classifier: Development Status :: 4 - Beta
11
+ Requires-Python: >=3.12.0
13
12
  Description-Content-Type: text/markdown
14
13
  License-File: LICENSE
15
14
  Requires-Dist: aiohttp
15
+ Dynamic: license-file
16
16
 
17
17
  ## pymiele
18
18
 
@@ -0,0 +1,10 @@
1
+ pymiele/__init__.py,sha256=w_JvyaBHVGM7eo-FFwIWFbQUeAowoA_fbnAfCWJFGek,221
2
+ pymiele/const.py,sha256=GNfH7v747oYiA1E-eQZeLVHJr70PPM5wkJqUhB6lGbw,219
3
+ pymiele/model.py,sha256=PTMJ-9F_r3Hb4Zs96TFESSZ-r39cLCNIcSmFV5DrDjw,9442
4
+ pymiele/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ pymiele/pymiele.py,sha256=3edghDZLPDg4KBV7AVTUGNeeyDPAAegyzRfBG9pFgeU,8647
6
+ pymiele-0.3.1.dist-info/licenses/LICENSE,sha256=scGm4_U2pd-rsGa6Edf6zsXFebrMT4RoyQz7-904_Wg,1072
7
+ pymiele-0.3.1.dist-info/METADATA,sha256=8gerA20pt0tKdijbYGzMsJm20pVbUxgW1iInKA-OlJo,675
8
+ pymiele-0.3.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
9
+ pymiele-0.3.1.dist-info/top_level.txt,sha256=BwkHrSO2w_Bfxh6s8Ikcao5enEuQOpQhJ3SwUXBqY10,8
10
+ pymiele-0.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.4.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- pymiele/__init__.py,sha256=ktGXAWddn6Rz8-Drh1ka6SPj3b3lIhlZ4015K1Hin04,198
2
- pymiele/const.py,sha256=jmbdbXIq_MkL9naquxpShmqAXKZzswZQEf5zV2mZLbk,189
3
- pymiele/pymiele.py,sha256=z7iD6uQZ8Z7paBs5XgvB6k6jkFo0cSB7ZYAUqPL7ECk,7185
4
- pymiele-0.2.0.dist-info/LICENSE,sha256=scGm4_U2pd-rsGa6Edf6zsXFebrMT4RoyQz7-904_Wg,1072
5
- pymiele-0.2.0.dist-info/METADATA,sha256=uDied5T_kAmplJefJRBcxqV00QdgI-ZmK0ciknra9dA,694
6
- pymiele-0.2.0.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
7
- pymiele-0.2.0.dist-info/top_level.txt,sha256=BwkHrSO2w_Bfxh6s8Ikcao5enEuQOpQhJ3SwUXBqY10,8
8
- pymiele-0.2.0.dist-info/RECORD,,