88import soco # type: ignore
99import soco .alarms # type: ignore
1010import tabulate # type: ignore
11+ from soco .alarms import Alarm , get_alarms
12+ from soco .core import SoCo
1113from soco .exceptions import SoCoUPnPException # type: ignore
1214
1315from 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
9395def 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
125127def 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
140142def 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
188190def 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
226228def 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