Skip to content

Commit a75b427

Browse files
committed
Use Sonos Favourites instead of URIs when creating alarms
1 parent e9cbd14 commit a75b427

3 files changed

Lines changed: 59 additions & 28 deletions

File tree

CHANGELOG.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
v0.4.62 -
1+
v0.4.62 - Use Sonos Favourites instead of URIs when creating alarms
22
v0.4.61 - Add 'set_queue_position' action
33
v0.4.60 - Add 'generic' HTTP macro and increase number of args to 12
44
- Fix remove_playlist / delete_playist actions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,13 +492,13 @@ Some SoCo-CLI alarm actions below require an **alarm specification** (`alarm_spe
492492
representing a day of the week (Sunday is 0, Monday is 1, etc.), e.g., `ON_034` means
493493
Sunday, Wednesday and Thursday
494494
4. Whether the alarm is enabled: `ON` or `OFF` (or `YES`, `NO`)
495-
5. What to play: `CHIME`, or a URI for a stream (which must be enclosed in double quotes)
495+
5. What to play: `CHIME` (or `chime`) for the standard Sonos alarm sound, or a choice from your Sonos Favourites. Sonos Favourite matching will use case-insensitive, partial matches.
496496
6. Play mode: one of `NORMAL`, `SHUFFLE_NOREPEAT`, `SHUFFLE`, `REPEAT_ALL`, `REPEAT_ONE`, `SHUFFLE_REPEAT_ONE` (note that `SHUFFLE` means SHUFFLE *and* REPEAT)
497497
7. The volume to play at: `0`-`100`
498498
8. Whether to include grouped speakers: `ON` or `OFF` (or `YES`, `NO`)
499499

500500
Examples of alarm specifications:
501-
- `07:00,01:30,WEEKDAYS,ON,"http://stream.live.vc.bbcmedia.co.uk/bbc_radio_fourfm",NORMAL,50,OFF`
501+
- `07:00,01:30,WEEKDAYS,ON,"Radio 4",NORMAL,50,OFF`
502502
- `06:30,00:01,WEEKDAYS,ON,CHIME,NORMAL,50,OFF`
503503

504504
In actions which **modify** (or copy and modify) an existing alarm, values that are to be left unchanged are denoted by an underscore in the `alarm_spec`. E.g., to change only the duration and volume of an alarm, use an alarm spec such as: `_,01:00,_,_,_,_,60,_`.

soco_cli/alarms.py

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import soco # type: ignore
99
import soco.alarms # type: ignore
1010
import tabulate # type: ignore
11+
from soco.alarms import Alarm, get_alarms
12+
from soco.core import SoCo
1113
from soco.exceptions import SoCoUPnPException # type: ignore
1214

1315
from soco_cli.utils import (
@@ -78,7 +80,7 @@ def list_alarms(speaker, action, args, soco_function, use_local_speaker_list):
7880
"2: Duration",
7981
"3: Recurrence",
8082
"4: Enabled",
81-
"5: Title or URI",
83+
"5: Title",
8284
"6: Play Mode",
8385
"7: Vol.",
8486
"8: Incl. Grouped",
@@ -91,7 +93,7 @@ def list_alarms(speaker, action, args, soco_function, use_local_speaker_list):
9193

9294
@one_parameter
9395
def remove_alarms(speaker, action, args, soco_function, use_local_speaker_list):
94-
alarms = soco.alarms.get_alarms(speaker)
96+
alarms = get_alarms(speaker)
9597

9698
if args[0].lower() == "all":
9799
for alarm in alarms:
@@ -123,14 +125,14 @@ def remove_alarms(speaker, action, args, soco_function, use_local_speaker_list):
123125

124126
@one_parameter
125127
def add_alarm(speaker, action, args, soco_function, use_local_speaker_list):
126-
new_alarm = soco.alarms.Alarm(zone=speaker)
127-
if not _modify_alarm_object(new_alarm, args[0]):
128+
new_alarm = Alarm(zone=speaker)
129+
if not _modify_alarm_object(speaker, new_alarm, args[0]):
128130
return False
129131

130132
try:
131133
new_alarm.save()
132-
except soco.exceptions.SoCoUPnPException:
133-
error_report("Failed to create alarm")
134+
except SoCoUPnPException as e:
135+
error_report("Failed to create alarm: {}".format(e))
134136
return False
135137

136138
return True
@@ -139,7 +141,7 @@ def add_alarm(speaker, action, args, soco_function, use_local_speaker_list):
139141
@two_parameters
140142
def modify_alarm(speaker, action, args, soco_function, use_local_speaker_list):
141143
alarm_ids = args[0].lower().split(",")
142-
all_alarms = soco.alarms.get_alarms(speaker)
144+
all_alarms = get_alarms(speaker)
143145
if alarm_ids[0] == "all":
144146
alarms = set(all_alarms)
145147
else:
@@ -153,7 +155,7 @@ def modify_alarm(speaker, action, args, soco_function, use_local_speaker_list):
153155
print("Alarm ID '{}' not found".format(alarm_id))
154156

155157
for index, alarm in enumerate(alarms):
156-
if not _modify_alarm_object(alarm, args[1]):
158+
if not _modify_alarm_object(speaker, alarm, args[1]):
157159
continue
158160
try:
159161
logging.info("Saving alarm '{}'".format(alarm.alarm_id))
@@ -166,8 +168,8 @@ def modify_alarm(speaker, action, args, soco_function, use_local_speaker_list):
166168
)
167169
)
168170
time.sleep(ALARM_SAVE_DELAY)
169-
except soco.exceptions.SoCoUPnPException:
170-
error_report("Failed to modify alarm {}".format(alarm.alarm_id))
171+
except SoCoUPnPException as e:
172+
error_report("Failed to modify alarm {}: {}".format(alarm.alarm_id, e))
171173
continue
172174

173175
return True
@@ -186,7 +188,7 @@ def move_alarm(speaker, action, args, soco_function, use_local_speaker_list):
186188

187189

188190
def move_or_copy_alarm(speaker, alarm_id, copy=True):
189-
alarms = soco.alarms.get_alarms(speaker)
191+
alarms = get_alarms(speaker)
190192
for alarm in alarms:
191193
if alarm_id == alarm.alarm_id:
192194
break
@@ -203,8 +205,8 @@ def move_or_copy_alarm(speaker, alarm_id, copy=True):
203205
alarm._alarm_id = None
204206
try:
205207
alarm.save()
206-
except soco.exceptions.SoCoUPnPException:
207-
error_report("Failed to copy/move alarm")
208+
except SoCoUPnPException as e:
209+
error_report("Failed to copy/move alarm: {}".format(e))
208210
return False
209211

210212
if copy is True:
@@ -224,7 +226,7 @@ def disable_alarms(speaker, action, args, soco_function, use_local_speaker_list)
224226

225227

226228
def set_alarms(speaker, alarm_ids, enabled=True):
227-
alarms = soco.alarms.get_alarms(speaker)
229+
alarms = get_alarms(speaker)
228230
alarm_ids = set(alarm_ids.lower().split(","))
229231

230232
all_alarms = False
@@ -243,7 +245,14 @@ def set_alarms(speaker, alarm_ids, enabled=True):
243245
if alarm.enabled != enabled:
244246
alarm.enabled = enabled
245247
logging.info("Saving alarm '{}'".format(alarm.alarm_id))
246-
alarm.save()
248+
try:
249+
alarm.save()
250+
except SoCoUPnPException as e:
251+
error_report(
252+
"Failed to change state of alarm {}: {}".format(
253+
alarm.alarm_id, e
254+
)
255+
)
247256
if len(alarm_ids) != 0 or all_alarms:
248257
# Stabilisation delay
249258
logging.info(
@@ -321,7 +330,7 @@ def copy_modify_alarm(speaker, action, args, soco_function, use_local_speaker_li
321330
alarm_parms = args[1]
322331

323332
# Find the alarm
324-
alarms = soco.alarms.get_alarms(speaker)
333+
alarms = get_alarms(speaker)
325334
for alarm in alarms:
326335
if alarm_id == alarm.alarm_id:
327336
break
@@ -339,20 +348,24 @@ def copy_modify_alarm(speaker, action, args, soco_function, use_local_speaker_li
339348
new_alarm.zone = speaker
340349

341350
# Apply modifications
342-
if not _modify_alarm_object(new_alarm, alarm_parms):
351+
if not _modify_alarm_object(speaker, new_alarm, alarm_parms):
343352
return False
344353

345354
# Save the new alarm
346355
try:
347356
new_alarm.save()
348-
except soco.exceptions.SoCoUPnPException:
349-
error_report("Failed to copy/move alarm; did you modify the start time?")
357+
except SoCoUPnPException as e:
358+
error_report(
359+
"Failed to copy/move alarm; did you remember to modify the start time?: {}".format(
360+
e
361+
)
362+
)
350363
return False
351364

352365
return True
353366

354367

355-
def _modify_alarm_object(alarm: soco.alarms.Alarm, parms_string: str) -> bool:
368+
def _modify_alarm_object(speaker: SoCo, alarm: Alarm, parms_string: str) -> bool:
356369
alarm_parameters = parms_string.split(",")
357370
if len(alarm_parameters) != 8:
358371
error_report(
@@ -398,11 +411,12 @@ def _modify_alarm_object(alarm: soco.alarms.Alarm, parms_string: str) -> bool:
398411
return False
399412
alarm.enabled = enabled
400413

401-
uri = alarm_parameters[4]
402-
if not uri == "_":
403-
if uri.lower() == "chime":
404-
uri = None
405-
alarm.program_uri = uri
414+
fav = alarm_parameters[4]
415+
if not fav == "_":
416+
if fav.lower() == "chime":
417+
alarm.program_uri = None
418+
else:
419+
set_program_data(speaker, alarm, fav)
406420

407421
play_mode = alarm_parameters[5].upper()
408422
if not play_mode == "_":
@@ -459,3 +473,20 @@ def _modify_alarm_object(alarm: soco.alarms.Alarm, parms_string: str) -> bool:
459473
alarm.include_linked_zones = include_linked
460474

461475
return True
476+
477+
478+
def set_program_data(speaker: SoCo, alarm: Alarm, fav: str):
479+
"""
480+
Set the program URI and metadata for the alarm, using a selection
481+
from the list of Sonos Favourites.
482+
"""
483+
s_favs = speaker.music_library.get_sonos_favorites(complete_result=True)
484+
for s_fav in s_favs:
485+
# This will pick the first, case-insensitive partial match
486+
if fav.lower() in s_fav.title.lower():
487+
alarm.program_metadata = s_fav.resource_meta_data
488+
# Assume there's only one 'resources' object in the list
489+
alarm.program_uri = s_fav.resources[0].uri
490+
return
491+
else:
492+
raise Exception("Favourite '{}' not found".format(fav))

0 commit comments

Comments
 (0)