diff --git a/src/demetriek/__init__.py b/src/demetriek/__init__.py index d14c252d..b4c3b3b5 100644 --- a/src/demetriek/__init__.py +++ b/src/demetriek/__init__.py @@ -33,6 +33,7 @@ Display, DisplayScreensaver, DisplayScreensaverModes, + DisplayScreensaverScreenOff, DisplayScreensaverTimeBased, DisplayScreensaverWhenDark, Goal, @@ -63,6 +64,7 @@ "Display", "DisplayScreensaver", "DisplayScreensaverModes", + "DisplayScreensaverScreenOff", "DisplayScreensaverTimeBased", "DisplayScreensaverWhenDark", "DisplayType", diff --git a/src/demetriek/const.py b/src/demetriek/const.py index d21feeef..cab00c1d 100644 --- a/src/demetriek/const.py +++ b/src/demetriek/const.py @@ -136,6 +136,7 @@ class NotificationType(str, Enum): class ScreensaverMode(str, Enum): """Enum holding the available screensaver modes.""" + SCREEN_OFF = "screen_off" TIME_BASED = "time_based" WHEN_DARK = "when_dark" diff --git a/src/demetriek/models.py b/src/demetriek/models.py index 7a6f2b8e..960027cb 100644 --- a/src/demetriek/models.py +++ b/src/demetriek/models.py @@ -71,6 +71,13 @@ class DisplayScreensaverTimeBased(DataClassORJSONMixin): local_start_time: time | None = None +@dataclass(kw_only=True) +class DisplayScreensaverScreenOff(DataClassORJSONMixin): + """Object holding the screen off screensaver mode of an LaMetric device.""" + + enabled: bool + + @dataclass(kw_only=True) class DisplayScreensaverWhenDark(DataClassORJSONMixin): """Object holding the when dark screensaver mode of an LaMetric device.""" @@ -80,10 +87,16 @@ class DisplayScreensaverWhenDark(DataClassORJSONMixin): @dataclass(kw_only=True) class DisplayScreensaverModes(DataClassORJSONMixin): - """Object holding the screensaver modes of an LaMetric device.""" + """Object holding the screensaver modes of an LaMetric device. + + Which modes a device reports depends on the hardware. A TIME reports + when_dark, a SKY reports screen_off, and both report time_based, so + every mode is optional. + """ - time_based: DisplayScreensaverTimeBased - when_dark: DisplayScreensaverWhenDark + screen_off: DisplayScreensaverScreenOff | None = None + time_based: DisplayScreensaverTimeBased | None = None + when_dark: DisplayScreensaverWhenDark | None = None @dataclass(kw_only=True) diff --git a/tests/__snapshots__/test_device.ambr b/tests/__snapshots__/test_device.ambr index 09c1d323..1b069bd2 100644 --- a/tests/__snapshots__/test_device.ambr +++ b/tests/__snapshots__/test_device.ambr @@ -39,6 +39,7 @@ 'screensaver': dict({ 'enabled': False, 'modes': dict({ + 'screen_off': None, 'time_based': dict({ 'enabled': True, 'end_time': None, @@ -113,6 +114,7 @@ 'screensaver': dict({ 'enabled': False, 'modes': dict({ + 'screen_off': None, 'time_based': dict({ 'enabled': True, 'end_time': None, @@ -187,6 +189,7 @@ 'screensaver': dict({ 'enabled': True, 'modes': dict({ + 'screen_off': None, 'time_based': dict({ 'enabled': False, 'end_time': None, @@ -223,3 +226,80 @@ }), }) # --- +# name: test_get_device[device_sky.json] + dict({ + 'audio': dict({ + 'available': True, + 'volume': 100, + 'volume_limit': dict({ + 'range_max': 100, + 'range_min': 0, + }), + 'volume_range': dict({ + 'range_max': 100, + 'range_min': 0, + }), + }), + 'bluetooth': dict({ + 'active': True, + 'address': 'AA:BB:CC:DD:EE:FF', + 'available': True, + 'discoverable': True, + 'name': 'SKY0123', + 'pairable': False, + }), + 'device_id': '12345', + 'display': dict({ + 'brightness': 66, + 'brightness_limit': dict({ + 'range_max': 100, + 'range_min': 2, + }), + 'brightness_mode': , + 'brightness_range': dict({ + 'range_max': 100, + 'range_min': 0, + }), + 'display_type': , + 'height': 8, + 'on': True, + 'screensaver': dict({ + 'enabled': True, + 'modes': dict({ + 'screen_off': dict({ + 'enabled': False, + }), + 'time_based': dict({ + 'enabled': False, + 'end_time': None, + 'local_end_time': None, + 'local_start_time': None, + 'start_time': None, + }), + 'when_dark': None, + }), + 'widget': '', + }), + 'width': 64, + }), + 'mode': , + 'model': 'sa5', + 'name': "spyfly's LaMetric SKY", + 'os_version': '3.0.13', + 'serial_number': 'SA52100000123TBNC', + 'update': dict({ + 'version': '3.2.1', + }), + 'wifi': dict({ + 'active': True, + 'available': True, + 'encryption': 'WPA', + 'ip': IPv4Address('127.0.0.1'), + 'mac': 'AA:BB:CC:DD:EE:FF', + 'mode': , + 'netmask': '255.255.255.0', + 'rssi': 58, + 'ssid': 'IoT', + }), + }) +# --- diff --git a/tests/fixtures/device_sky.json b/tests/fixtures/device_sky.json new file mode 100644 index 00000000..e4d2d250 --- /dev/null +++ b/tests/fixtures/device_sky.json @@ -0,0 +1,74 @@ +{ + "audio": { + "volume": 100, + "volume_limit": { + "max": 100, + "min": 0 + }, + "volume_range": { + "max": 100, + "min": 0 + } + }, + "bluetooth": { + "active": true, + "address": "AA:BB:CC:DD:EE:FF", + "available": true, + "discoverable": true, + "low_energy": { + "active": true, + "advertising": true, + "connectable": true + }, + "name": "SKY0123", + "pairable": false + }, + "display": { + "brightness": 66, + "brightness_limit": { + "max": 100, + "min": 2 + }, + "brightness_mode": "manual", + "brightness_range": { + "max": 100, + "min": 0 + }, + "height": 8, + "on": true, + "screensaver": { + "enabled": true, + "modes": { + "screen_off": { + "enabled": false + }, + "time_based": { + "enabled": false + } + }, + "widget": "" + }, + "type": "mixed", + "width": 64 + }, + "id": "12345", + "mode": "manual", + "model": "sa5", + "name": "spyfly's LaMetric SKY", + "os_version": "3.0.13", + "serial_number": "SA52100000123TBNC", + "update_available": { + "version": "3.2.1" + }, + "wifi": { + "active": true, + "address": "AA:BB:CC:DD:EE:FF", + "available": true, + "encryption": "WPA", + "essid": "IoT", + "ip": "127.0.0.1", + "mode": "dhcp", + "netmask": "255.255.255.0", + "strength": 58 + } +} diff --git a/tests/test_device.py b/tests/test_device.py index 159e9189..415db9e2 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -35,6 +35,7 @@ "device.json", "device2.json", "device3.json", + "device_sky.json", ], ) async def test_get_device( @@ -186,3 +187,29 @@ async def test_notification(aresponses: ResponsesMockServer) -> None: assert notification.notification_type is NotificationType.EXTERNAL assert notification.priority is NotificationPriority.INFO assert notification.model.frames == [Simple(text="fixture")] + + +async def test_get_device_sky_screensaver(aresponses: ResponsesMockServer) -> None: + """Test the screensaver modes a SKY reports differ from a TIME.""" + aresponses.add( + "127.0.0.2:4343", + "/api/v2/device", + "GET", + aresponses.Response( + status=200, + headers={"Content-Type": "application/json"}, + text=load_fixture("device_sky.json"), + ), + ) + async with aiohttp.ClientSession() as session: + demetriek = LaMetricDevice(host="127.0.0.2", api_key="abc", session=session) + device = await demetriek.device() + + assert (screensaver := device.display.screensaver) + assert (modes := screensaver.modes) + # A SKY reports screen_off where a TIME reports when_dark. + assert modes.screen_off is not None + assert modes.screen_off.enabled is False + assert modes.when_dark is None + assert modes.time_based is not None + assert modes.time_based.enabled is False diff --git a/tests/test_display.py b/tests/test_display.py index 8e233eea..de6d3fb7 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -41,16 +41,20 @@ async def test_get_display(aresponses: ResponsesMockServer) -> None: assert display.height == 8 assert display.display_type is DisplayType.MIXED assert display.on is True - assert display.screensaver - assert display.screensaver.enabled is False - assert display.screensaver.widget == "08b8eac21074f8f7e5a29f2855ba8060" - assert display.screensaver.modes - assert display.screensaver.modes.when_dark.enabled is False - assert display.screensaver.modes.time_based.enabled is True - assert display.screensaver.modes.time_based.start_time == time(0, 0, 39) - assert display.screensaver.modes.time_based.end_time is None - assert display.screensaver.modes.time_based.local_start_time == time(1, 0, 39) - assert display.screensaver.modes.time_based.local_end_time is None + assert (screensaver := display.screensaver) + assert screensaver.enabled is False + assert screensaver.widget == "08b8eac21074f8f7e5a29f2855ba8060" + assert (modes := screensaver.modes) + assert (when_dark := modes.when_dark) + assert when_dark.enabled is False + assert (time_based := modes.time_based) + assert time_based.enabled is True + assert time_based.start_time == time(0, 0, 39) + assert time_based.end_time is None + assert time_based.local_start_time == time(1, 0, 39) + assert time_based.local_end_time is None + # A TIME reports no screen_off mode. + assert modes.screen_off is None async def test_set_display(aresponses: ResponsesMockServer) -> None: @@ -130,15 +134,17 @@ async def response_handler(request: aiohttp.ClientResponse) -> Response: screensaver_end_time=time(7, 0, 0), ) - assert display.screensaver - assert display.screensaver.modes - assert display.screensaver.modes.time_based.enabled is True - assert display.screensaver.modes.time_based.start_time == time(23, 0, 0) - assert display.screensaver.modes.time_based.end_time == time(7, 0, 0) - assert display.screensaver.modes.time_based.local_start_time == time(1, 0, 0) - assert display.screensaver.modes.time_based.local_end_time == time(9, 0, 0) + assert (screensaver := display.screensaver) + assert (modes := screensaver.modes) + assert (time_based := modes.time_based) + assert time_based.enabled is True + assert time_based.start_time == time(23, 0, 0) + assert time_based.end_time == time(7, 0, 0) + assert time_based.local_start_time == time(1, 0, 0) + assert time_based.local_end_time == time(9, 0, 0) # Enabling one mode disables the other on the device. - assert display.screensaver.modes.when_dark.enabled is False + assert (when_dark := modes.when_dark) + assert when_dark.enabled is False async def test_set_display_screensaver_mode_without_params(