Skip to content

Commit 9f96a47

Browse files
authored
Merge pull request #29 from PiotrMachowski/dev
Added dedicated methods and properties for media devices
2 parents a247ac8 + 39fa109 commit 9f96a47

19 files changed

+877
-1
lines changed

pysmartthings/capability.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,26 @@ class Attribute:
358358
Attribute.filter_status: "replace",
359359
Attribute.motion: "active",
360360
Attribute.mute: "muted",
361+
Attribute.playback_shuffle: "enabled",
361362
Attribute.presence: "present",
362363
Attribute.sound: "detected",
363364
Attribute.switch: "on",
364365
Attribute.tamper: "detected",
365366
Attribute.valve: "open",
366367
Attribute.water: "wet",
367368
}
369+
370+
ATTRIBUTE_OFF_VALUES = {
371+
Attribute.acceleration: "inactive",
372+
Attribute.contact: "closed",
373+
Attribute.filter_status: "normal",
374+
Attribute.motion: "inactive",
375+
Attribute.mute: "unmuted",
376+
Attribute.playback_shuffle: "disabled",
377+
Attribute.presence: "not present",
378+
Attribute.sound: "not detected",
379+
Attribute.switch: "off",
380+
Attribute.tamper: "clear",
381+
Attribute.valve: "closed",
382+
Attribute.water: "dry",
383+
}

pysmartthings/device.py

Lines changed: 290 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from .api import Api
1818
from .capability import (
19+
ATTRIBUTE_OFF_VALUES,
1920
ATTRIBUTE_ON_VALUES,
2021
Attribute,
2122
Capability,
@@ -52,6 +53,11 @@ def hex_to_hs(color_hex: str) -> (int, int):
5253
return round(hsv[0] * 100, 3), round(hsv[1] * 100, 3)
5354

5455

56+
def bool_to_value(attribute: str, value: bool) -> str:
57+
"""Convert bool value to ON/OFF value of given attribute."""
58+
return ATTRIBUTE_ON_VALUES[attribute] if value else ATTRIBUTE_OFF_VALUES[attribute]
59+
60+
5561
class Command:
5662
"""Define common commands."""
5763

@@ -78,6 +84,22 @@ class Command:
7884
set_thermostat_fan_mode = "setThermostatFanMode"
7985
set_thermostat_mode = "setThermostatMode"
8086
unlock = "unlock"
87+
mute = "mute"
88+
unmute = "unmute"
89+
set_volume = "setVolume"
90+
volume_up = "volumeUp"
91+
volume_down = "volumeDown"
92+
play = "play"
93+
pause = "pause"
94+
stop = "stop"
95+
fast_forward = "fastForward"
96+
rewind = "rewind"
97+
set_input_source = "setInputSource"
98+
set_playback_shuffle = "setPlaybackShuffle"
99+
set_playback_repeat_mode = "setPlaybackRepeatMode"
100+
set_tv_channel = "setTvChannel"
101+
channel_up = "channelUp"
102+
channel_down = "channelDown"
81103

82104

83105
class Device:
@@ -308,7 +330,7 @@ def switch(self) -> bool:
308330
@switch.setter
309331
def switch(self, value: bool):
310332
"""Set the value of the switch attribute."""
311-
status_value = ATTRIBUTE_ON_VALUES[Attribute.switch] if value else "off"
333+
status_value = bool_to_value(Attribute.switch, value)
312334
self.update_attribute_value(Attribute.switch, status_value)
313335

314336
@property
@@ -588,6 +610,97 @@ def three_axis(self) -> Optional[Tuple[int, int, int]]:
588610
"""Get the three axis attribute."""
589611
return self._attributes[Attribute.three_axis].value
590612

613+
@property
614+
def mute(self) -> bool:
615+
"""Get the mute attribute."""
616+
return self.is_on(Attribute.mute)
617+
618+
@mute.setter
619+
def mute(self, value: bool):
620+
"""Set the mute attribute."""
621+
status_value = bool_to_value(Attribute.mute, value)
622+
self.update_attribute_value(Attribute.mute, status_value)
623+
624+
@property
625+
def volume(self) -> int:
626+
"""Get the volume attribute."""
627+
return self._attributes[Attribute.volume].value
628+
629+
@volume.setter
630+
def volume(self, value: float):
631+
"""Set the volume attribute, scaled 0-100."""
632+
if not 0 <= value <= 100:
633+
raise ValueError("value must be scaled between 0-100.")
634+
self.update_attribute_value(Attribute.volume, value)
635+
636+
@property
637+
def playback_status(self) -> str:
638+
"""Get the playbackStatus attribute."""
639+
return self._attributes[Attribute.playback_status].value
640+
641+
@playback_status.setter
642+
def playback_status(self, value: str):
643+
"""Set the playbackStatus attribute."""
644+
self.update_attribute_value(Attribute.playback_status, value)
645+
646+
@property
647+
def input_source(self) -> str:
648+
"""Get the inputSource attribute."""
649+
return self._attributes[Attribute.input_source].value
650+
651+
@input_source.setter
652+
def input_source(self, value: str):
653+
"""Set the volume attribute."""
654+
if value not in self.supported_input_sources:
655+
raise ValueError("value must be supported.")
656+
self.update_attribute_value(Attribute.input_source, value)
657+
658+
@property
659+
def supported_input_sources(self) -> Sequence[str]:
660+
"""Get the supportedInputSources attribute."""
661+
value = self._attributes[Attribute.supported_input_sources].value
662+
if "value" in value:
663+
return value["value"]
664+
return value
665+
666+
@property
667+
def playback_shuffle(self) -> bool:
668+
"""Get the playbackShuffle attribute."""
669+
return self.is_on(Attribute.playback_shuffle)
670+
671+
@playback_shuffle.setter
672+
def playback_shuffle(self, value: bool):
673+
"""Set the playbackShuffle attribute."""
674+
status_value = bool_to_value(Attribute.playback_shuffle, value)
675+
self.update_attribute_value(Attribute.playback_shuffle, status_value)
676+
677+
@property
678+
def playback_repeat_mode(self) -> str:
679+
"""Get the playbackRepeatMode attribute."""
680+
return self._attributes[Attribute.playback_repeat_mode].value
681+
682+
@playback_repeat_mode.setter
683+
def playback_repeat_mode(self, value: str):
684+
"""Set the playbackRepeatMode attribute."""
685+
if value not in ["all", "off", "one"]:
686+
raise ValueError("value must be one of: all, off, one")
687+
self.update_attribute_value(Attribute.playback_repeat_mode, value)
688+
689+
@property
690+
def tv_channel(self) -> str:
691+
"""Get the tvChannel attribute."""
692+
return self._attributes[Attribute.tv_channel].value
693+
694+
@tv_channel.setter
695+
def tv_channel(self, value: str):
696+
"""Set the tvChannel attribute."""
697+
self.update_attribute_value(Attribute.tv_channel, value)
698+
699+
@property
700+
def media_title(self) -> bool:
701+
"""Get the trackDescription attribute."""
702+
return self._attributes["trackDescription"].value
703+
591704

592705
class DeviceStatus(DeviceStatusBase):
593706
"""Define the device status."""
@@ -1061,6 +1174,182 @@ async def set_air_flow_direction(
10611174
self.status.update_attribute_value(Attribute.air_flow_direction, direction)
10621175
return result
10631176

1177+
async def mute(
1178+
self, set_status: bool = False, *, component_id: str = "main"
1179+
) -> bool:
1180+
"""Call the mute command."""
1181+
result = await self.command(component_id, Capability.audio_mute, Command.mute)
1182+
if result and set_status:
1183+
self.status.mute = True
1184+
return result
1185+
1186+
async def unmute(
1187+
self, set_status: bool = False, *, component_id: str = "main"
1188+
) -> bool:
1189+
"""Call the unmute command."""
1190+
result = await self.command(component_id, Capability.audio_mute, Command.unmute)
1191+
if result and set_status:
1192+
self.status.mute = False
1193+
return result
1194+
1195+
async def set_volume(
1196+
self, volume: int, set_status: bool = False, *, component_id: str = "main"
1197+
) -> bool:
1198+
"""Call the setVolume command."""
1199+
result = await self.command(
1200+
component_id, Capability.audio_volume, Command.set_volume, [volume]
1201+
)
1202+
if result and set_status:
1203+
self.status.volume = volume
1204+
return result
1205+
1206+
async def volume_up(
1207+
self, set_status: bool = False, *, component_id: str = "main"
1208+
) -> bool:
1209+
"""Call the volumeUp command."""
1210+
result = await self.command(
1211+
component_id, Capability.audio_volume, Command.volume_up
1212+
)
1213+
if result and set_status:
1214+
self.status.volume = min(self.status.volume + 1, 100)
1215+
return result
1216+
1217+
async def volume_down(
1218+
self, set_status: bool = False, *, component_id: str = "main"
1219+
) -> bool:
1220+
"""Call the volumeDown command."""
1221+
result = await self.command(
1222+
component_id, Capability.audio_volume, Command.volume_down
1223+
)
1224+
if result and set_status:
1225+
self.status.volume = max(self.status.volume - 1, 0)
1226+
return result
1227+
1228+
async def play(
1229+
self, set_status: bool = False, *, component_id: str = "main"
1230+
) -> bool:
1231+
"""Call the play command."""
1232+
result = await self.command(
1233+
component_id, Capability.media_playback, Command.play
1234+
)
1235+
if result and set_status:
1236+
self.status.playback_status = "play"
1237+
return result
1238+
1239+
async def pause(
1240+
self, set_status: bool = False, *, component_id: str = "main"
1241+
) -> bool:
1242+
"""Call the pause command."""
1243+
result = await self.command(
1244+
component_id, Capability.media_playback, Command.pause
1245+
)
1246+
if result and set_status:
1247+
self.status.playback_status = "pause"
1248+
return result
1249+
1250+
async def stop(
1251+
self, set_status: bool = False, *, component_id: str = "main"
1252+
) -> bool:
1253+
"""Call the stop command."""
1254+
result = await self.command(
1255+
component_id, Capability.media_playback, Command.stop
1256+
)
1257+
if result and set_status:
1258+
self.status.playback_status = "stop"
1259+
return result
1260+
1261+
async def fast_forward(
1262+
self, set_status: bool = False, *, component_id: str = "main"
1263+
) -> bool:
1264+
"""Call the fastForward command."""
1265+
result = await self.command(
1266+
component_id, Capability.media_playback, Command.fast_forward
1267+
)
1268+
if result and set_status:
1269+
self.status.playback_status = "fast forward"
1270+
return result
1271+
1272+
async def rewind(
1273+
self, set_status: bool = False, *, component_id: str = "main"
1274+
) -> bool:
1275+
"""Call the rewind command."""
1276+
result = await self.command(
1277+
component_id, Capability.media_playback, Command.rewind
1278+
)
1279+
if result and set_status:
1280+
self.status.playback_status = "rewind"
1281+
return result
1282+
1283+
async def set_input_source(
1284+
self, source: str, set_status: bool = False, *, component_id: str = "main"
1285+
) -> bool:
1286+
"""Call the setInputSource command."""
1287+
result = await self.command(
1288+
component_id,
1289+
Capability.media_input_source,
1290+
Command.set_input_source,
1291+
[source],
1292+
)
1293+
if result and set_status:
1294+
self.status.input_source = source
1295+
return result
1296+
1297+
async def set_playback_shuffle(
1298+
self, shuffle: bool, set_status: bool = False, *, component_id: str = "main"
1299+
) -> bool:
1300+
"""Call the setPlaybackShuffle command."""
1301+
shuffle_value = bool_to_value(Attribute.playback_shuffle, shuffle)
1302+
result = await self.command(
1303+
component_id,
1304+
Capability.media_playback_shuffle,
1305+
Command.set_playback_shuffle,
1306+
[shuffle_value],
1307+
)
1308+
if result and set_status:
1309+
self.status.playback_shuffle = shuffle
1310+
return result
1311+
1312+
async def set_repeat(
1313+
self, repeat: str, set_status: bool = False, *, component_id: str = "main"
1314+
) -> bool:
1315+
"""Call the setPlaybackRepeatMode command."""
1316+
result = await self.command(
1317+
component_id,
1318+
Capability.media_playback_repeat,
1319+
Command.set_playback_repeat_mode,
1320+
[repeat],
1321+
)
1322+
if result and set_status:
1323+
self.status.playback_repeat_mode = repeat
1324+
return result
1325+
1326+
async def set_tv_channel(
1327+
self, channel: str, set_status: bool = False, *, component_id: str = "main"
1328+
) -> bool:
1329+
"""Call the setTvChannel command."""
1330+
result = await self.command(
1331+
component_id, Capability.tv_channel, Command.set_tv_channel, [channel]
1332+
)
1333+
if result and set_status:
1334+
self.status.tv_channel = channel
1335+
return result
1336+
1337+
async def channel_up(
1338+
self, set_status: bool = False, *, component_id: str = "main"
1339+
) -> bool:
1340+
"""Call the channelUp command."""
1341+
return await self.command(
1342+
component_id, Capability.tv_channel, Command.channel_up
1343+
)
1344+
1345+
async def channel_down(
1346+
self, set_status: bool = False, *, component_id: str = "main"
1347+
) -> bool:
1348+
"""Call the channelDown command."""
1349+
return await self.command(
1350+
component_id, Capability.tv_channel, Command.channel_down
1351+
)
1352+
10641353
@property
10651354
def status(self):
10661355
"""Get the status entity of the device."""
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"commands": [
3+
{
4+
"component": "main",
5+
"capability": "tvChannel",
6+
"command": "channelDown"
7+
}
8+
]
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"commands": [
3+
{
4+
"component": "main",
5+
"capability": "tvChannel",
6+
"command": "channelUp"
7+
}
8+
]
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"commands": [
3+
{
4+
"component": "main",
5+
"capability": "mediaPlayback",
6+
"command": "fastForward"
7+
}
8+
]
9+
}

0 commit comments

Comments
 (0)