pizzabox-main/pizzactrl/statemachine.py
jpunkt d837134db5 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
2022-01-17 20:39:36 +01:00

207 lines
5.5 KiB
Python

import logging
import os.path
from typing import Any
from time import sleep
from enum import Enum, auto
from subprocess import call
from pizzactrl import SOUNDS_PATH, fs_names
from pizzactrl.hal import ScrollSensor
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__)
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 Statemachine:
def __init__(self,
hal: PizzaHAL,
story: Storyboard,
default_lang=Language.NOT_SET,
move: bool = False,
loop: bool = True,
test: bool = False):
self.hal = hal
self.LANG = default_lang
self.lang = None
self.story = story
self.story.MOVE = move
self.test = test
self.loop = loop
self.state = State.POWER_ON
def run(self):
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:
logger.debug('An error occurred. Trying to notify user...')
if self.lang is Language.DE:
play_sound(self.hal, fs_names.SFX_ERROR_DE)
elif self.lang is Language.EN:
play_sound(self.hal, fs_names.SFX_ERROR_EN)
else:
play_sound(self.hal, fs_names.SFX_ERROR)
self._shutdown()
def _power_on(self):
"""
Initialize hal callbacks, load sounds
"""
# 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()
self.hal.init_camera()
self.state = State.POST
def _post(self):
"""
Power on self test.
"""
if (not self.test) and (not os.path.exists(fs_names.USB_STICK)):
logger.warning('USB-Stick not found.')
self.state = State.ERROR
return
# TODO set RPi_HELO pins, wait for response
# Callback for start when blue button is held
# self.hal.btn_start.when_activated = self._start_or_rewind
# logger.debug('start button callback activated')
try:
self.hal.init_connection()
except SerialCommunicationError as e:
self.state = State.ERROR
logger.exception(e)
return
# play a sound if everything is alright
play_sound(self.hal, fs_names.SFX_POST_OK)
if self.test:
self.state = State.LANGUAGE_SELECT
else:
self.state = State.IDLE_START
def _idle_start(self):
"""
Device is armed. Wait for user to press start button
"""
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
"""
self.story.hal = self.hal
while self.story.hasnext():
self.story.play_chapter()
self.story.advance_chapter()
self.state = State.POST_PROCESS
def _post_process(self):
"""
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)
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
else:
self.state = State.IDLE_END
def _idle_end(self):
"""
Initialize shutdown
"""
self.state = State.SHUTDOWN
def _shutdown(self):
"""
Clean up, end execution
"""
del self.hal