Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/demetriek/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Display,
DisplayScreensaver,
DisplayScreensaverModes,
DisplayScreensaverScreenOff,
DisplayScreensaverTimeBased,
DisplayScreensaverWhenDark,
Goal,
Expand Down Expand Up @@ -63,6 +64,7 @@
"Display",
"DisplayScreensaver",
"DisplayScreensaverModes",
"DisplayScreensaverScreenOff",
"DisplayScreensaverTimeBased",
"DisplayScreensaverWhenDark",
"DisplayType",
Expand Down
1 change: 1 addition & 0 deletions src/demetriek/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
19 changes: 16 additions & 3 deletions src/demetriek/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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)
Expand Down
80 changes: 80 additions & 0 deletions tests/__snapshots__/test_device.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
'screensaver': dict({
'enabled': False,
'modes': dict({
'screen_off': None,
'time_based': dict({
'enabled': True,
'end_time': None,
Expand Down Expand Up @@ -113,6 +114,7 @@
'screensaver': dict({
'enabled': False,
'modes': dict({
'screen_off': None,
'time_based': dict({
'enabled': True,
'end_time': None,
Expand Down Expand Up @@ -187,6 +189,7 @@
'screensaver': dict({
'enabled': True,
'modes': dict({
'screen_off': None,
'time_based': dict({
'enabled': False,
'end_time': None,
Expand Down Expand Up @@ -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': <BrightnessMode.MANUAL: 'manual'>,
'brightness_range': dict({
'range_max': 100,
'range_min': 0,
}),
'display_type': <DisplayType.MIXED: 'mixed'>,
'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': <DeviceMode.MANUAL: 'manual'>,
'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': <WifiMode.DHCP: 'dhcp'>,
'netmask': '255.255.255.0',
'rssi': 58,
'ssid': 'IoT',
}),
})
# ---
74 changes: 74 additions & 0 deletions tests/fixtures/device_sky.json
Original file line number Diff line number Diff line change
@@ -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
}
}
27 changes: 27 additions & 0 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"device.json",
"device2.json",
"device3.json",
"device_sky.json",
],
)
async def test_get_device(
Expand Down Expand Up @@ -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
42 changes: 24 additions & 18 deletions tests/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down