python-ember-mug 1.3.0b5__tar.gz → 1.3.0b7__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_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/PKG-INFO +1 -1
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/__init__.py +1 -1
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/cli/helpers.py +3 -3
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/consts.py +11 -8
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/mug.py +11 -15
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/utils.py +5 -5
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/pyproject.toml +0 -1
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/.gitignore +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/LICENSE +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/README.md +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/__main__.py +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/cli/__init__.py +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/cli/commands.py +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/data.py +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/formatting.py +0 -0
- {python_ember_mug-1.3.0b5 → python_ember_mug-1.3.0b7}/ember_mug/scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-ember-mug
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.0b7
|
|
4
4
|
Summary: Python Library for Ember Mugs.
|
|
5
5
|
Project-URL: Changelog, https://sopelj.github.io/python-ember-mug/changelog/
|
|
6
6
|
Project-URL: Documentation, https://sopelj.github.io/python-ember-mug/
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import re
|
|
6
5
|
from argparse import ArgumentTypeError
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
from functools import partial
|
|
9
8
|
from typing import TYPE_CHECKING
|
|
10
9
|
|
|
11
|
-
from ember_mug.consts import MAC_ADDRESS_REGEX, TemperatureUnit
|
|
10
|
+
from ember_mug.consts import IS_MACOS, MAC_ADDRESS_REGEX, MAC_UUID_REGEX, TemperatureUnit
|
|
12
11
|
from ember_mug.data import Change
|
|
13
12
|
from ember_mug.formatting import format_led_colour, format_liquid_level, format_temp
|
|
14
13
|
|
|
@@ -26,7 +25,8 @@ base_formatters: dict[str, Callable] = {
|
|
|
26
25
|
|
|
27
26
|
def validate_mac(value: str) -> str:
|
|
28
27
|
"""Check if specified MAC Address is valid."""
|
|
29
|
-
|
|
28
|
+
mac_reg = MAC_UUID_REGEX if IS_MACOS else MAC_ADDRESS_REGEX
|
|
29
|
+
if not isinstance(value, str) or not mac_reg.match(value):
|
|
30
30
|
raise ArgumentTypeError("Invalid MAC Address")
|
|
31
31
|
return value.lower()
|
|
32
32
|
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import platform
|
|
6
6
|
import re
|
|
7
|
-
from enum import
|
|
7
|
+
from enum import IntEnum, StrEnum
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
from typing import NamedTuple
|
|
10
10
|
from uuid import UUID
|
|
@@ -17,7 +17,7 @@ EMBER_BLE_SIG = 0x03C1
|
|
|
17
17
|
DEFAULT_NAME = "Ember Device"
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class DeviceType(
|
|
20
|
+
class DeviceType(StrEnum):
|
|
21
21
|
"""Base device types."""
|
|
22
22
|
|
|
23
23
|
CUP = "cup"
|
|
@@ -26,7 +26,7 @@ class DeviceType(str, Enum):
|
|
|
26
26
|
TUMBLER = "tumbler"
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class DeviceModel(
|
|
29
|
+
class DeviceModel(StrEnum):
|
|
30
30
|
"""Know device models."""
|
|
31
31
|
|
|
32
32
|
CUP_6_OZ = "CM21S"
|
|
@@ -50,7 +50,7 @@ DEVICE_MODEL_NAMES: dict[DeviceModel, str] = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class DeviceColour(
|
|
53
|
+
class DeviceColour(StrEnum):
|
|
54
54
|
"""All colours possible found across models."""
|
|
55
55
|
|
|
56
56
|
SAGE_GREEN = "Sage Green"
|
|
@@ -66,7 +66,7 @@ class DeviceColour(str, Enum):
|
|
|
66
66
|
ROSE_GOLD = "Rose Gold"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
class TemperatureUnit(
|
|
69
|
+
class TemperatureUnit(StrEnum):
|
|
70
70
|
"""Temperature Units."""
|
|
71
71
|
|
|
72
72
|
CELSIUS = "°C"
|
|
@@ -177,7 +177,7 @@ class LiquidState(IntEnum):
|
|
|
177
177
|
return self.label
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
class VolumeLevel(
|
|
180
|
+
class VolumeLevel(StrEnum):
|
|
181
181
|
"""Class to manage volume levels."""
|
|
182
182
|
|
|
183
183
|
LOW = "low"
|
|
@@ -268,8 +268,11 @@ UPDATE_ATTRS = {
|
|
|
268
268
|
EXTRA_ATTRS = {"battery_voltage", "date_time_zone", "udsk", "dsk"}
|
|
269
269
|
|
|
270
270
|
# Validation
|
|
271
|
-
|
|
271
|
+
# *Note*: Additional characters are escaped because Home Assistant uses LitElement with "v" mode which is stricter.
|
|
272
|
+
MUG_NAME_REGEX = re.compile(r"^[A-Za-z0-9,.\[\]#\(\)!\"\';:\|\-_+<>%= ]{1,16}$")
|
|
272
273
|
MUG_NAME_PATTERN = MUG_NAME_REGEX.pattern
|
|
273
|
-
MAC_ADDRESS_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
|
|
274
|
+
MAC_ADDRESS_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$", re.IGNORECASE)
|
|
275
|
+
MAC_UUID_REGEX = re.compile(r"^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", re.IGNORECASE)
|
|
274
276
|
|
|
275
277
|
IS_LINUX = platform.system() == "Linux"
|
|
278
|
+
IS_MACOS = platform.system() == "Darwin"
|
|
@@ -7,7 +7,7 @@ import contextlib
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
9
|
from datetime import UTC, datetime
|
|
10
|
-
from enum import
|
|
10
|
+
from enum import StrEnum
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
from time import time
|
|
13
13
|
from typing import TYPE_CHECKING, Any, Concatenate, Literal, ParamSpec, TypeVar
|
|
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
|
|
|
46
46
|
from bleak.backends.characteristic import BleakGATTCharacteristic
|
|
47
47
|
from bleak.backends.device import BLEDevice
|
|
48
48
|
|
|
49
|
-
TempUnitType = Literal["°C", "°F"] | TemperatureUnit |
|
|
49
|
+
TempUnitType = Literal["°C", "°F"] | TemperatureUnit | StrEnum
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
logger = logging.getLogger(__name__)
|
|
@@ -136,17 +136,13 @@ class EmberMug:
|
|
|
136
136
|
|
|
137
137
|
def _convert_to_device_unit(self, value: float) -> float:
|
|
138
138
|
"""Convert user value to the unit the device expects."""
|
|
139
|
-
if self.data.user_unit == TemperatureUnit.
|
|
140
|
-
return convert_temp_to_fahrenheit(value)
|
|
141
|
-
if self.data.user_unit == TemperatureUnit.FAHRENHEIT and self.data.temperature_unit == TemperatureUnit.CELSIUS:
|
|
139
|
+
if self.data.user_unit == TemperatureUnit.FAHRENHEIT:
|
|
142
140
|
return convert_temp_to_celsius(value)
|
|
143
141
|
return value
|
|
144
142
|
|
|
145
143
|
def _convert_to_user_unit(self, value: float) -> float:
|
|
146
144
|
"""Convert device value to the unit the user expects."""
|
|
147
|
-
if self.data.user_unit == TemperatureUnit.
|
|
148
|
-
return convert_temp_to_celsius(value)
|
|
149
|
-
if self.data.user_unit == TemperatureUnit.FAHRENHEIT and self.data.temperature_unit == TemperatureUnit.CELSIUS:
|
|
145
|
+
if self.data.user_unit == TemperatureUnit.FAHRENHEIT:
|
|
150
146
|
return convert_temp_to_fahrenheit(value)
|
|
151
147
|
return value
|
|
152
148
|
|
|
@@ -291,7 +287,7 @@ class EmberMug:
|
|
|
291
287
|
|
|
292
288
|
async def pair(self) -> None:
|
|
293
289
|
"""Attempt to pair."""
|
|
294
|
-
with contextlib.suppress(BleakError, EOFError):
|
|
290
|
+
with contextlib.suppress(BleakError, EOFError, NotImplementedError):
|
|
295
291
|
await self._ensure_connection()
|
|
296
292
|
await self._client.pair()
|
|
297
293
|
|
|
@@ -314,11 +310,11 @@ class EmberMug:
|
|
|
314
310
|
|
|
315
311
|
async def set_target_temp(self, target_temp: float) -> None:
|
|
316
312
|
"""Set new target temp for mug."""
|
|
317
|
-
if self.data.use_metric is
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
unit = TemperatureUnit.FAHRENHEIT if self.data.use_metric is False else TemperatureUnit.CELSIUS
|
|
314
|
+
min_temp, max_temp = MIN_MAX_TEMPS[unit]
|
|
315
|
+
if target_temp != 0 and not (min_temp <= target_temp <= max_temp):
|
|
316
|
+
raise ValueError(f"Temperature '{target_temp}' is not between {min_temp} and {max_temp} or 0.")
|
|
317
|
+
|
|
322
318
|
target_temp = self._convert_to_device_unit(target_temp)
|
|
323
319
|
target = bytearray(round(target_temp / 0.01).to_bytes(2, "little"))
|
|
324
320
|
await self._write(MugCharacteristic.TARGET_TEMPERATURE, target)
|
|
@@ -405,7 +401,7 @@ class EmberMug:
|
|
|
405
401
|
|
|
406
402
|
async def set_temperature_unit(self, unit: TempUnitType) -> None:
|
|
407
403
|
"""Set mug unit."""
|
|
408
|
-
text_unit = unit.value if isinstance(unit,
|
|
404
|
+
text_unit = unit.value if isinstance(unit, StrEnum) else unit
|
|
409
405
|
unit_bytes = bytearray([1 if text_unit == TemperatureUnit.FAHRENHEIT else 0])
|
|
410
406
|
await self._write(MugCharacteristic.TEMPERATURE_UNIT, unit_bytes)
|
|
411
407
|
self.data.temperature_unit = TemperatureUnit(unit)
|
|
@@ -67,13 +67,13 @@ def get_colour_from_int(colour_id: int) -> DeviceColour | None: # noqa: PLR0911
|
|
|
67
67
|
return DeviceColour.BLACK
|
|
68
68
|
if colour_id in (-126, -62, 2):
|
|
69
69
|
return DeviceColour.WHITE
|
|
70
|
-
if colour_id in (
|
|
70
|
+
if colour_id in (-120, -117, -63, -56, -53, 8, 11):
|
|
71
71
|
return DeviceColour.RED
|
|
72
|
-
if colour_id in (-131, -125, -61, 3, 83):
|
|
72
|
+
if colour_id in (-131, -125, -61, 3, 51, 83):
|
|
73
73
|
return DeviceColour.COPPER
|
|
74
74
|
if colour_id in (-124, -60):
|
|
75
75
|
return DeviceColour.ROSE_GOLD
|
|
76
|
-
if colour_id in (-
|
|
76
|
+
if colour_id in (-123, -59):
|
|
77
77
|
return DeviceColour.STAINLESS_STEEL
|
|
78
78
|
return {
|
|
79
79
|
-51: DeviceColour.SANDSTONE,
|
|
@@ -95,7 +95,7 @@ def get_model_from_single_int_and_services( # noqa PLR0911
|
|
|
95
95
|
return DeviceModel.MUG_1_10_OZ
|
|
96
96
|
if model_id == 65:
|
|
97
97
|
return DeviceModel.MUG_1_14_OZ
|
|
98
|
-
if model_id in (-51, -59, -63, -61, -62):
|
|
98
|
+
if model_id in (-51, -59, -63, -61, -62, 120):
|
|
99
99
|
return DeviceModel.MUG_2_14_OZ
|
|
100
100
|
if model_id == -60:
|
|
101
101
|
return DeviceModel.CUP_6_OZ
|
|
@@ -108,7 +108,7 @@ def get_model_from_id_and_gen(model_id: int, generation: int) -> DeviceModel | N
|
|
|
108
108
|
"""Extract model from identifier in advertiser data."""
|
|
109
109
|
if model_id == 1:
|
|
110
110
|
return DeviceModel.MUG_1_10_OZ if generation < 2 else DeviceModel.MUG_2_10_OZ
|
|
111
|
-
if model_id
|
|
111
|
+
if model_id in (2, 120):
|
|
112
112
|
return DeviceModel.MUG_1_14_OZ if generation < 2 else DeviceModel.MUG_2_14_OZ
|
|
113
113
|
if model_id == 3:
|
|
114
114
|
return DeviceModel.TRAVEL_MUG_12_OZ
|
|
@@ -74,7 +74,6 @@ features = ["test"]
|
|
|
74
74
|
|
|
75
75
|
[[tool.hatch.envs.test.matrix]]
|
|
76
76
|
python = ["3.11", "3.12", "3.13", "3.14"]
|
|
77
|
-
bleak = ["1.0.1", "2.0.0"]
|
|
78
77
|
|
|
79
78
|
[tool.hatch.envs.test.scripts]
|
|
80
79
|
cov = "pytest -vvv --asyncio-mode=auto --cov=ember_mug --cov-branch --cov-report=xml --cov-report=term-missing tests"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|