Major refactoring:
- New Storyboard class now has execution logic included - Implemented dummy storyboard for testing - Language selection is a state in Statemachine - Added test class for instantiating a Statemachine
This commit is contained in:
parent
a64fd6460a
commit
d837134db5
9 changed files with 444 additions and 307 deletions
|
@ -98,4 +98,4 @@ SFX_SHUTTER = SfxFile('done')
|
|||
SFX_REC_AUDIO = SfxFile('countdown')
|
||||
SFX_STOP_REC = SfxFile('done')
|
||||
|
||||
SND_SELECT_LANG = StoryFile(name='IC-SIBI-00')
|
||||
SND_SELECT_LANG = SfxFile('lang-select')
|
||||
|
|
|
@ -118,18 +118,18 @@ class PizzaHAL:
|
|||
:param hal:
|
||||
:param sounds: A list of sound files
|
||||
"""
|
||||
if self.soundcache is None:
|
||||
self.soundcache = {}
|
||||
# if self.soundcache is None:
|
||||
# self.soundcache = {}
|
||||
|
||||
if not mx.get_init():
|
||||
mx.init()
|
||||
|
||||
if sounds is not None:
|
||||
for sound in sounds:
|
||||
# Extract data and sampling rate from file
|
||||
# data, fs = sf.read(str(sound), dtype='float32')
|
||||
# self.soundcache[str(sound)] = (data, fs)
|
||||
self.soundcache[str(sound)] = mx.Sound(str(sound))
|
||||
# if sounds is not None:
|
||||
# for sound in sounds:
|
||||
# # Extract data and sampling rate from file
|
||||
# # data, fs = sf.read(str(sound), dtype='float32')
|
||||
# # self.soundcache[str(sound)] = (data, fs)
|
||||
# self.soundcache[str(sound)] = mx.Sound(str(sound))
|
||||
|
||||
def init_camera(self):
|
||||
if self.camera is None:
|
||||
|
@ -192,7 +192,6 @@ def turn_off(hal: PizzaHAL, **kwargs):
|
|||
"""
|
||||
Turn off the lights.
|
||||
"""
|
||||
|
||||
set_light(hal, Lights.BACKLIGHT, 0, 0, 0, 0, 0)
|
||||
set_light(hal, Lights.FRONTLIGHT, 0, 0, 0, 0, 0)
|
||||
do_it(hal)
|
||||
|
@ -232,6 +231,7 @@ def wait_for_input(hal: PizzaHAL,
|
|||
(8 if green_cb else 0)
|
||||
|
||||
if sound is not None:
|
||||
logger.debug(f'Waiting for user, playing sound {sound}.')
|
||||
hal.play_sound(str(sound))
|
||||
|
||||
resp = hal.send_cmd(SerialCommands.USER_INTERACT, bitmask.to_bytes(1, 'little', signed=False), timeout.to_bytes(4, 'little', signed=False))
|
||||
|
|
|
@ -112,7 +112,7 @@ STORYBOARD = [
|
|||
storyboard.Do(storyboard.Activity.ADVANCE_UP) # Bild 9
|
||||
),
|
||||
storyboard.Chapter( # X9
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1., fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=1., fade=0.5),
|
||||
|
@ -122,7 +122,7 @@ STORYBOARD = [
|
|||
sound=fs_names.StoryFile('23de')),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('24de')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0., fade=1.),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=0., fade=1.),
|
||||
|
|
|
@ -1,42 +1,47 @@
|
|||
from pizzactrl import fs_names
|
||||
from pizzactrl.storyboard import *
|
||||
|
||||
STORYBOARD = [
|
||||
Chapter(
|
||||
Do(Activity.ADVANCE_UP,
|
||||
steps=50),
|
||||
Do(Activity.LIGHT_BACK, # Bild 1
|
||||
intensity=1.0, fade=1.0),
|
||||
STORYBOARD = Storyboard(
|
||||
Chapter( # Chapter 0
|
||||
Do(Activity.PARALLEL,
|
||||
activities = [
|
||||
Do(Activity.ADVANCE_UP, steps=30),
|
||||
Do(Activity.LIGHT_BACK, r=0, g=0, b=0, w=1.0, fade=1.0),
|
||||
]),
|
||||
Do(Activity.WAIT_FOR_INPUT,
|
||||
on_blue=Select(Option.CONTINUE),
|
||||
on_red=Select(Option.REPEAT))
|
||||
on_red=Select(Option.REPEAT)),
|
||||
Do(Activity.LIGHT_BACK) # Fade out
|
||||
),
|
||||
Chapter(
|
||||
Do(Activity.ADVANCE_LEFT,
|
||||
steps=100),
|
||||
Do(Activity.ADVANCE_UP,
|
||||
steps=50),
|
||||
Chapter( # Chapter 1
|
||||
Do(Activity.PARALLEL,
|
||||
activities = [
|
||||
Do(Activity.LIGHT_FRONT, r=1.0, fade=2.0),
|
||||
Do(Activity.ADVANCE_LEFT, steps=50),
|
||||
Do(Activity.ADVANCE_UP, steps=25)
|
||||
]),
|
||||
Do(Activity.WAIT_FOR_INPUT,
|
||||
on_blue=Select(Option.CONTINUE),
|
||||
on_red=Select(Option.REPEAT),
|
||||
on_yellow=Select(Option.GOTO, chapter=0),
|
||||
on_green=Select(Option.QUIT))
|
||||
on_green=Select(Option.QUIT)),
|
||||
Do(Activity.LIGHT_FRONT) # Fade out
|
||||
),
|
||||
Chapter(
|
||||
Do(Activity.ADVANCE_LEFT,
|
||||
steps=-50),
|
||||
Do(Activity.ADVANCE_UP,
|
||||
steps=-20)
|
||||
Chapter( # Chapter 2
|
||||
Do(Activity.LIGHT_BACK, b=1., fade=2.0),
|
||||
Do(Activity.ADVANCE_LEFT, steps=-50),
|
||||
Do(Activity.ADVANCE_UP, steps=-20),
|
||||
Do(Activity.LIGHT_BACK)
|
||||
),
|
||||
Chapter(
|
||||
Do(Activity.ADVANCE_LEFT,
|
||||
steps=100),
|
||||
Do(Activity.ADVANCE_UP,
|
||||
steps=50),
|
||||
Chapter( # Chapter 3
|
||||
Do(Activity.LIGHT_FRONT, r=1., g=1., fade=2.0),
|
||||
Do(Activity.ADVANCE_LEFT, steps=50),
|
||||
Do(Activity.ADVANCE_UP, steps=50),
|
||||
Do(Activity.LIGHT_FRONT, fade=5.0),
|
||||
Do(Activity.WAIT_FOR_INPUT,
|
||||
on_blue=Select(Option.CONTINUE),
|
||||
on_red=Select(Option.REPEAT),
|
||||
on_yellow=Select(Option.GOTO, chapter=0),
|
||||
on_green=Select(Option.QUIT))
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ from pizzactrl import storyboard, fs_names
|
|||
|
||||
STORYBOARD = [
|
||||
storyboard.Chapter(
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER, # VERT01
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT, # VERT01
|
||||
intensity=1.0, fade=1.0)
|
||||
),
|
||||
storyboard.Chapter(
|
||||
|
@ -19,7 +19,7 @@ STORYBOARD = [
|
|||
# storyboard.Do(storyboard.Activity.ADVANCE_UP, steps=90), # VERT02down
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=1.0, fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0.0, fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-05')),
|
||||
|
@ -32,7 +32,7 @@ STORYBOARD = [
|
|||
sound=fs_names.StoryFile('IC-SIBI-07')),
|
||||
storyboard.Do(storyboard.Activity.WAIT_FOR_INPUT),
|
||||
# storyboard.Do(storyboard.Activity.ADVANCE_UP), # VERT04
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
),
|
||||
storyboard.Chapter(
|
||||
|
@ -41,7 +41,7 @@ STORYBOARD = [
|
|||
# storyboard.Do(storyboard.Activity.ADVANCE_LEFT), # HOR02
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=1.0, fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0., fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-09')),
|
||||
|
@ -71,14 +71,14 @@ STORYBOARD = [
|
|||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-13')),
|
||||
# storyboard.Do(storyboard.Activity.ADVANCE_UP), # VERT05
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-14')),
|
||||
# storyboard.Do(storyboard.Activity.ADVANCE_LEFT), # HOR03
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=1.0, fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0., fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-15')),
|
||||
|
@ -91,11 +91,11 @@ STORYBOARD = [
|
|||
storyboard.Chapter(
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-17')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-18')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-19')),
|
||||
|
@ -104,11 +104,11 @@ STORYBOARD = [
|
|||
storyboard.Chapter(
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-20')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-21')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-22')),
|
||||
|
@ -117,16 +117,16 @@ STORYBOARD = [
|
|||
storyboard.Chapter(
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-23')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-24')),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0.0, fade=1.0),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.StoryFile('IC-SIBI-25')),
|
||||
storyboard.Do(storyboard.Activity.WAIT_FOR_INPUT),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=1.0, fade=1.0),
|
||||
),
|
||||
storyboard.Chapter(
|
||||
|
@ -143,7 +143,7 @@ STORYBOARD = [
|
|||
cache=False),
|
||||
storyboard.Do(storyboard.Activity.PLAY_SOUND,
|
||||
sound=fs_names.SFX_STOP_REC),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_LAYER,
|
||||
storyboard.Do(storyboard.Activity.LIGHT_FRONT,
|
||||
intensity=0.0, fade=0.5),
|
||||
storyboard.Do(storyboard.Activity.LIGHT_BACK,
|
||||
intensity=1.0, fade=0.5),
|
||||
|
|
BIN
pizzactrl/sounds/lang-select.wav
Normal file
BIN
pizzactrl/sounds/lang-select.wav
Normal file
Binary file not shown.
|
@ -8,11 +8,10 @@ from time import sleep
|
|||
from enum import Enum, auto
|
||||
from subprocess import call
|
||||
|
||||
from pizzactrl import fs_names, sb_dummy
|
||||
from pizzactrl import SOUNDS_PATH, fs_names
|
||||
from pizzactrl.hal import ScrollSensor
|
||||
from .storyboard import Activity, Select, Option
|
||||
from .hal_serial import SerialCommunicationError, PizzaHAL, play_sound, take_photo, record_video, record_sound, turn_off, wait_for_input, \
|
||||
set_light, set_movement, rewind
|
||||
from .storyboard import Language, Activity, Select, Option, Storyboard
|
||||
from .hal_serial import SerialCommunicationError, PizzaHAL, wait_for_input, play_sound, turn_off
|
||||
from pizzactrl import storyboard
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -22,88 +21,54 @@ class State(Enum):
|
|||
POWER_ON = auto()
|
||||
POST = auto()
|
||||
IDLE_START = auto()
|
||||
LANGUAGE_SELECT = auto()
|
||||
PLAY = auto()
|
||||
PAUSE = auto()
|
||||
POST_PROCESS = auto()
|
||||
REWIND = auto()
|
||||
IDLE_END = auto()
|
||||
SHUTDOWN = auto()
|
||||
ERROR = -1
|
||||
|
||||
|
||||
class Language(Enum):
|
||||
NOT_SET = auto()
|
||||
DE = auto()
|
||||
EN = auto()
|
||||
|
||||
|
||||
def load_sounds():
|
||||
"""
|
||||
Load all prerecorded Sounds from the cache
|
||||
|
||||
:returns a list of sound file names
|
||||
"""
|
||||
soundcache = [
|
||||
fs_names.SFX_SHUTTER,
|
||||
fs_names.SFX_ERROR,
|
||||
fs_names.SFX_POST_OK,
|
||||
fs_names.SND_SELECT_LANG
|
||||
]
|
||||
return soundcache
|
||||
|
||||
|
||||
# Map Activities to function calls
|
||||
ACTIVITY_SELECTOR = {
|
||||
Activity.PLAY_SOUND: play_sound,
|
||||
Activity.RECORD_SOUND: record_sound,
|
||||
Activity.RECORD_VIDEO: record_video,
|
||||
Activity.TAKE_PHOTO: take_photo,
|
||||
Activity.LIGHT_LAYER: set_light,
|
||||
Activity.LIGHT_BACK: set_light,
|
||||
Activity.ADVANCE_UP: set_movement,
|
||||
Activity.ADVANCE_LEFT: set_movement
|
||||
}
|
||||
|
||||
|
||||
class Statemachine:
|
||||
def __init__(self,
|
||||
story_de: Any=None,
|
||||
story_en: Any=None,
|
||||
hal: PizzaHAL,
|
||||
story: Storyboard,
|
||||
default_lang=Language.NOT_SET,
|
||||
move: bool = False,
|
||||
loop: bool = True,
|
||||
test: bool = False):
|
||||
self.state = State.POWER_ON
|
||||
self.hal = PizzaHAL()
|
||||
self.hal = hal
|
||||
|
||||
self.chapter = 0 # The storyboard index of the current chapter to play
|
||||
self.next_chapter = 0 # The storyboard index of the next chapter to play
|
||||
self.chapter_set = False # `True` if the next chapter has been set
|
||||
self.LANG = default_lang
|
||||
self.lang = None
|
||||
|
||||
self.story = story
|
||||
self.story.MOVE = move
|
||||
|
||||
self.story = None
|
||||
self.story_de = story_de
|
||||
self.story_en = story_en
|
||||
|
||||
self.lang = Language.NOT_SET
|
||||
|
||||
self.MOVE = move # self.move is reset to this value
|
||||
self.move = self.MOVE
|
||||
|
||||
self.test = test
|
||||
self.loop = loop
|
||||
|
||||
self.state = State.POWER_ON
|
||||
|
||||
def run(self):
|
||||
logger.debug(f'Run(state={self.state})')
|
||||
logger.debug(f'Starting Statemachine...')
|
||||
|
||||
choice = {
|
||||
State.POWER_ON: self._power_on,
|
||||
State.POST: self._post,
|
||||
State.IDLE_START: self._idle_start,
|
||||
State.LANGUAGE_SELECT: self._lang_select,
|
||||
State.PLAY: self._play,
|
||||
State.POST_PROCESS: self._post_process,
|
||||
State.REWIND: self._rewind,
|
||||
State.IDLE_END: self._idle_end
|
||||
}
|
||||
|
||||
while (self.state is not State.ERROR) and \
|
||||
(self.state is not State.SHUTDOWN):
|
||||
logger.debug(f'Run(state={self.state})')
|
||||
choice[self.state]()
|
||||
|
||||
if self.state is State.ERROR:
|
||||
|
@ -117,27 +82,15 @@ class Statemachine:
|
|||
|
||||
self._shutdown()
|
||||
|
||||
def _lang_de(self, **kwargs):
|
||||
logger.info(f'select language german')
|
||||
self.lang = Language.DE
|
||||
self.story = self.story_de
|
||||
|
||||
def _lang_en(self, **kwargs):
|
||||
logger.info(f'select language english')
|
||||
self.lang = Language.EN
|
||||
self.story = self.story_en
|
||||
|
||||
def _power_on(self):
|
||||
"""
|
||||
Initialize hal callbacks, load sounds
|
||||
"""
|
||||
logger.debug(f'power on')
|
||||
|
||||
# TODO enable lid sensor
|
||||
# self.hal.lid_sensor.when_pressed = self._lid_open
|
||||
# self.hal.lid_sensor.when_released = self._lid_closed
|
||||
|
||||
self.hal.init_sounds(load_sounds())
|
||||
self.hal.init_sounds()
|
||||
self.hal.init_camera()
|
||||
|
||||
self.state = State.POST
|
||||
|
@ -146,9 +99,6 @@ class Statemachine:
|
|||
"""
|
||||
Power on self test.
|
||||
"""
|
||||
|
||||
logger.debug(f'post')
|
||||
|
||||
if (not self.test) and (not os.path.exists(fs_names.USB_STICK)):
|
||||
logger.warning('USB-Stick not found.')
|
||||
self.state = State.ERROR
|
||||
|
@ -171,11 +121,9 @@ class Statemachine:
|
|||
play_sound(self.hal, fs_names.SFX_POST_OK)
|
||||
|
||||
if self.test:
|
||||
self.state = State.PLAY
|
||||
logger.debug('play')
|
||||
self.state = State.LANGUAGE_SELECT
|
||||
else:
|
||||
self.state = State.IDLE_START
|
||||
logger.debug('idle_start')
|
||||
|
||||
|
||||
def _idle_start(self):
|
||||
|
@ -184,164 +132,62 @@ class Statemachine:
|
|||
"""
|
||||
pass
|
||||
|
||||
def _lang_select(self):
|
||||
"""
|
||||
Select language
|
||||
"""
|
||||
def _select_de():
|
||||
self.lang = Language.DE
|
||||
|
||||
def _select_en():
|
||||
self.lang = Language.EN
|
||||
|
||||
def _select_default():
|
||||
self.lang = self.LANG
|
||||
|
||||
#sound = self.hal.soundcache.get(fs_names.SND_SELECT_LANG)
|
||||
#logger.debug(f'got sound {sound} from soundcache.')
|
||||
|
||||
wait_for_input(self.hal,
|
||||
blue_cb=_select_de,
|
||||
red_cb=_select_en,
|
||||
sound=fs_names.SND_SELECT_LANG,
|
||||
timeout_cb=_select_default)
|
||||
|
||||
self.story.language = self.lang
|
||||
|
||||
logger.debug(f'User selected language={self.lang}')
|
||||
self.state = State.PLAY
|
||||
|
||||
def _play(self):
|
||||
"""
|
||||
Select language, then run the storyboard
|
||||
"""
|
||||
logger.debug(f'play')
|
||||
if self.test:
|
||||
self.story = sb_dummy.STORYBOARD
|
||||
else:
|
||||
# TODO reenable language selection
|
||||
self.story = self.story_en
|
||||
self.story.hal = self.hal
|
||||
|
||||
while self.chapter is not None:
|
||||
self._play_chapter()
|
||||
self._advance_chapter()
|
||||
while self.story.hasnext():
|
||||
self.story.play_chapter()
|
||||
self.story.advance_chapter()
|
||||
|
||||
self.state = State.REWIND
|
||||
self.state = State.POST_PROCESS
|
||||
|
||||
def _option_callback(self, selection: Select):
|
||||
def _post_process(self):
|
||||
"""
|
||||
Return a callback for the appropriate option and parameters.
|
||||
Callbacks set the properties of `Statemachine` to determine it's behaviour.
|
||||
"""
|
||||
rewind = selection.values.get('rewind', Option.REPEAT.value['rewind'])
|
||||
next_chapter = selection.values.get('chapter', Option.GOTO.value['chapter'])
|
||||
shutdown = selection.values.get('shutdown', Option.QUIT.value['shutdown'])
|
||||
|
||||
def _continue(**kwargs):
|
||||
"""
|
||||
Continue in the Storyboard. Prepare advancing to the next chapter.
|
||||
"""
|
||||
self.move = self.MOVE
|
||||
self.chapter_set = True
|
||||
if len(self.story) > (self.chapter + 1):
|
||||
self.next_chapter = self.chapter + 1
|
||||
else:
|
||||
self.next_chapter = None
|
||||
|
||||
def _repeat(**kwargs):
|
||||
"""
|
||||
Repeat the current chapter. Do not rewind if the selection says so.
|
||||
"""
|
||||
self.chapter_set = True
|
||||
self.move = rewind
|
||||
self.next_chapter = self.chapter
|
||||
|
||||
def _goto(**kwargs):
|
||||
"""
|
||||
Jump to a specified chapter.
|
||||
"""
|
||||
self.chapter_set = True
|
||||
self.move = self.MOVE
|
||||
self.next_chapter = next_chapter
|
||||
|
||||
def _quit(**kwargs):
|
||||
self.chapter_set = True
|
||||
self.move = self.MOVE
|
||||
self.loop = not shutdown
|
||||
self.next_chapter = None
|
||||
|
||||
return {
|
||||
Option.CONTINUE: _continue,
|
||||
Option.REPEAT: _repeat,
|
||||
Option.GOTO: _goto,
|
||||
Option.QUIT: _quit,
|
||||
None: None
|
||||
}[selection.option]
|
||||
|
||||
def _play_chapter(self):
|
||||
"""
|
||||
Play the chapter specified by self.chapter
|
||||
"""
|
||||
logger.debug(f'playing chapter {self.chapter}')
|
||||
|
||||
if self.chapter < len(self.story):
|
||||
chapter = self.story[self.chapter]
|
||||
|
||||
while chapter.hasnext():
|
||||
act = next(chapter)
|
||||
logger.debug(f'next activity {act.activity}')
|
||||
if act.activity is Activity.WAIT_FOR_INPUT:
|
||||
wait_for_input(hal=self.hal,
|
||||
blue_cb = self._option_callback(act.values['on_blue']),
|
||||
red_cb = self._option_callback(act.values['on_red']),
|
||||
yellow_cb = self._option_callback(act.values['on_yellow']),
|
||||
green_cb = self._option_callback(act.values['on_green']),
|
||||
timeout_cb = self._option_callback(act.values['on_timeout']),
|
||||
**act.values)
|
||||
else:
|
||||
try:
|
||||
ACTIVITY_SELECTOR[act.activity](self.hal, **act.values)
|
||||
except KeyError:
|
||||
logger.exception('Caught KeyError, ignoring...')
|
||||
pass
|
||||
|
||||
if not self.chapter_set:
|
||||
self.chapter_set = True
|
||||
self.next_chapter = self.chapter + 1
|
||||
|
||||
else:
|
||||
self.next_chapter = None
|
||||
|
||||
def _advance_chapter(self):
|
||||
"""
|
||||
Update chapters and move the scrolls.
|
||||
Update self.chapter to self.next_chapter
|
||||
"""
|
||||
if self.chapter_set and (self.next_chapter is not None):
|
||||
diff = self.next_chapter - self.chapter
|
||||
h_steps = 0
|
||||
v_steps = 0
|
||||
if diff < 0:
|
||||
"""
|
||||
Rewind all chapters up to target
|
||||
"""
|
||||
for ch in self.story[self.next_chapter:self.chapter]:
|
||||
steps = ch.rewind()
|
||||
h_steps += steps['h_steps']
|
||||
v_steps += steps['v_steps']
|
||||
|
||||
elif diff > 0:
|
||||
"""
|
||||
Skip all chapters up to target
|
||||
"""
|
||||
for ch in self.story[self.chapter:self.next_chapter]:
|
||||
steps = ch.skip()
|
||||
h_steps += steps['h_steps']
|
||||
v_steps += steps['v_steps']
|
||||
else:
|
||||
"""
|
||||
Rewind current chapter
|
||||
"""
|
||||
steps = self.story[self.chapter].rewind()
|
||||
h_steps = steps['h_steps']
|
||||
v_steps = steps['v_steps']
|
||||
|
||||
if self.move:
|
||||
set_movement(self.hal, h_steps, True)
|
||||
set_movement(self.hal, v_steps, False)
|
||||
|
||||
logger.debug(f'Setting chapter (cur: {self.chapter}) to {self.next_chapter}.')
|
||||
self.chapter = self.next_chapter
|
||||
self.chapter_set = False
|
||||
|
||||
def _rewind(self):
|
||||
"""
|
||||
Rewind all scrolls, post-process videos
|
||||
Post-processing
|
||||
"""
|
||||
# TODO postprocessing - add sound
|
||||
# logger.debug('Converting video...')
|
||||
# cmdstring = f'MP4Box -add {fs_names.REC_DRAW_CITY} {fs_names.REC_MERGED_VIDEO}'
|
||||
# call([cmdstring], shell=True)
|
||||
|
||||
logger.debug('Rewinding...')
|
||||
if self.move:
|
||||
rewind(self.hal)
|
||||
|
||||
for chapter in self.story:
|
||||
chapter.rewind()
|
||||
self.state = State.REWIND
|
||||
|
||||
def _rewind(self):
|
||||
"""
|
||||
Rewind all scrolls, post-process videos
|
||||
"""
|
||||
turn_off(self.hal)
|
||||
self.story.rewind()
|
||||
|
||||
if self.loop:
|
||||
self.state = State.IDLE_START
|
||||
|
@ -358,8 +204,4 @@ class Statemachine:
|
|||
"""
|
||||
Clean up, end execution
|
||||
"""
|
||||
logger.debug('shutdown')
|
||||
|
||||
turn_off(self.hal)
|
||||
|
||||
del self.hal
|
||||
|
|
|
@ -4,6 +4,9 @@ import logging
|
|||
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
|
||||
|
||||
from pizzactrl.statemachine import Statemachine
|
||||
from pizzactrl.sb_dummy import STORYBOARD
|
||||
from pizzactrl.hal_serial import PizzaHAL, rewind, turn_off
|
||||
|
||||
sm = Statemachine(move=True, loop=False, test=True)
|
||||
hal = PizzaHAL()
|
||||
sm = Statemachine(hal, STORYBOARD, move=True, loop=False, test=True)
|
||||
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
import logging
|
||||
from enum import Enum, auto
|
||||
from typing import List
|
||||
from threading import active_count
|
||||
from typing import List, Any
|
||||
|
||||
from pizzactrl.hal_serial import SerialCommands
|
||||
from pizzactrl.hal_serial import Lights, Scrolls, SerialCommands, PizzaHAL, \
|
||||
do_it, play_sound, take_photo, record_video, \
|
||||
record_sound, turn_off, wait_for_input, \
|
||||
set_light, set_movement, rewind
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigurationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Language(Enum):
|
||||
NOT_SET = 'NA'
|
||||
DE = 'DE'
|
||||
EN = 'EN'
|
||||
|
||||
|
||||
class Option(Enum):
|
||||
|
@ -11,7 +28,7 @@ class Option(Enum):
|
|||
CONTINUE = {} # Continue with chapter
|
||||
REPEAT = {'rewind': True} # Repeat chapter from beginning. `rewind=True`: reset scrolls to starting position
|
||||
GOTO = {'chapter': 0} # Jump to chapter number
|
||||
QUIT = {'shutdown': True} # End playback. `shutdown=True` also powers off box
|
||||
QUIT = {'quit': True} # End playback.
|
||||
|
||||
|
||||
class Select:
|
||||
|
@ -35,33 +52,37 @@ class Activity(Enum):
|
|||
'on_yellow': Select(None),
|
||||
'on_green': Select(None),
|
||||
'on_timeout': Select(Option.QUIT),
|
||||
'sound': None,
|
||||
Language.NOT_SET.value: None,
|
||||
Language.DE.value: None,
|
||||
Language.EN.value: None,
|
||||
'timeout': 0}
|
||||
PLAY_SOUND = {'sound': None}
|
||||
PLAY_SOUND = {Language.NOT_SET.value: None,
|
||||
Language.DE.value: None,
|
||||
Language.EN.value: None}
|
||||
RECORD_SOUND = {'duration': 10.0,
|
||||
'filename': '',
|
||||
'cache': False}
|
||||
RECORD_VIDEO = {'duration': 60.0,
|
||||
'filename': ''}
|
||||
TAKE_PHOTO = {'filename': ''}
|
||||
ADVANCE_UP = {'steps': 100, # TODO set right number of steps
|
||||
'direction': True,
|
||||
'horizontal': False}
|
||||
ADVANCE_LEFT = {'steps': 200, # TODO set right number of steps
|
||||
'direction': True,
|
||||
'horizontal': True}
|
||||
LIGHT_LAYER = {'r': 0,
|
||||
ADVANCE_UP = {'steps': 100,
|
||||
'scroll': Scrolls.VERTICAL}
|
||||
ADVANCE_LEFT = {'steps': 200,
|
||||
'scroll': Scrolls.HORIZONTAL}
|
||||
LIGHT_FRONT = {'r': 0,
|
||||
'g': 0,
|
||||
'b': 0,
|
||||
'w': 1.0,
|
||||
'w': 0,
|
||||
'fade': 1.0,
|
||||
'light': [ SerialCommands.FRONTLIGHT ]}
|
||||
'light': Lights.FRONTLIGHT}
|
||||
LIGHT_BACK = {'r': 0,
|
||||
'g': 0,
|
||||
'b': 0,
|
||||
'w': 1.0,
|
||||
'w': 0,
|
||||
'fade': 1.0,
|
||||
'light': [ SerialCommands.BACKLIGHT ]}
|
||||
'light': Lights.BACKLIGHT}
|
||||
PARALLEL = {'activities': []}
|
||||
GOTO = {'index': 0}
|
||||
|
||||
|
||||
class Do:
|
||||
|
@ -74,21 +95,29 @@ class Do:
|
|||
for key, value in self.activity.value.items():
|
||||
self.values[key] = kwargs.get(key, value)
|
||||
|
||||
def execute():
|
||||
# TODO implement
|
||||
pass
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.activity.name}({self.values})'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.activity.name}({self.values})'
|
||||
|
||||
class Do_Parallel:
|
||||
"""
|
||||
A list of activities which should be done at the same time
|
||||
"""
|
||||
def __init__(self, *activities: List[Do]) -> None:
|
||||
self.act_list = activities
|
||||
|
||||
def execute():
|
||||
for act in self.act_list:
|
||||
act.execute()
|
||||
def get_steps(self):
|
||||
"""
|
||||
Returns the number of steps this activity makes.
|
||||
returns: h_steps, v_steps
|
||||
"""
|
||||
h_steps = 0
|
||||
v_steps = 0
|
||||
if self.activity is Activity.ADVANCE_UP:
|
||||
v_steps += self.values['steps']
|
||||
elif self.activity is Activity.ADVANCE_LEFT:
|
||||
h_steps += self.values['steps']
|
||||
elif self.activity is Activity.PARALLEL:
|
||||
for act in self.values['activities']:
|
||||
h, v = act.get_steps()
|
||||
h_steps += h
|
||||
v_steps += v
|
||||
return h_steps, v_steps
|
||||
|
||||
|
||||
class Chapter:
|
||||
|
@ -115,16 +144,15 @@ class Chapter:
|
|||
self._update_pos(act)
|
||||
return act
|
||||
|
||||
def _update_pos(self, act: Activity):
|
||||
def _update_pos(self, act: Do):
|
||||
"""
|
||||
Update the positions from the activity.
|
||||
Implicitly increments the index.
|
||||
"""
|
||||
self.index += 1
|
||||
if act.activity is Activity.ADVANCE_UP:
|
||||
self.v_pos += act.values.get('steps', 0)
|
||||
elif act.activity is Activity.ADVANCE_LEFT:
|
||||
self.h_pos += act.values.get('steps', 0)
|
||||
h, v = act.get_steps()
|
||||
self.h_pos += h
|
||||
self.v_pos += v
|
||||
|
||||
def hasnext(self):
|
||||
"""
|
||||
|
@ -153,3 +181,262 @@ class Chapter:
|
|||
self._update_pos(act)
|
||||
return {'h_steps': self.h_pos - h_pos, 'v_steps': self.v_pos - v_pos}
|
||||
|
||||
|
||||
def _get_sound(language, **kwargs):
|
||||
"""
|
||||
Select the right sound depending on the language
|
||||
"""
|
||||
sound = kwargs.get(language, kwargs.get(Language.NOT_SET.value, None))
|
||||
if sound is None:
|
||||
logger.debug(f'_get_sound(language={language}) Could not find sound, returning None.')
|
||||
|
||||
return sound
|
||||
|
||||
|
||||
class Storyboard:
|
||||
def __init__(self, *story: List[Do]) -> None:
|
||||
self.story = story
|
||||
self.hal = None
|
||||
|
||||
self._index = 0 # The storyboard index of the current chapter to play
|
||||
self._next_chapter = 0 # The storyboard index of the next chapter to play
|
||||
self._chapter_set = False # `True` if the next chapter has been set
|
||||
|
||||
self.MOVE = False # self.move is reset to this value
|
||||
self._move = self.MOVE
|
||||
|
||||
self._lang = Language.NOT_SET
|
||||
|
||||
self.ACTIVITY_SELECTOR = None
|
||||
|
||||
@property
|
||||
def move(self) -> bool:
|
||||
return self._move
|
||||
|
||||
@move.setter
|
||||
def move(self, move: bool):
|
||||
if move is None:
|
||||
self._move = self.MOVE
|
||||
else:
|
||||
self._move = move
|
||||
|
||||
@property
|
||||
def language(self) -> Language:
|
||||
return self._lang
|
||||
|
||||
@language.setter
|
||||
def language(self, language: Language):
|
||||
self._lang = language
|
||||
|
||||
@property
|
||||
def next_chapter(self):
|
||||
return self._next_chapter
|
||||
|
||||
@next_chapter.setter
|
||||
def next_chapter(self, next_chapter):
|
||||
self._chapter_set = True
|
||||
self.move = None # Reset to default value
|
||||
self._next_chapter = next_chapter
|
||||
|
||||
def hasnext(self):
|
||||
return self._index is not None
|
||||
|
||||
def _option_callback(self, selection: Select):
|
||||
"""
|
||||
Return a callback for the appropriate option and parameters.
|
||||
Callbacks set the properties of `Statemachine` to determine it's behaviour.
|
||||
"""
|
||||
# rewind = selection.values.get('rewind', Option.REPEAT.value['rewind'])
|
||||
# next_chapter = selection.values.get('chapter', Option.GOTO.value['chapter'])
|
||||
# shutdown = selection.values.get('shutdown', Option.QUIT.value['shutdown'])
|
||||
|
||||
def _continue(**kwargs):
|
||||
"""
|
||||
Continue in the Storyboard. Prepare advancing to the next chapter.
|
||||
"""
|
||||
logger.debug('User selected continue')
|
||||
if len(self.story) > (self._index + 1):
|
||||
self.next_chapter = self._index + 1
|
||||
else:
|
||||
self.next_chapter = None
|
||||
|
||||
def _repeat(rewind: bool=None, **kwargs):
|
||||
"""
|
||||
Repeat the current chapter. Do not rewind if the selection says so.
|
||||
"""
|
||||
logger.debug('User selected repeat')
|
||||
self.next_chapter = self._index
|
||||
self.move = rewind
|
||||
|
||||
def _goto(next_chapter: int=None, **kwargs):
|
||||
"""
|
||||
Jump to a specified chapter.
|
||||
"""
|
||||
logger.debug(f'User selected goto {next_chapter}')
|
||||
self.next_chapter = next_chapter
|
||||
|
||||
def _quit(**kwargs):
|
||||
logger.debug('User selected quit')
|
||||
self.next_chapter = None
|
||||
|
||||
return {
|
||||
Option.CONTINUE: _continue,
|
||||
Option.REPEAT: _repeat,
|
||||
Option.GOTO: _goto,
|
||||
Option.QUIT: _quit,
|
||||
None: None
|
||||
}[selection.option]
|
||||
|
||||
def play_chapter(self):
|
||||
"""
|
||||
Play the chapter specified by self.chapter
|
||||
"""
|
||||
logger.debug(f'playing chapter {self._index}')
|
||||
|
||||
if self.hal is None:
|
||||
raise ConfigurationException('Set Storyboard.hal before calling Storyboard.play_chapter()')
|
||||
|
||||
if self._index is None:
|
||||
# Reached end of story
|
||||
return
|
||||
|
||||
def _play_sound(hal, **kwargs):
|
||||
"""
|
||||
Handle Activity.PLAY_SOUND
|
||||
"""
|
||||
logger.debug(f'Storyboard._play_sound({kwargs})')
|
||||
play_sound(hal, sound=_get_sound(language=self.language, **kwargs), **kwargs)
|
||||
|
||||
def _wait_for_input(hal, sound=None, **kwargs):
|
||||
"""
|
||||
Handle Activity.WAIT_FOR_INPUT
|
||||
"""
|
||||
logger.debug(f'Storyboard._wait_for_input({kwargs})')
|
||||
wait_for_input(hal=hal,
|
||||
blue_cb = self._option_callback(kwargs['on_blue']),
|
||||
red_cb = self._option_callback(kwargs['on_red']),
|
||||
yellow_cb = self._option_callback(kwargs['on_yellow']),
|
||||
green_cb = self._option_callback(kwargs['on_green']),
|
||||
timeout_cb = self._option_callback(kwargs['on_timeout']),
|
||||
sound = _get_sound(language=self.language, **kwargs),
|
||||
**kwargs)
|
||||
|
||||
def _parallel(hal, activities: List[Do], **kwargs):
|
||||
"""
|
||||
Handle Activity.PARALLEL
|
||||
"""
|
||||
logger.debug(f'Storyboard._parallel({activities})')
|
||||
for paract in activities:
|
||||
self.ACTIVITY_SELECTOR[paract.activity](hal, do_now=False, **paract.values)
|
||||
|
||||
do_it(self.hal)
|
||||
|
||||
def _move(hal, do_now=True, **kwargs):
|
||||
logger.debug(f'Storyboard._move({kwargs})')
|
||||
set_movement(hal, **kwargs)
|
||||
if do_now:
|
||||
do_it(hal)
|
||||
|
||||
def _light(hal, do_now=True, **kwargs):
|
||||
logger.debug(f'Storyboard._light({kwargs})')
|
||||
set_light(hal, **kwargs)
|
||||
if do_now:
|
||||
do_it(hal)
|
||||
|
||||
def _goto(hal, index:int, **kwargs):
|
||||
"""
|
||||
Set the next chapter
|
||||
"""
|
||||
logger.debug(f'Storyboard._goto({kwargs})')
|
||||
self.next_chapter = index
|
||||
|
||||
self.ACTIVITY_SELECTOR = {
|
||||
Activity.PLAY_SOUND: _play_sound,
|
||||
Activity.WAIT_FOR_INPUT: _wait_for_input,
|
||||
Activity.PARALLEL: _parallel,
|
||||
Activity.GOTO: _goto,
|
||||
Activity.RECORD_SOUND: record_sound,
|
||||
Activity.RECORD_VIDEO: record_video,
|
||||
Activity.TAKE_PHOTO: take_photo,
|
||||
Activity.LIGHT_FRONT: _light,
|
||||
Activity.LIGHT_BACK: _light,
|
||||
Activity.ADVANCE_UP: _move,
|
||||
Activity.ADVANCE_LEFT: _move,
|
||||
}
|
||||
|
||||
if self._index < len(self.story):
|
||||
chapter = self.story[self._index]
|
||||
|
||||
while chapter.hasnext():
|
||||
act = next(chapter)
|
||||
logger.debug(f'next activity {act.activity}')
|
||||
try:
|
||||
self.ACTIVITY_SELECTOR[act.activity](self.hal, **act.values)
|
||||
except KeyError as e:
|
||||
raise ConfigurationException('Missing handler for {act.activity}', e)
|
||||
|
||||
if not self._chapter_set:
|
||||
self._chapter_set = True
|
||||
self._next_chapter = self._index + 1
|
||||
|
||||
else:
|
||||
self._next_chapter = None
|
||||
|
||||
def advance_chapter(self):
|
||||
"""
|
||||
Update chapters and move the scrolls.
|
||||
Update self.chapter to self.next_chapter
|
||||
"""
|
||||
if not self._chapter_set:
|
||||
return
|
||||
elif self._index is None:
|
||||
return
|
||||
elif self._next_chapter is not None:
|
||||
diff = self._next_chapter - self._index
|
||||
h_steps = 0
|
||||
v_steps = 0
|
||||
if diff < 0:
|
||||
"""
|
||||
Rewind all chapters up to target
|
||||
"""
|
||||
for ch in self.story[self._next_chapter:self._index]:
|
||||
steps = ch.rewind()
|
||||
h_steps += steps['h_steps']
|
||||
v_steps += steps['v_steps']
|
||||
|
||||
elif diff > 0:
|
||||
"""
|
||||
Skip all chapters up to target
|
||||
"""
|
||||
for ch in self.story[self._index:self._next_chapter]:
|
||||
steps = ch.skip()
|
||||
h_steps += steps['h_steps']
|
||||
v_steps += steps['v_steps']
|
||||
else:
|
||||
"""
|
||||
Rewind current chapter
|
||||
"""
|
||||
steps = self.story[self._index].rewind()
|
||||
h_steps = steps['h_steps']
|
||||
v_steps = steps['v_steps']
|
||||
|
||||
if self.move:
|
||||
set_movement(self.hal, scroll=Scrolls.HORIZONTAL, steps=h_steps)
|
||||
set_movement(self.hal, scroll=Scrolls.VERTICAL, steps=v_steps)
|
||||
do_it(self.hal)
|
||||
|
||||
logger.debug(f'Setting chapter (cur: {self._index}) to {self._next_chapter}.')
|
||||
self._index = self._next_chapter
|
||||
self._chapter_set = False
|
||||
|
||||
def rewind(self):
|
||||
if self.hal is None:
|
||||
raise ConfigurationException('Set Storyboard.hal before calling Storyboard.rewind()')
|
||||
|
||||
if self.move:
|
||||
rewind(self.hal)
|
||||
|
||||
for chapter in self.story:
|
||||
chapter.rewind()
|
||||
|
||||
self._index = self._next_chapter = 0
|
||||
|
|
Loading…
Reference in a new issue