diff options
-rw-r--r-- | helpers/action.py | 102 | ||||
-rw-r--r-- | helpers/mapping.py | 4 | ||||
-rw-r--r-- | helpers/music_file.py | 22 |
3 files changed, 109 insertions, 19 deletions
diff --git a/helpers/action.py b/helpers/action.py index ec8fcb6..a6c48e9 100644 --- a/helpers/action.py +++ b/helpers/action.py | |||
@@ -1,10 +1,11 @@ | |||
1 | import threading | 1 | import threading |
2 | import time | 2 | import time |
3 | 3 | ||
4 | from . import debug_print | 4 | from transitions.extensions import HierarchicalMachine as Machine |
5 | from . import debug_print, error_print | ||
5 | 6 | ||
6 | class Action: | 7 | class Action: |
7 | action_types = [ | 8 | ACTION_TYPES = [ |
8 | 'command', | 9 | 'command', |
9 | 'interrupt_wait', | 10 | 'interrupt_wait', |
10 | 'pause', | 11 | 'pause', |
@@ -17,40 +18,106 @@ class Action: | |||
17 | 'wait', | 18 | 'wait', |
18 | ] | 19 | ] |
19 | 20 | ||
21 | STATES = [ | ||
22 | 'initial', | ||
23 | 'loading', | ||
24 | 'failed', | ||
25 | { | ||
26 | 'name': 'loaded', | ||
27 | 'children': ['running'] | ||
28 | } | ||
29 | ] | ||
30 | |||
31 | TRANSITIONS = [ | ||
32 | { | ||
33 | 'trigger': 'load', | ||
34 | 'source': 'initial', | ||
35 | 'dest': 'loading' | ||
36 | }, | ||
37 | { | ||
38 | 'trigger': 'fail', | ||
39 | 'source': 'loading', | ||
40 | 'dest': 'failed' | ||
41 | }, | ||
42 | { | ||
43 | 'trigger': 'success', | ||
44 | 'source': 'loading', | ||
45 | 'dest': 'loaded' | ||
46 | }, | ||
47 | { | ||
48 | 'trigger': 'run', | ||
49 | 'source': 'loaded', | ||
50 | 'dest': 'loaded_running', | ||
51 | 'after': 'finish_action' | ||
52 | }, | ||
53 | { | ||
54 | 'trigger': 'interrupt', | ||
55 | 'source': 'loaded_running', | ||
56 | 'dest': 'loaded', | ||
57 | 'before': 'trigger_interrupt' | ||
58 | }, | ||
59 | { | ||
60 | 'trigger': 'finish_action', | ||
61 | 'source': 'loaded_running', | ||
62 | 'dest': 'loaded' | ||
63 | } | ||
64 | ] | ||
65 | |||
20 | def __init__(self, action, key, **kwargs): | 66 | def __init__(self, action, key, **kwargs): |
21 | if action in self.action_types: | 67 | Machine(model=self, states=self.STATES, |
22 | self.action = action | 68 | transitions=self.TRANSITIONS, initial='initial', |
23 | else: | 69 | ignore_invalid_triggers=True, queued=True) |
24 | raise Exception("Unknown action {}".format(action)) | ||
25 | 70 | ||
71 | self.action = action | ||
26 | self.key = key | 72 | self.key = key |
27 | self.mapping = key.parent | 73 | self.mapping = key.parent |
28 | self.arguments = kwargs | 74 | self.arguments = kwargs |
29 | self.sleep_event = None | 75 | self.sleep_event = None |
76 | self.waiting_music = None | ||
77 | self.load() | ||
30 | 78 | ||
31 | def ready(self): | 79 | def ready(self): |
32 | if 'music' in self.arguments: | 80 | return self.is_loaded(allow_substates=True) |
33 | return self.arguments['music'].is_loaded(allow_substates=True) | 81 | |
82 | def callback_loaded(self, success): | ||
83 | if success: | ||
84 | self.success() | ||
85 | else: | ||
86 | self.fail() | ||
87 | |||
88 | # Machine states / events | ||
89 | def on_enter_loading(self): | ||
90 | if self.action in self.ACTION_TYPES: | ||
91 | if 'music' in self.arguments: | ||
92 | self.arguments['music'].subscribe_loaded(self.callback_loaded) | ||
93 | else: | ||
94 | self.success() | ||
34 | else: | 95 | else: |
35 | return True | 96 | error_print("Unknown action {}".format(self.action)) |
97 | self.fail() | ||
98 | |||
36 | 99 | ||
37 | def run(self): | 100 | def on_enter_loaded_running(self): |
38 | debug_print(self.description()) | 101 | debug_print(self.description()) |
39 | getattr(self, self.action)(**self.arguments) | 102 | getattr(self, self.action)(**self.arguments) |
40 | 103 | ||
41 | def description(self): | 104 | def trigger_interrupt(self): |
42 | return getattr(self, self.action + "_print")(**self.arguments) | ||
43 | |||
44 | def interrupt(self): | ||
45 | if getattr(self, self.action + "_interrupt", None): | 105 | if getattr(self, self.action + "_interrupt", None): |
46 | return getattr(self, self.action + "_interrupt")(**self.arguments) | 106 | return getattr(self, self.action + "_interrupt")(**self.arguments) |
47 | 107 | ||
108 | # Helpers | ||
48 | def music_list(self, music): | 109 | def music_list(self, music): |
49 | if music is not None: | 110 | if music is not None: |
50 | return [music] | 111 | return [music] |
51 | else: | 112 | else: |
52 | return self.mapping.open_files.values() | 113 | return self.mapping.open_files.values() |
53 | 114 | ||
115 | def description(self): | ||
116 | if getattr(self, self.action + "_print", None): | ||
117 | return getattr(self, self.action + "_print")(**self.arguments) | ||
118 | else: | ||
119 | return "unknown action {}".format(self.action) | ||
120 | |||
54 | # Actions | 121 | # Actions |
55 | def command(self, command="", **kwargs): | 122 | def command(self, command="", **kwargs): |
56 | # FIXME: todo | 123 | # FIXME: todo |
@@ -104,6 +171,7 @@ class Action: | |||
104 | music.stop(fade_out=fade_out) | 171 | music.stop(fade_out=fade_out) |
105 | 172 | ||
106 | if previous is not None: | 173 | if previous is not None: |
174 | self.waiting_music = previous | ||
107 | previous.stop( | 175 | previous.stop( |
108 | fade_out=fade_out, | 176 | fade_out=fade_out, |
109 | wait=wait, | 177 | wait=wait, |
@@ -254,10 +322,14 @@ class Action: | |||
254 | 322 | ||
255 | return message | 323 | return message |
256 | 324 | ||
257 | # Interruptions | 325 | # Interruptions (only for non-"atomic" actions) |
258 | def wait_interrupt(self, duration=0, music=None, **kwargs): | 326 | def wait_interrupt(self, duration=0, music=None, **kwargs): |
259 | if self.sleep_event is not None: | 327 | if self.sleep_event is not None: |
260 | self.sleep_event.set() | 328 | self.sleep_event.set() |
261 | if music is not None: | 329 | if music is not None: |
262 | music.wait_event.set() | 330 | music.wait_event.set() |
263 | 331 | ||
332 | def stop_interrupt(self, music=None, fade_out=0, wait=False, | ||
333 | set_wait_id=None, **kwargs): | ||
334 | if self.waiting_music is not None: | ||
335 | self.waiting_music.wait_event.set() | ||
diff --git a/helpers/mapping.py b/helpers/mapping.py index c2a94e6..b71f3fe 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py | |||
@@ -178,8 +178,8 @@ class Mapping(RelativeLayout): | |||
178 | **config['music_properties'][filename]) | 178 | **config['music_properties'][filename]) |
179 | else: | 179 | else: |
180 | seen_files[filename] = MusicFile( | 180 | seen_files[filename] = MusicFile( |
181 | self, | 181 | filename, |
182 | filename) | 182 | self) |
183 | 183 | ||
184 | if filename not in key_properties[mapped_key]['files']: | 184 | if filename not in key_properties[mapped_key]['files']: |
185 | key_properties[mapped_key]['files'] \ | 185 | key_properties[mapped_key]['files'] \ |
diff --git a/helpers/music_file.py b/helpers/music_file.py index ccf60ce..aeba1b9 100644 --- a/helpers/music_file.py +++ b/helpers/music_file.py | |||
@@ -32,7 +32,8 @@ class MusicFile: | |||
32 | { | 32 | { |
33 | 'trigger': 'load', | 33 | 'trigger': 'load', |
34 | 'source': 'initial', | 34 | 'source': 'initial', |
35 | 'dest': 'loading' | 35 | 'dest': 'loading', |
36 | 'after': 'poll_loaded' | ||
36 | }, | 37 | }, |
37 | { | 38 | { |
38 | 'trigger': 'fail', | 39 | 'trigger': 'fail', |
@@ -68,7 +69,8 @@ class MusicFile: | |||
68 | 'trigger': 'stopped', | 69 | 'trigger': 'stopped', |
69 | 'source': '*', | 70 | 'source': '*', |
70 | 'dest': 'loaded', | 71 | 'dest': 'loaded', |
71 | 'before': 'trigger_stopped_events' | 72 | 'before': 'trigger_stopped_events', |
73 | 'conditions': ['is_in_use'] | ||
72 | } | 74 | } |
73 | ] | 75 | ] |
74 | 76 | ||
@@ -77,6 +79,7 @@ class MusicFile: | |||
77 | transitions=self.TRANSITIONS, initial='initial', | 79 | transitions=self.TRANSITIONS, initial='initial', |
78 | ignore_invalid_triggers=True) | 80 | ignore_invalid_triggers=True) |
79 | 81 | ||
82 | self.loaded_callbacks = [] | ||
80 | self.mapping = mapping | 83 | self.mapping = mapping |
81 | self.filename = filename | 84 | self.filename = filename |
82 | self.name = name or filename | 85 | self.name = name or filename |
@@ -230,6 +233,21 @@ class MusicFile: | |||
230 | self.wait_event.clear() | 233 | self.wait_event.clear() |
231 | self.wait_event.wait() | 234 | self.wait_event.wait() |
232 | 235 | ||
236 | # Let other subscribe for an event when they are ready | ||
237 | def subscribe_loaded(self, callback): | ||
238 | with file_lock: | ||
239 | if self.is_loaded(allow_substates=True): | ||
240 | callback(True) | ||
241 | elif self.is_failed(): | ||
242 | callback(False) | ||
243 | else: | ||
244 | self.loaded_callbacks.append(callback) | ||
245 | |||
246 | def poll_loaded(self): | ||
247 | for callback in self.loaded_callbacks: | ||
248 | callback(self.is_loaded()) | ||
249 | self.loaded_callbacks = [] | ||
250 | |||
233 | # Callbacks | 251 | # Callbacks |
234 | def finished_callback(self): | 252 | def finished_callback(self): |
235 | self.stopped() | 253 | self.stopped() |