python-ember-mug 1.0.1__tar.gz → 1.1.0__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.0.1 → python_ember_mug-1.1.0}/PKG-INFO +8 -10
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/README.md +3 -3
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/__init__.py +2 -1
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/cli/commands.py +23 -23
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/mug.py +2 -2
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/scanner.py +23 -12
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/pyproject.toml +7 -9
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/cli/test_commands.py +43 -43
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_scanner.py +16 -16
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/.gitignore +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/LICENSE +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/__main__.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/cli/__init__.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/cli/helpers.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/consts.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/data.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/formatting.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/ember_mug/utils.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/__init__.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/cli/__init__.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/cli/test_helpers.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/conftest.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_connection.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_consts.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_data.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_formatting.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_mug_data.py +0 -0
- {python_ember_mug-1.0.1 → python_ember_mug-1.1.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-ember-mug
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Python Library for Ember Mugs.
|
|
5
5
|
Project-URL: Documentation, https://sopelj.github.io/python-ember-mug/
|
|
6
6
|
Project-URL: Source code, https://github.com/sopelj/python-ember-mug/
|
|
@@ -13,18 +13,16 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Natural Language :: English
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Requires-Python: >=3.
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: bleak
|
|
22
|
-
Requires-Dist: bleak>=0.21.0
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: bleak-retry-connector>=3.5.0
|
|
20
|
+
Requires-Dist: bleak>=0.22.2
|
|
23
21
|
Provides-Extra: docs
|
|
24
22
|
Requires-Dist: black; extra == 'docs'
|
|
25
23
|
Requires-Dist: mkdocs-autorefs; extra == 'docs'
|
|
26
24
|
Requires-Dist: mkdocs-gen-files; extra == 'docs'
|
|
27
|
-
Requires-Dist: mkdocs-include-markdown-plugin<
|
|
25
|
+
Requires-Dist: mkdocs-include-markdown-plugin<8.0.0,>=3.6.1; extra == 'docs'
|
|
28
26
|
Requires-Dist: mkdocs-literate-nav; extra == 'docs'
|
|
29
27
|
Requires-Dist: mkdocs-material-extensions; extra == 'docs'
|
|
30
28
|
Requires-Dist: mkdocs-material<10.0.0,>=8.4.1; extra == 'docs'
|
|
@@ -102,15 +100,15 @@ Attributes by device:
|
|
|
102
100
|
### Python
|
|
103
101
|
|
|
104
102
|
```python
|
|
105
|
-
from ember_mug.scanner import
|
|
103
|
+
from ember_mug.scanner import find_device, discover_devices
|
|
106
104
|
from ember_mug.utils import get_model_info_from_advertiser_data
|
|
107
105
|
from ember_mug.mug import EmberMug
|
|
108
106
|
|
|
109
107
|
# if first time with mug in pairing
|
|
110
|
-
|
|
108
|
+
devices = await discover_devices()
|
|
111
109
|
|
|
112
110
|
# after paired you can simply use
|
|
113
|
-
device, advertisement = await
|
|
111
|
+
device, advertisement = await find_device()
|
|
114
112
|
model_info = get_model_info_from_advertiser_data(advertisement)
|
|
115
113
|
mug = EmberMug(device, model_info)
|
|
116
114
|
await mug.update_all()
|
|
@@ -62,15 +62,15 @@ Attributes by device:
|
|
|
62
62
|
### Python
|
|
63
63
|
|
|
64
64
|
```python
|
|
65
|
-
from ember_mug.scanner import
|
|
65
|
+
from ember_mug.scanner import find_device, discover_devices
|
|
66
66
|
from ember_mug.utils import get_model_info_from_advertiser_data
|
|
67
67
|
from ember_mug.mug import EmberMug
|
|
68
68
|
|
|
69
69
|
# if first time with mug in pairing
|
|
70
|
-
|
|
70
|
+
devices = await discover_devices()
|
|
71
71
|
|
|
72
72
|
# after paired you can simply use
|
|
73
|
-
device, advertisement = await
|
|
73
|
+
device, advertisement = await find_device()
|
|
74
74
|
model_info = get_model_info_from_advertiser_data(advertisement)
|
|
75
75
|
mug = EmberMug(device, model_info)
|
|
76
76
|
await mug.update_all()
|
|
@@ -14,7 +14,7 @@ from bleak import AdvertisementData, BleakError
|
|
|
14
14
|
from ember_mug.consts import ATTR_LABELS, EXTRA_ATTRS, IS_LINUX, VolumeLevel
|
|
15
15
|
from ember_mug.data import Colour
|
|
16
16
|
from ember_mug.mug import EmberMug
|
|
17
|
-
from ember_mug.scanner import
|
|
17
|
+
from ember_mug.scanner import discover_devices, find_device
|
|
18
18
|
|
|
19
19
|
from ..formatting import format_capacity
|
|
20
20
|
from ..utils import get_model_info_from_advertiser_data
|
|
@@ -29,9 +29,9 @@ all_attrs = list(ATTR_LABELS) + list(EXTRA_ATTRS)
|
|
|
29
29
|
get_attribute_names = [n.replace("_", "-") for n in all_attrs]
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
async def
|
|
33
|
-
"""Help to get the
|
|
34
|
-
device, advertisement = await
|
|
32
|
+
async def get_device(args: Namespace) -> EmberMug:
|
|
33
|
+
"""Help to get the devices based on command args."""
|
|
34
|
+
device, advertisement = await find_device_cmd(args)
|
|
35
35
|
mug = EmberMug(
|
|
36
36
|
device,
|
|
37
37
|
get_model_info_from_advertiser_data(advertisement),
|
|
@@ -43,10 +43,10 @@ async def get_mug(args: Namespace) -> EmberMug:
|
|
|
43
43
|
return mug
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
async def
|
|
46
|
+
async def find_device_cmd(args: Namespace) -> tuple[BLEDevice, AdvertisementData]:
|
|
47
47
|
"""Find a single device that has already been paired."""
|
|
48
48
|
try:
|
|
49
|
-
device, advertisement = await
|
|
49
|
+
device, advertisement = await find_device(mac=args.mac, adapter=args.adapter)
|
|
50
50
|
except BleakError as e:
|
|
51
51
|
print(f"An error occurred trying to find a device: {e}")
|
|
52
52
|
sys.exit(1)
|
|
@@ -58,10 +58,10 @@ async def find_device(args: Namespace) -> tuple[BLEDevice, AdvertisementData]:
|
|
|
58
58
|
return device, advertisement
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
async def
|
|
61
|
+
async def discover_cmd(args: Namespace) -> list[tuple[BLEDevice, AdvertisementData]]:
|
|
62
62
|
"""Discover new devices in pairing mode."""
|
|
63
63
|
try:
|
|
64
|
-
mugs = await
|
|
64
|
+
mugs = await discover_devices(mac=args.mac)
|
|
65
65
|
except BleakError as e:
|
|
66
66
|
print(f"An error occurred trying to discover devices: {e}")
|
|
67
67
|
sys.exit(1)
|
|
@@ -83,9 +83,9 @@ async def discover(args: Namespace) -> list[tuple[BLEDevice, AdvertisementData]]
|
|
|
83
83
|
return mugs
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
async def
|
|
86
|
+
async def fetch_info_cmd(args: Namespace) -> None:
|
|
87
87
|
"""Fetch all information from a mug and end."""
|
|
88
|
-
mug = await
|
|
88
|
+
mug = await get_device(args)
|
|
89
89
|
async with mug.connection(adapter=args.adapter):
|
|
90
90
|
if not args.raw:
|
|
91
91
|
print("Connected.\nFetching Info")
|
|
@@ -93,9 +93,9 @@ async def fetch_info(args: Namespace) -> None:
|
|
|
93
93
|
print_info(mug)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
async def
|
|
96
|
+
async def poll_device_cmd(args: Namespace) -> None:
|
|
97
97
|
"""Fetch all information and keep polling for changes."""
|
|
98
|
-
mug = await
|
|
98
|
+
mug = await get_device(args)
|
|
99
99
|
async with mug.connection(adapter=args.adapter):
|
|
100
100
|
if not args.raw:
|
|
101
101
|
print("Connected.\nFetching Info")
|
|
@@ -111,9 +111,9 @@ async def poll_mug(args: Namespace) -> None:
|
|
|
111
111
|
print_changes(await mug.update_all(), mug.data.use_metric)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
async def
|
|
114
|
+
async def get_device_value_cmd(args: Namespace) -> None:
|
|
115
115
|
"""Get values from the mug and print them."""
|
|
116
|
-
mug = await
|
|
116
|
+
mug = await get_device(args)
|
|
117
117
|
data = {}
|
|
118
118
|
attributes = [a.replace("-", "_") for a in args.attributes]
|
|
119
119
|
async with mug.connection(adapter=args.adapter):
|
|
@@ -131,8 +131,8 @@ async def get_mug_value(args: Namespace) -> None:
|
|
|
131
131
|
print_table([(ATTR_LABELS.get(attr, attr), str(mug.data.get_formatted_attr(attr))) for attr in data])
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
async def
|
|
135
|
-
"""Set one or more values on the
|
|
134
|
+
async def set_device_value_cmd(args: Namespace) -> None:
|
|
135
|
+
"""Set one or more values on the device."""
|
|
136
136
|
attrs = ("name", "target_temp", "temperature_unit", "led_colour", "volume_level")
|
|
137
137
|
values = [(attr, value) for attr in attrs if (value := getattr(args, attr, None))]
|
|
138
138
|
if not values:
|
|
@@ -141,7 +141,7 @@ async def set_mug_value(args: Namespace) -> None:
|
|
|
141
141
|
print(f'Options: {", ".join(options)}')
|
|
142
142
|
sys.exit(1)
|
|
143
143
|
|
|
144
|
-
mug = await
|
|
144
|
+
mug = await get_device(args)
|
|
145
145
|
async with mug.connection(adapter=args.adapter):
|
|
146
146
|
for attr, value in values:
|
|
147
147
|
method = getattr(mug, f'set_{attr.replace("-", "_")}')
|
|
@@ -180,12 +180,12 @@ class EmberMugCli:
|
|
|
180
180
|
"""Very simple CLI Interface to interact with a mug."""
|
|
181
181
|
|
|
182
182
|
_commands: ClassVar[dict[str, Callable[[Namespace], Awaitable]]] = {
|
|
183
|
-
"find":
|
|
184
|
-
"discover":
|
|
185
|
-
"info":
|
|
186
|
-
"poll":
|
|
187
|
-
"get":
|
|
188
|
-
"set":
|
|
183
|
+
"find": find_device_cmd,
|
|
184
|
+
"discover": discover_cmd,
|
|
185
|
+
"info": fetch_info_cmd,
|
|
186
|
+
"poll": poll_device_cmd,
|
|
187
|
+
"get": get_device_value_cmd,
|
|
188
|
+
"set": set_device_value_cmd,
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
def __init__(self) -> None:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import contextlib
|
|
6
6
|
import logging
|
|
7
|
-
from datetime import
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from functools import cached_property
|
|
10
10
|
from time import time
|
|
@@ -389,7 +389,7 @@ class EmberMug:
|
|
|
389
389
|
"""Get date and time zone."""
|
|
390
390
|
date_time_zone_bytes = await self._read(MugCharacteristic.DATE_TIME_AND_ZONE)
|
|
391
391
|
time_value = bytes_to_big_int(date_time_zone_bytes[:4])
|
|
392
|
-
return datetime.fromtimestamp(time_value,
|
|
392
|
+
return datetime.fromtimestamp(time_value, UTC) if time_value > 0 else None
|
|
393
393
|
|
|
394
394
|
async def get_firmware(self) -> MugFirmwareInfo:
|
|
395
395
|
"""Get firmware info."""
|
|
@@ -4,19 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import contextlib
|
|
6
6
|
import logging
|
|
7
|
-
import sys
|
|
8
7
|
from typing import TYPE_CHECKING, Any
|
|
9
8
|
|
|
10
9
|
from bleak import BleakScanner
|
|
11
10
|
|
|
12
11
|
from .consts import DEVICE_SERVICE_UUIDS, IS_LINUX
|
|
13
12
|
|
|
14
|
-
if sys.version_info < (3, 11):
|
|
15
|
-
# library required before Python 3.11
|
|
16
|
-
import async_timeout
|
|
17
|
-
else:
|
|
18
|
-
async_timeout = asyncio
|
|
19
|
-
|
|
20
13
|
if TYPE_CHECKING:
|
|
21
14
|
from bleak.backends.device import BLEDevice
|
|
22
15
|
from bleak.backends.scanner import AdvertisementData
|
|
@@ -36,12 +29,22 @@ def build_scanner_kwargs(adapter: str | None = None) -> dict[str, Any]:
|
|
|
36
29
|
return kwargs | {"adapter": adapter} if adapter else kwargs
|
|
37
30
|
|
|
38
31
|
|
|
39
|
-
async def
|
|
32
|
+
async def discover_devices(
|
|
40
33
|
mac: str | None = None,
|
|
41
34
|
adapter: str | None = None,
|
|
42
35
|
wait: int = 5,
|
|
43
36
|
) -> list[tuple[BLEDevice, AdvertisementData]]:
|
|
44
|
-
"""
|
|
37
|
+
"""
|
|
38
|
+
Discover new devices in pairing mode.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
-------
|
|
42
|
+
```python
|
|
43
|
+
devices = await discover_devices()
|
|
44
|
+
for device, advertisement in devices:
|
|
45
|
+
print(device.address, advertisement)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
45
48
|
async with BleakScanner(**build_scanner_kwargs(adapter)) as scanner:
|
|
46
49
|
await asyncio.sleep(wait)
|
|
47
50
|
return [
|
|
@@ -51,17 +54,25 @@ async def discover_mugs(
|
|
|
51
54
|
]
|
|
52
55
|
|
|
53
56
|
|
|
54
|
-
async def
|
|
57
|
+
async def find_device(
|
|
55
58
|
mac: str | None = None,
|
|
56
59
|
adapter: str | None = None,
|
|
57
60
|
timeout: int = DEFAULT_TIMEOUT,
|
|
58
61
|
) -> tuple[BLEDevice, AdvertisementData] | tuple[None, None]:
|
|
59
|
-
"""
|
|
62
|
+
"""
|
|
63
|
+
Find a device that has previously been discovered.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
-------
|
|
67
|
+
```python
|
|
68
|
+
device = await find_device("my:mac:addr")
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
60
71
|
if mac is not None:
|
|
61
72
|
mac = mac.lower()
|
|
62
73
|
async with BleakScanner(**build_scanner_kwargs(adapter)) as scanner:
|
|
63
74
|
with contextlib.suppress(asyncio.TimeoutError):
|
|
64
|
-
async with
|
|
75
|
+
async with asyncio.timeout(timeout):
|
|
65
76
|
async for device, advertisement in scanner.advertisement_data():
|
|
66
77
|
if (not mac and device.name and device.name.startswith("Ember")) or (
|
|
67
78
|
mac and device.address.lower() == mac
|
|
@@ -8,21 +8,19 @@ authors = [
|
|
|
8
8
|
{ name = "Jesse Sopel", email = "jesse.sopel@gmail.com" },
|
|
9
9
|
]
|
|
10
10
|
license = "MIT"
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
12
|
dynamic = ["version"]
|
|
13
13
|
classifiers=[
|
|
14
14
|
'Intended Audience :: Developers',
|
|
15
15
|
'License :: OSI Approved :: MIT License',
|
|
16
16
|
'Natural Language :: English',
|
|
17
17
|
'Programming Language :: Python :: 3',
|
|
18
|
-
'Programming Language :: Python :: 3.10',
|
|
19
18
|
'Programming Language :: Python :: 3.11',
|
|
20
19
|
'Programming Language :: Python :: 3.12',
|
|
21
20
|
]
|
|
22
21
|
dependencies = [
|
|
23
|
-
"bleak-retry-connector>=3.
|
|
24
|
-
"bleak>=0.
|
|
25
|
-
"async-timeout; python_version < '3.11'",
|
|
22
|
+
"bleak-retry-connector>=3.5.0",
|
|
23
|
+
"bleak>=0.22.2",
|
|
26
24
|
]
|
|
27
25
|
|
|
28
26
|
[project.optional-dependencies]
|
|
@@ -33,7 +31,7 @@ test = [
|
|
|
33
31
|
]
|
|
34
32
|
docs = [
|
|
35
33
|
"mkdocs>=1.1.21",
|
|
36
|
-
"mkdocs-include-markdown-plugin>=3.6.1,<
|
|
34
|
+
"mkdocs-include-markdown-plugin>=3.6.1,<8.0.0",
|
|
37
35
|
"mkdocs-material>=8.4.1,<10.0.0",
|
|
38
36
|
"mkdocstrings[python]",
|
|
39
37
|
"mkdocs-material-extensions",
|
|
@@ -78,7 +76,7 @@ python = "3.12"
|
|
|
78
76
|
features = ["test"]
|
|
79
77
|
|
|
80
78
|
[[tool.hatch.envs.test.matrix]]
|
|
81
|
-
python = ["3.
|
|
79
|
+
python = ["3.11", "3.12"]
|
|
82
80
|
|
|
83
81
|
[tool.hatch.envs.test.scripts]
|
|
84
82
|
cov = "pytest -vvv --asyncio-mode=auto --cov=ember_mug --cov-branch --cov-report=xml --cov-report=term-missing tests"
|
|
@@ -95,7 +93,7 @@ serve = "mkdocs serve --dev-addr localhost:8000"
|
|
|
95
93
|
[tool.black]
|
|
96
94
|
line-length = 120
|
|
97
95
|
skip-string-normalization = true
|
|
98
|
-
target-version = [
|
|
96
|
+
target-version = ["py311", "py312"]
|
|
99
97
|
include = '\.pyi?$'
|
|
100
98
|
exclude = '''
|
|
101
99
|
/(
|
|
@@ -126,7 +124,7 @@ exclude_lines = [
|
|
|
126
124
|
[tool.ruff]
|
|
127
125
|
fix = true
|
|
128
126
|
line-length = 120
|
|
129
|
-
target-version = "
|
|
127
|
+
target-version = "py311"
|
|
130
128
|
select = [
|
|
131
129
|
"A",
|
|
132
130
|
"ASYNC",
|
|
@@ -13,13 +13,13 @@ from ember_mug import EmberMug
|
|
|
13
13
|
from ember_mug.cli.commands import (
|
|
14
14
|
EmberMugCli,
|
|
15
15
|
colour_type,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
discover_cmd,
|
|
17
|
+
fetch_info_cmd,
|
|
18
|
+
find_device_cmd,
|
|
19
|
+
get_device,
|
|
20
|
+
get_device_value_cmd,
|
|
21
|
+
poll_device_cmd,
|
|
22
|
+
set_device_value_cmd,
|
|
23
23
|
)
|
|
24
24
|
from ember_mug.consts import DeviceColour, DeviceModel
|
|
25
25
|
from ember_mug.data import Colour, ModelInfo, MugData
|
|
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
|
|
34
34
|
|
|
35
35
|
@pytest.fixture()
|
|
36
36
|
def mock_mug_with_connection() -> Generator[AsyncMock, None, None]:
|
|
37
|
-
with patch("ember_mug.cli.commands.
|
|
37
|
+
with patch("ember_mug.cli.commands.get_device") as mock:
|
|
38
38
|
mock_mug = AsyncMock()
|
|
39
39
|
mock_mug.connection = Mock(return_value=mock_connection)
|
|
40
40
|
mock.return_value = mock_mug
|
|
@@ -54,18 +54,18 @@ def mock_namespace(**kwargs: Any) -> Namespace:
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
@patch("ember_mug.cli.commands.EmberMug", spec=EmberMug)
|
|
57
|
-
@patch("ember_mug.cli.commands.
|
|
58
|
-
async def
|
|
59
|
-
|
|
57
|
+
@patch("ember_mug.cli.commands.find_device_cmd")
|
|
58
|
+
async def test_get_device(
|
|
59
|
+
mock_find_device_cmd: AsyncMock,
|
|
60
60
|
mock_ember_mug: AsyncMock,
|
|
61
61
|
capsys: CaptureFixture,
|
|
62
62
|
ble_device: BLEDevice,
|
|
63
63
|
) -> None:
|
|
64
|
-
|
|
64
|
+
mock_find_device_cmd.return_value = ble_device, TEST_MUG_ADVERTISEMENT
|
|
65
65
|
args = mock_namespace(extra=True)
|
|
66
|
-
mug = await
|
|
66
|
+
mug = await get_device(args)
|
|
67
67
|
assert mug is not None
|
|
68
|
-
|
|
68
|
+
mock_find_device_cmd.assert_called_once_with(args)
|
|
69
69
|
mock_ember_mug.assert_called_once_with(
|
|
70
70
|
ble_device,
|
|
71
71
|
ModelInfo(DeviceModel.MUG_2_10_OZ, DeviceColour.BLACK),
|
|
@@ -77,17 +77,17 @@ async def test_get_mug(
|
|
|
77
77
|
|
|
78
78
|
# Raw prints nothing
|
|
79
79
|
args = mock_namespace(extra=True, raw=True)
|
|
80
|
-
mug = await
|
|
80
|
+
mug = await get_device(args)
|
|
81
81
|
assert mug is not None
|
|
82
82
|
captured = capsys.readouterr()
|
|
83
83
|
assert captured.out == ""
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
@patch("ember_mug.cli.commands.
|
|
86
|
+
@patch("ember_mug.cli.commands.find_device")
|
|
87
87
|
async def test_find_device(mock_find_mug: AsyncMock, capsys: CaptureFixture, ble_device: BLEDevice) -> None:
|
|
88
88
|
mock_find_mug.return_value = (ble_device, TEST_MUG_ADVERTISEMENT)
|
|
89
89
|
args = mock_namespace(mac=ble_device.address)
|
|
90
|
-
device, advertisement = await
|
|
90
|
+
device, advertisement = await find_device_cmd(args)
|
|
91
91
|
assert device == ble_device
|
|
92
92
|
assert advertisement == TEST_MUG_ADVERTISEMENT
|
|
93
93
|
mock_find_mug.assert_called_once_with(mac=ble_device.address, adapter=None)
|
|
@@ -96,38 +96,38 @@ async def test_find_device(mock_find_mug: AsyncMock, capsys: CaptureFixture, ble
|
|
|
96
96
|
|
|
97
97
|
# Raw prints nothing
|
|
98
98
|
args = Namespace(mac=ble_device.address, adapter=None, raw=True)
|
|
99
|
-
await
|
|
99
|
+
await find_device_cmd(args)
|
|
100
100
|
captured = capsys.readouterr()
|
|
101
101
|
assert captured.out == ""
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
@patch("ember_mug.cli.commands.
|
|
104
|
+
@patch("ember_mug.cli.commands.find_device")
|
|
105
105
|
async def test_find_device_no_device(mock_find_mug: AsyncMock, capsys: CaptureFixture) -> None:
|
|
106
106
|
mock_find_mug.return_value = (None, None)
|
|
107
107
|
args = mock_namespace(mac=TEST_MAC)
|
|
108
108
|
with pytest.raises(SystemExit, match="1"):
|
|
109
|
-
await
|
|
109
|
+
await find_device_cmd(args)
|
|
110
110
|
mock_find_mug.assert_called_once_with(mac=TEST_MAC, adapter=None)
|
|
111
111
|
captured = capsys.readouterr()
|
|
112
112
|
assert captured.out == "No device was found.\n"
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
@patch("ember_mug.cli.commands.
|
|
115
|
+
@patch("ember_mug.cli.commands.find_device")
|
|
116
116
|
async def test_find_device_bleak_error(mock_find_mug: AsyncMock, capsys: CaptureFixture) -> None:
|
|
117
117
|
mock_find_mug.side_effect = BleakError("Test Error")
|
|
118
118
|
args = mock_namespace(mac=TEST_MAC)
|
|
119
119
|
with pytest.raises(SystemExit, match="1"):
|
|
120
|
-
await
|
|
120
|
+
await find_device_cmd(args)
|
|
121
121
|
mock_find_mug.assert_called_once_with(mac=TEST_MAC, adapter=None)
|
|
122
122
|
captured = capsys.readouterr()
|
|
123
123
|
assert captured.out == "An error occurred trying to find a device: Test Error\n"
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
@patch("ember_mug.cli.commands.
|
|
126
|
+
@patch("ember_mug.cli.commands.discover_devices")
|
|
127
127
|
async def test_discover(mock_discover_mugs: AsyncMock, capsys: CaptureFixture, ble_device: BLEDevice) -> None:
|
|
128
128
|
mock_discover_mugs.return_value = [(ble_device, TEST_MUG_ADVERTISEMENT)]
|
|
129
129
|
args = mock_namespace(mac=TEST_MAC)
|
|
130
|
-
mugs = await
|
|
130
|
+
mugs = await discover_cmd(args)
|
|
131
131
|
assert mugs == [(ble_device, TEST_MUG_ADVERTISEMENT)]
|
|
132
132
|
mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
|
|
133
133
|
captured = capsys.readouterr()
|
|
@@ -141,30 +141,30 @@ async def test_discover(mock_discover_mugs: AsyncMock, capsys: CaptureFixture, b
|
|
|
141
141
|
|
|
142
142
|
mock_discover_mugs.reset_mock()
|
|
143
143
|
args = mock_namespace(mac=TEST_MAC, raw=True)
|
|
144
|
-
mugs = await
|
|
144
|
+
mugs = await discover_cmd(args)
|
|
145
145
|
assert mugs == [(ble_device, TEST_MUG_ADVERTISEMENT)]
|
|
146
146
|
mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
|
|
147
147
|
captured = capsys.readouterr()
|
|
148
148
|
assert captured.out == f"{TEST_MAC}\n"
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
@patch("ember_mug.cli.commands.
|
|
151
|
+
@patch("ember_mug.cli.commands.discover_devices")
|
|
152
152
|
async def test_discover_no_device(mock_discover_mugs: AsyncMock, capsys: CaptureFixture) -> None:
|
|
153
153
|
mock_discover_mugs.return_value = []
|
|
154
154
|
args = mock_namespace(mac=TEST_MAC)
|
|
155
155
|
with pytest.raises(SystemExit, match="1"):
|
|
156
|
-
await
|
|
156
|
+
await discover_cmd(args)
|
|
157
157
|
mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
|
|
158
158
|
captured = capsys.readouterr()
|
|
159
159
|
assert captured.out == 'No devices were found. Be sure it is in pairing mode. Or use "find" if already paired.\n'
|
|
160
160
|
|
|
161
161
|
|
|
162
|
-
@patch("ember_mug.cli.commands.
|
|
162
|
+
@patch("ember_mug.cli.commands.discover_devices")
|
|
163
163
|
async def test_discover_bleak_error(mock_discover_mugs: AsyncMock, capsys: CaptureFixture) -> None:
|
|
164
164
|
mock_discover_mugs.side_effect = BleakError("Test Error")
|
|
165
165
|
args = mock_namespace(mac=TEST_MAC)
|
|
166
166
|
with pytest.raises(SystemExit, match="1"):
|
|
167
|
-
await
|
|
167
|
+
await discover_cmd(args)
|
|
168
168
|
mock_discover_mugs.assert_called_once_with(mac=TEST_MAC)
|
|
169
169
|
captured = capsys.readouterr()
|
|
170
170
|
assert captured.out == "An error occurred trying to discover devices: Test Error\n"
|
|
@@ -178,14 +178,14 @@ async def test_fetch_info(
|
|
|
178
178
|
) -> None:
|
|
179
179
|
# Test normal
|
|
180
180
|
args = mock_namespace(mac=TEST_MAC)
|
|
181
|
-
await
|
|
181
|
+
await fetch_info_cmd(args)
|
|
182
182
|
captured = capsys.readouterr()
|
|
183
183
|
assert captured.out == "Connected.\nFetching Info\n"
|
|
184
184
|
mock_print_info.assert_called_once_with(mock_mug_with_connection)
|
|
185
185
|
|
|
186
186
|
# Test with Raw
|
|
187
187
|
args = mock_namespace(mac=TEST_MAC, raw=True)
|
|
188
|
-
await
|
|
188
|
+
await fetch_info_cmd(args)
|
|
189
189
|
captured = capsys.readouterr()
|
|
190
190
|
assert captured.out == ""
|
|
191
191
|
|
|
@@ -194,7 +194,7 @@ async def test_fetch_info(
|
|
|
194
194
|
@patch("ember_mug.cli.commands.print_info")
|
|
195
195
|
@patch("ember_mug.cli.commands.print_changes")
|
|
196
196
|
@patch("ember_mug.cli.commands.CommandLoop", lambda: [1])
|
|
197
|
-
async def
|
|
197
|
+
async def test_poll_device_cmd(
|
|
198
198
|
mock_print_changes: AsyncMock,
|
|
199
199
|
mock_print_info: AsyncMock,
|
|
200
200
|
mock_sleep: AsyncMock,
|
|
@@ -203,7 +203,7 @@ async def test_poll_mug(
|
|
|
203
203
|
) -> None:
|
|
204
204
|
# Test normal
|
|
205
205
|
args = mock_namespace(mac=TEST_MAC)
|
|
206
|
-
await
|
|
206
|
+
await poll_device_cmd(args)
|
|
207
207
|
captured = capsys.readouterr()
|
|
208
208
|
assert captured.out == "Connected.\nFetching Info\n\nWatching for changes\n"
|
|
209
209
|
mock_sleep.assert_has_calls([call(1)] * 60)
|
|
@@ -211,14 +211,14 @@ async def test_poll_mug(
|
|
|
211
211
|
# Test with Raw
|
|
212
212
|
mock_sleep.reset_mock()
|
|
213
213
|
args = mock_namespace(mac=TEST_MAC, raw=True)
|
|
214
|
-
await
|
|
214
|
+
await poll_device_cmd(args)
|
|
215
215
|
mock_sleep.assert_has_calls([call(1)] * 60)
|
|
216
216
|
captured = capsys.readouterr()
|
|
217
217
|
assert captured.out == ""
|
|
218
218
|
|
|
219
219
|
|
|
220
220
|
@patch("ember_mug.cli.commands.print_table")
|
|
221
|
-
async def
|
|
221
|
+
async def test_get_device_value_cmd(
|
|
222
222
|
mocked_print_table: Mock,
|
|
223
223
|
mock_mug_with_connection: AsyncMock,
|
|
224
224
|
capsys: CaptureFixture,
|
|
@@ -228,25 +228,25 @@ async def test_get_mug_value(
|
|
|
228
228
|
mock_mug_with_connection.get_target_temp.return_value = 55.5
|
|
229
229
|
mock_mug_with_connection.get_name.return_value = "test"
|
|
230
230
|
args = mock_namespace(attributes=["target_temp", "name"])
|
|
231
|
-
await
|
|
231
|
+
await get_device_value_cmd(args)
|
|
232
232
|
mock_mug_with_connection.get_target_temp.assert_called_once()
|
|
233
233
|
mocked_print_table.assert_called_once_with([("Target Temp", "test"), ("Device Name", "test")])
|
|
234
234
|
|
|
235
235
|
mock_mug_with_connection.get_led_colour.return_value = 55.5
|
|
236
236
|
args = mock_namespace(attributes=["led_colour", "name"], raw=True)
|
|
237
|
-
await
|
|
237
|
+
await get_device_value_cmd(args)
|
|
238
238
|
captured = capsys.readouterr()
|
|
239
239
|
assert captured.out == "55.5\ntest\n"
|
|
240
240
|
|
|
241
241
|
mock_mug_with_connection.get_name.side_effect = NotImplementedError
|
|
242
242
|
args = mock_namespace(attributes=["name"], raw=True)
|
|
243
243
|
with pytest.raises(SystemExit, match="1"):
|
|
244
|
-
await
|
|
244
|
+
await get_device_value_cmd(args)
|
|
245
245
|
|
|
246
246
|
|
|
247
|
-
async def
|
|
247
|
+
async def test_set_device_value_cmd_no_value(capsys: CaptureFixture) -> None:
|
|
248
248
|
with pytest.raises(SystemExit, match="1"):
|
|
249
|
-
await
|
|
249
|
+
await set_device_value_cmd(Namespace())
|
|
250
250
|
captured = capsys.readouterr()
|
|
251
251
|
assert captured.out == (
|
|
252
252
|
"Please specify at least one attribute and value to set.\n"
|
|
@@ -254,16 +254,16 @@ async def test_set_mug_value_no_value(capsys: CaptureFixture) -> None:
|
|
|
254
254
|
)
|
|
255
255
|
|
|
256
256
|
|
|
257
|
-
async def
|
|
257
|
+
async def test_set_device_value_cmd(mock_mug_with_connection: AsyncMock) -> None:
|
|
258
258
|
mock_mug_with_connection.data = MugData(ModelInfo())
|
|
259
259
|
args = mock_namespace(name="test")
|
|
260
|
-
await
|
|
260
|
+
await set_device_value_cmd(args)
|
|
261
261
|
mock_mug_with_connection.set_name.assert_called_once_with("test")
|
|
262
262
|
|
|
263
263
|
mock_mug_with_connection.reset_mock()
|
|
264
264
|
mock_mug_with_connection.set_name.side_effect = NotImplementedError("Unable to set name on Cup")
|
|
265
265
|
with pytest.raises(SystemExit, match="1"):
|
|
266
|
-
await
|
|
266
|
+
await set_device_value_cmd(args)
|
|
267
267
|
|
|
268
268
|
|
|
269
269
|
@pytest.mark.parametrize(
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
from bleak.backends.device import BLEDevice
|
|
6
6
|
|
|
7
7
|
from ember_mug.consts import DEVICE_SERVICE_UUIDS
|
|
8
|
-
from ember_mug.scanner import build_scanner_kwargs,
|
|
8
|
+
from ember_mug.scanner import build_scanner_kwargs, discover_devices, find_device
|
|
9
9
|
|
|
10
10
|
from .conftest import TEST_MUG_ADVERTISEMENT
|
|
11
11
|
|
|
@@ -29,34 +29,34 @@ def test_build_scanner_kwargs_other() -> None:
|
|
|
29
29
|
|
|
30
30
|
@patch("asyncio.sleep")
|
|
31
31
|
@patch("ember_mug.scanner.BleakScanner")
|
|
32
|
-
async def
|
|
32
|
+
async def test_discover_devices(mock_scanner: AsyncMock, mock_sleep: AsyncMock) -> None:
|
|
33
33
|
mock_scanner.return_value.__aenter__.return_value.discovered_devices_and_advertisement_data = {
|
|
34
34
|
m.address: (m, TEST_MUG_ADVERTISEMENT) for m in EXAMPLE_MUGS
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
assert len(
|
|
38
|
-
|
|
39
|
-
assert len(
|
|
40
|
-
device_1, advertisement_1 =
|
|
36
|
+
devices = await discover_devices()
|
|
37
|
+
assert len(devices) == 2
|
|
38
|
+
devices = await discover_devices(mac="32:36:a5:be:88:cb")
|
|
39
|
+
assert len(devices) == 1
|
|
40
|
+
device_1, advertisement_1 = devices[0]
|
|
41
41
|
assert device_1.address == "32:36:a5:be:88:cb"
|
|
42
42
|
mock_sleep.assert_called_with(5)
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
@patch("asyncio.sleep")
|
|
46
46
|
@patch("ember_mug.scanner.BleakScanner")
|
|
47
|
-
async def
|
|
47
|
+
async def test_find_device(mock_scanner: AsyncMock, mock_sleep: AsyncMock) -> None:
|
|
48
48
|
mock_data_iterator = MagicMock()
|
|
49
49
|
mock_data_iterator().__aiter__.return_value = [(m, TEST_MUG_ADVERTISEMENT) for m in EXAMPLE_MUGS]
|
|
50
50
|
mock_scanner.return_value.__aenter__.return_value.advertisement_data = mock_data_iterator
|
|
51
51
|
|
|
52
52
|
# Without filter
|
|
53
|
-
|
|
54
|
-
assert
|
|
55
|
-
assert
|
|
56
|
-
assert
|
|
53
|
+
device, advertisement = await find_device()
|
|
54
|
+
assert device is not None
|
|
55
|
+
assert device.name == "Ember Ceramic Mug"
|
|
56
|
+
assert device.address == MUG_1.address
|
|
57
57
|
|
|
58
58
|
# With Filter
|
|
59
|
-
|
|
60
|
-
assert
|
|
61
|
-
assert
|
|
62
|
-
assert
|
|
59
|
+
device, advertisement = await find_device(mac=MUG_2.address)
|
|
60
|
+
assert device is not None
|
|
61
|
+
assert device.name == "Ember Ceramic Mug"
|
|
62
|
+
assert device.address == MUG_2.address
|
|
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
|
|
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
|
|
File without changes
|