python-ember-mug 0.7.0b4__tar.gz → 0.7.0b6__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.
Files changed (29) hide show
  1. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/PKG-INFO +1 -1
  2. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/__init__.py +1 -1
  3. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/cli/commands.py +18 -4
  4. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/consts.py +1 -0
  5. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/data.py +8 -2
  6. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/mug.py +21 -11
  7. python_ember_mug-0.7.0b6/ember_mug/utils.py +100 -0
  8. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/pyproject.toml +1 -1
  9. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_consts.py +11 -1
  10. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_mug_data.py +1 -0
  11. python_ember_mug-0.7.0b6/tests/test_utils.py +70 -0
  12. python_ember_mug-0.7.0b4/ember_mug/utils.py +0 -62
  13. python_ember_mug-0.7.0b4/tests/test_utils.py +0 -32
  14. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/LICENSE +0 -0
  15. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/README.md +0 -0
  16. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/__main__.py +0 -0
  17. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/cli/__init__.py +0 -0
  18. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/cli/helpers.py +0 -0
  19. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/formatting.py +0 -0
  20. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/ember_mug/scanner.py +0 -0
  21. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/__init__.py +0 -0
  22. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/cli/__init__.py +0 -0
  23. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/cli/test_commands.py +0 -0
  24. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/cli/test_helpers.py +0 -0
  25. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/conftest.py +0 -0
  26. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_connection.py +0 -0
  27. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_data.py +0 -0
  28. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_formatting.py +0 -0
  29. {python_ember_mug-0.7.0b4 → python_ember_mug-0.7.0b6}/tests/test_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-ember-mug
3
- Version: 0.7.0b4
3
+ Version: 0.7.0b6
4
4
  Summary: Python Library for Ember Mugs.
5
5
  Home-page: https://github.com/sopelj/python-ember-mug
6
6
  License: MIT
@@ -5,4 +5,4 @@ __all__ = ('EmberMug',)
5
5
 
6
6
  __author__ = """Jesse Sopel"""
7
7
  __email__ = 'jesse.sopel@gmail.com'
8
- __version__ = '0.7.0b4'
8
+ __version__ = '0.7.0b6'
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING
12
12
 
13
13
  from bleak import BleakError
14
14
 
15
- from ..consts import ATTR_LABELS, EXTRA_ATTRS
15
+ from ..consts import ATTR_LABELS, EXTRA_ATTRS, VolumeLevel
16
16
  from ..data import Colour
17
17
  from ..mug import EmberMug
18
18
  from ..scanner import discover_mugs, find_mug
@@ -103,7 +103,11 @@ async def get_mug_value(args: Namespace) -> None:
103
103
  attributes = [a.replace('-', '_') for a in args.attributes]
104
104
  async with mug.connection(adapter=args.adapter):
105
105
  for attr in attributes:
106
- value = await getattr(mug, f'get_{attr}')()
106
+ try:
107
+ value = await getattr(mug, f'get_{attr}')()
108
+ except NotImplementedError as e:
109
+ print(e)
110
+ sys.exit(1)
107
111
  setattr(mug.data, attr, value)
108
112
  data[attr] = value
109
113
  if args.raw:
@@ -114,7 +118,7 @@ async def get_mug_value(args: Namespace) -> None:
114
118
 
115
119
  async def set_mug_value(args: Namespace) -> None:
116
120
  """Set one or more values on the mug."""
117
- attrs = ('name', 'target_temp', 'temperature_unit', 'led_colour')
121
+ attrs = ('name', 'target_temp', 'temperature_unit', 'led_colour', 'volume_level')
118
122
  values = [(attr, value) for attr in attrs if (value := getattr(args, attr))]
119
123
  if not values:
120
124
  print('Please specify at least one attribute and value to set.')
@@ -127,7 +131,11 @@ async def set_mug_value(args: Namespace) -> None:
127
131
  for attr, value in values:
128
132
  method = getattr(mug, f'set_{attr.replace("-", "_")}')
129
133
  print(f'Setting {attr} to {value}')
130
- await method(value)
134
+ try:
135
+ await method(value)
136
+ except NotImplementedError as e:
137
+ print(e)
138
+ sys.exit(1)
131
139
 
132
140
 
133
141
  def colour_type(value: str) -> Colour:
@@ -205,6 +213,12 @@ class EmberMugCli:
205
213
  set_parser.add_argument('--target-temp', help='Target Temperature', type=float, required=False)
206
214
  set_parser.add_argument('--temperature-unit', help='Temperature Unit', choices=['C', 'F'], required=False)
207
215
  set_parser.add_argument('--led-colour', help='LED Colour', type=colour_type, required=False)
216
+ set_parser.add_argument(
217
+ '--volume-level',
218
+ help='Volume Level',
219
+ choices=[v.value for v in VolumeLevel],
220
+ required=False,
221
+ )
208
222
 
209
223
  async def run(self) -> None:
210
224
  """Run the specified command based on subparser."""
@@ -205,6 +205,7 @@ EXTRA_ATTRS = {'dsk', 'udsk', 'battery_voltage', 'date_time_zone'}
205
205
 
206
206
  # Validation
207
207
  MUG_NAME_REGEX = re.compile(r"^[A-Za-z0-9,.\[\]#()!\"\';:|\-_+<>%= ]{1,16}$")
208
+ MUG_NAME_PATTERN = MUG_NAME_REGEX.pattern
208
209
  MAC_ADDRESS_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
209
210
 
210
211
  IS_LINUX = platform.system() == "Linux"
@@ -155,6 +155,11 @@ class Model:
155
155
  return INITIAL_ATTRS - EXTRA_ATTRS
156
156
  return INITIAL_ATTRS
157
157
 
158
+ @cached_property
159
+ def all_attributes(self) -> set[str]:
160
+ """All attributes."""
161
+ return self.initial_attributes | self.update_attributes
162
+
158
163
  @cached_property
159
164
  def update_attributes(self) -> set[str]:
160
165
  """Attributes to update based on model and extra."""
@@ -279,8 +284,9 @@ class MugData:
279
284
  data = {k: asdict(v) if is_dataclass(v) else v for k, v in asdict(self).items()}
280
285
  data.update(
281
286
  {
282
- f'{attr}_display': getattr(self, f'{attr}_display')
283
- for attr in ('led_colour', 'liquid_state', 'liquid_level', 'current_temp', 'target_temp')
287
+ f'{attr}_display': getattr(self, f'{attr}_display', None)
288
+ for attr in self.model.all_attributes
289
+ if hasattr(self, f'{attr}_display')
284
290
  },
285
291
  )
286
292
  return data
@@ -31,8 +31,8 @@ from .utils import (
31
31
  bytes_to_big_int,
32
32
  bytes_to_little_int,
33
33
  decode_byte_string,
34
+ discover_services,
34
35
  encode_byte_string,
35
- log_services,
36
36
  temp_from_bytes,
37
37
  )
38
38
 
@@ -69,6 +69,10 @@ class EmberMug:
69
69
  self._latest_events: dict[int, float] = {}
70
70
  self._client_kwargs: dict[str, str] = {}
71
71
 
72
+ # Just shortcuts, the value doesn't change once initialized
73
+ self.is_travel_mug = self.data.model.is_travel_mug
74
+ self.is_cup = self.data.model.is_cup
75
+
72
76
  logger.debug("New mug connection initialized.")
73
77
  self.set_client_options(**kwargs)
74
78
 
@@ -105,7 +109,7 @@ class EmberMug:
105
109
  ble_device_callback=lambda: self.device,
106
110
  )
107
111
  if self.debug is True:
108
- log_services(client.services)
112
+ await discover_services(client)
109
113
  self._expected_disconnect = False
110
114
  except (asyncio.TimeoutError, BleakError) as error:
111
115
  logger.error("%s: Failed to connect to the mug: %s", self.device, error)
@@ -195,10 +199,14 @@ class EmberMug:
195
199
 
196
200
  async def get_led_colour(self) -> Colour:
197
201
  """Get RGBA colours from mug gatt."""
202
+ if self.is_travel_mug is True:
203
+ raise NotImplementedError('The Travel Mug does not have an LED colour attribute')
198
204
  return Colour.from_bytes(await self._read(MugCharacteristic.LED))
199
205
 
200
206
  async def set_led_colour(self, colour: Colour) -> None:
201
207
  """Set new target temp for mug."""
208
+ if self.is_travel_mug is True:
209
+ raise NotImplementedError('The Travel Mug does not have an LED colour attribute')
202
210
  colour = Colour(*colour[:3], 255) # It always expects 255 for alpha
203
211
  await self._write(MugCharacteristic.LED, colour.as_bytearray())
204
212
  self.data.led_colour = colour
@@ -226,13 +234,9 @@ class EmberMug:
226
234
 
227
235
  async def get_volume_level(self) -> VolumeLevel | None:
228
236
  """Get volume level from mug gatt."""
229
- try:
230
- volume_bytes = await self._read(MugCharacteristic.VOLUME)
231
- except BleakError as e:
232
- if not self.data.model.is_travel_mug:
233
- raise NotImplementedError('Ony the travel mug has a volume')
234
- logger.error('Failed to fetch volume attribute: %s', e)
235
- return None
237
+ if self.is_travel_mug is False:
238
+ raise NotImplementedError('The Mug and Cup do not have a volume level attribute')
239
+ volume_bytes = await self._read(MugCharacteristic.VOLUME)
236
240
  volume_int = bytes_to_little_int(volume_bytes)
237
241
  return VolumeLevel.from_state(volume_int)
238
242
 
@@ -240,8 +244,10 @@ class EmberMug:
240
244
  """Set volume_level on Travel Mug."""
241
245
  if volume not in (0, 1, 2):
242
246
  raise ValueError('Volume level must be between 0 and 2 inclusively')
247
+ if self.is_travel_mug is False:
248
+ raise NotImplementedError('The Mug and Cup do not have a volume level attribute')
243
249
  volume_level = volume if isinstance(volume, VolumeLevel) else VolumeLevel.from_state(volume)
244
- await self._write(MugCharacteristic.VOLUME, bytearray(bytearray([volume_level.state])))
250
+ await self._write(MugCharacteristic.VOLUME, bytearray([volume_level.state]))
245
251
  self.data.volume_level = volume_level
246
252
 
247
253
  async def get_liquid_state(self) -> LiquidState:
@@ -252,13 +258,17 @@ class EmberMug:
252
258
 
253
259
  async def get_name(self) -> str:
254
260
  """Get mug name from gatt."""
261
+ if self.is_cup is True:
262
+ raise NotImplementedError('The Cup does not have a name attribute')
255
263
  name_bytes: bytearray = await self._read(MugCharacteristic.MUG_NAME)
256
264
  return bytes(name_bytes).decode("utf8")
257
265
 
258
266
  async def set_name(self, name: str) -> None:
259
267
  """Assign new name to mug."""
260
268
  if MUG_NAME_REGEX.match(name) is None:
261
- raise ValueError('Name cannot contain any special characters')
269
+ raise ValueError('Name cannot contain any special characters and must be 16 characters or less')
270
+ if self.is_cup is True:
271
+ raise NotImplementedError('The Cup does not have a name attribute')
262
272
  await self._write(MugCharacteristic.MUG_NAME, bytearray(name.encode("utf8")))
263
273
  self.data.name = name
264
274
 
@@ -0,0 +1,100 @@
1
+ """Helpful utils for processing mug data."""
2
+ from __future__ import annotations
3
+
4
+ import base64
5
+ import contextlib
6
+ import logging
7
+ import re
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from bleak import BleakError
11
+
12
+ if TYPE_CHECKING:
13
+ from bleak import BleakClient
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def decode_byte_string(data: bytes | bytearray) -> str:
19
+ """Convert bytes to text as Ember expects."""
20
+ if not data:
21
+ return ''
22
+ with contextlib.suppress(ValueError):
23
+ b64_as_str = base64.encodebytes(data).decode("utf-8")
24
+ return re.sub("[\r\n]", "", b64_as_str)
25
+ logger.warning('Failed to decode bytes "%s". Forcing to string.', data)
26
+ return str(data)
27
+
28
+
29
+ def encode_byte_string(data: str) -> bytes:
30
+ """Encode string from Ember Mug."""
31
+ return re.sub(b"[\r\n]", b"", base64.encodebytes(data.encode("utf8")))
32
+
33
+
34
+ def bytes_to_little_int(data: bytearray | bytes) -> int:
35
+ """Convert bytes to little int."""
36
+ return int.from_bytes(data, byteorder="little", signed=False)
37
+
38
+
39
+ def bytes_to_big_int(data: bytearray | bytes) -> int:
40
+ """Convert bytes to big int."""
41
+ return int.from_bytes(data, byteorder="big")
42
+
43
+
44
+ def temp_from_bytes(temp_bytes: bytearray, metric: bool = True) -> float:
45
+ """Get temperature from bytearray and convert to Fahrenheit if needed."""
46
+ temp = float(bytes_to_little_int(temp_bytes)) * 0.01
47
+ if metric is False:
48
+ # Convert to fahrenheit
49
+ temp = (temp * 9 / 5) + 32
50
+ return round(temp, 2)
51
+
52
+
53
+ async def discover_services(client: BleakClient) -> dict[str, Any]:
54
+ """Log all services and all values for debugging/development."""
55
+ logger.info("Logging all services that were discovered")
56
+ services: dict[str, Any] = {}
57
+ for service in client.services:
58
+ logger.debug("[Service] %s: %s", service.uuid, service.description)
59
+ characteristics: dict[str, Any] = {}
60
+ services[service.uuid] = {
61
+ 'uuid': service.uuid,
62
+ 'characteristics': characteristics,
63
+ }
64
+ for characteristic in service.characteristics:
65
+ value: bytes | BleakError | None = None
66
+ if "read" in characteristic.properties:
67
+ try:
68
+ value = bytes(await client.read_gatt_char(characteristic.uuid))
69
+ except BleakError as e:
70
+ value = e
71
+ logger.debug(
72
+ "\t[Characteristic] %s: %s | Description: %s | Value: '%s'",
73
+ characteristic.uuid,
74
+ ",".join(characteristic.properties),
75
+ characteristic.description,
76
+ value,
77
+ )
78
+ descriptors: list[dict[str, Any]] = []
79
+ characteristics[characteristic.uuid] = {
80
+ 'uuid': characteristic.uuid,
81
+ 'properties': characteristic.properties,
82
+ 'value': value,
83
+ 'descriptors': descriptors,
84
+ }
85
+ for descriptor in characteristic.descriptors:
86
+ value = bytes(await client.read_gatt_descriptor(descriptor.handle))
87
+ logger.debug(
88
+ "\t\t[Descriptor] %s: Handle: %s | Value: '%s'",
89
+ descriptor.uuid,
90
+ descriptor.handle,
91
+ value,
92
+ )
93
+ descriptors.append(
94
+ {
95
+ 'uuid': descriptor.uuid,
96
+ 'handle': descriptor.handle,
97
+ 'value': value,
98
+ },
99
+ )
100
+ return services
@@ -1,7 +1,7 @@
1
1
  [tool]
2
2
  [tool.poetry]
3
3
  name = "python-ember-mug"
4
- version = "0.7.0b4"
4
+ version = "0.7.0b6"
5
5
  homepage = "https://github.com/sopelj/python-ember-mug"
6
6
  description = "Python Library for Ember Mugs."
7
7
  authors = ["Jesse Sopel <jesse.sopel@gmail.com>"]
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  from uuid import UUID
5
5
 
6
- from ember_mug.consts import LiquidState, MugCharacteristic
6
+ from ember_mug.consts import LiquidState, MugCharacteristic, VolumeLevel
7
7
 
8
8
 
9
9
  def test_mug_uuids() -> None:
@@ -20,3 +20,13 @@ def test_liquid_state() -> None:
20
20
  assert LiquidState(5).label == "Heating"
21
21
  assert LiquidState(6).label == "Perfect"
22
22
  assert LiquidState(7).label == "Warm (No control)"
23
+
24
+
25
+ def test_volume_level() -> None:
26
+ assert VolumeLevel.from_state(0) == VolumeLevel.LOW
27
+ assert VolumeLevel.from_state(1) == VolumeLevel.MEDIUM
28
+ assert VolumeLevel.from_state(2) == VolumeLevel.HIGH
29
+
30
+ assert VolumeLevel.HIGH.state == 2
31
+ assert VolumeLevel.MEDIUM.state == 1
32
+ assert VolumeLevel.LOW.state == 0
@@ -92,6 +92,7 @@ def test_mug_dict(mug_data: MugData) -> None:
92
92
  'liquid_state': LiquidState.UNKNOWN,
93
93
  'liquid_state_display': 'Unknown',
94
94
  'meta': {'mug_id': 'test_id', 'serial_number': 'serial number'},
95
+ 'meta_display': 'Serial Number: serial number',
95
96
  'name': '',
96
97
  'target_temp': 0.0,
97
98
  'target_temp_display': '0.00°C',
@@ -0,0 +1,70 @@
1
+ """Tests for `ember_mug.utils`."""
2
+ from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
3
+
4
+ from ember_mug.utils import (
5
+ bytes_to_big_int,
6
+ bytes_to_little_int,
7
+ decode_byte_string,
8
+ discover_services,
9
+ encode_byte_string,
10
+ temp_from_bytes,
11
+ )
12
+
13
+
14
+ def test_bytes_to_little_int() -> None:
15
+ assert bytes_to_little_int(b'\x05') == 5
16
+
17
+
18
+ def test_bytes_to_big_int() -> None:
19
+ assert bytes_to_big_int(b'\x01\xc2') == 450
20
+
21
+
22
+ def test_temp_from_bytes() -> None:
23
+ raw_data = bytearray(b'\xcd\x15') # int: 5581
24
+ assert temp_from_bytes(raw_data) == 55.81
25
+ assert temp_from_bytes(raw_data, metric=False) == 132.46
26
+
27
+
28
+ def test_decode_byte_string() -> None:
29
+ assert decode_byte_string(b'abcd12345') == 'YWJjZDEyMzQ1'
30
+ assert decode_byte_string(b'') == ''
31
+
32
+
33
+ def test_encode_byte_string() -> None:
34
+ assert encode_byte_string('abcd12345') == b'YWJjZDEyMzQ1'
35
+
36
+
37
+ @patch('ember_mug.utils.logger')
38
+ async def test_discover_services(read_gatt_descriptor: Mock) -> None:
39
+ mock_descriptor = MagicMock(uuid='test-desc', handle=2)
40
+ mock_characteristic = MagicMock(
41
+ uuid='char-abc',
42
+ description='test char',
43
+ properties=['read'],
44
+ descriptors=[mock_descriptor],
45
+ )
46
+ mock_service = MagicMock(
47
+ uuid='service-abc',
48
+ description='test service',
49
+ characteristics=[mock_characteristic],
50
+ )
51
+ client = AsyncMock(services=[mock_service])
52
+ client.read_gatt_char = AsyncMock(return_value=bytearray(b'test char'))
53
+ client.read_gatt_descriptor = AsyncMock(return_value=bytearray(b'test descriptor'))
54
+ await discover_services(client)
55
+ read_gatt_descriptor.assert_has_calls(
56
+ [
57
+ call.info('Logging all services that were discovered'),
58
+ call.debug('[Service] %s: %s', 'service-abc', 'test service'),
59
+ call.debug(
60
+ "\t[Characteristic] %s: %s | Description: %s | Value: '%s'",
61
+ 'char-abc',
62
+ 'read',
63
+ 'test char',
64
+ b'test char',
65
+ ),
66
+ call.debug("\t\t[Descriptor] %s: Handle: %s | Value: '%s'", 'test-desc', 2, b'test descriptor'),
67
+ ],
68
+ )
69
+ client.read_gatt_char.assert_called_once_with('char-abc')
70
+ client.read_gatt_descriptor.assert_called_once_with(2)
@@ -1,62 +0,0 @@
1
- """Helpful utils for processing mug data."""
2
- from __future__ import annotations
3
-
4
- import base64
5
- import contextlib
6
- import logging
7
- import re
8
- from typing import TYPE_CHECKING
9
-
10
- if TYPE_CHECKING:
11
- from bleak import BleakGATTServiceCollection
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- def decode_byte_string(data: bytes | bytearray) -> str:
17
- """Convert bytes to text as Ember expects."""
18
- if not data:
19
- return ''
20
- with contextlib.suppress(ValueError):
21
- b64_as_str = base64.encodebytes(data).decode("utf-8")
22
- return re.sub("[\r\n]", "", b64_as_str)
23
- logger.warning('Failed to decode bytes "%s". Forcing to string.', data)
24
- return str(data)
25
-
26
-
27
- def encode_byte_string(data: str) -> bytes:
28
- """Encode string from Ember Mug."""
29
- return re.sub(b"[\r\n]", b"", base64.encodebytes(data.encode("utf8")))
30
-
31
-
32
- def bytes_to_little_int(data: bytearray | bytes) -> int:
33
- """Convert bytes to little int."""
34
- return int.from_bytes(data, byteorder="little", signed=False)
35
-
36
-
37
- def bytes_to_big_int(data: bytearray | bytes) -> int:
38
- """Convert bytes to big int."""
39
- return int.from_bytes(data, byteorder="big")
40
-
41
-
42
- def temp_from_bytes(temp_bytes: bytearray, metric: bool = True) -> float:
43
- """Get temperature from bytearray and convert to Fahrenheit if needed."""
44
- temp = float(bytes_to_little_int(temp_bytes)) * 0.01
45
- if metric is False:
46
- # Convert to fahrenheit
47
- temp = (temp * 9 / 5) + 32
48
- return round(temp, 2)
49
-
50
-
51
- def log_services(services: BleakGATTServiceCollection) -> None:
52
- """Log services for debugging."""
53
- logger.debug("Logging all services that were discovered")
54
- for service in services:
55
- logger.debug(
56
- "Service '%s' (UUID: %s) has the characteristics:\n %s",
57
- service.description,
58
- service.uuid,
59
- "\n".join(
60
- f'{characteristic.uuid}: {characteristic.description}' for characteristic in service.characteristics
61
- ),
62
- )
@@ -1,32 +0,0 @@
1
- """Tests for `ember_mug.utils`."""
2
-
3
- from ember_mug.utils import (
4
- bytes_to_big_int,
5
- bytes_to_little_int,
6
- decode_byte_string,
7
- encode_byte_string,
8
- temp_from_bytes,
9
- )
10
-
11
-
12
- def test_bytes_to_little_int() -> None:
13
- assert bytes_to_little_int(b'\x05') == 5
14
-
15
-
16
- def test_bytes_to_big_int() -> None:
17
- assert bytes_to_big_int(b'\x01\xc2') == 450
18
-
19
-
20
- def test_temp_from_bytes() -> None:
21
- raw_data = bytearray(b'\xcd\x15') # int: 5581
22
- assert temp_from_bytes(raw_data) == 55.81
23
- assert temp_from_bytes(raw_data, metric=False) == 132.46
24
-
25
-
26
- def test_decode_byte_string() -> None:
27
- assert decode_byte_string(b'abcd12345') == 'YWJjZDEyMzQ1'
28
- assert decode_byte_string(b'') == ''
29
-
30
-
31
- def test_encode_byte_string() -> None:
32
- assert encode_byte_string('abcd12345') == b'YWJjZDEyMzQ1'