Going through TODOs for SIBI.
Implemented, untested: - start button - record sounds as mp3 (using pydub) - video and audio converted to mp4 - added new state REWIND
This commit is contained in:
parent
16165b61bd
commit
711454a06c
7 changed files with 86 additions and 71 deletions
|
@ -80,14 +80,15 @@ class StoryFile(FileHandle):
|
|||
FileHandle.__init__(self, name, FileType.STORY)
|
||||
|
||||
|
||||
REC_NAME = RecFile('name.wav')
|
||||
REC_MY_IBK = RecFile('my_ibk.wav')
|
||||
REC_NAME = RecFile('name.mp3')
|
||||
REC_MY_IBK = RecFile('my_ibk.mp3')
|
||||
REC_PORTRAIT = RecFile('portrait.jpg')
|
||||
REC_CITY_NAME = RecFile('city_name.wav')
|
||||
REC_CITY_DESC = RecFile('city_description.wav')
|
||||
REC_CITY_SOUND = RecFile('city_sound.wav')
|
||||
REC_CITY_NAME = RecFile('city_name.mp3')
|
||||
REC_CITY_DESC = RecFile('city_description.mp3')
|
||||
REC_CITY_SOUND = RecFile('city_sound.mp3')
|
||||
REC_DRAW_CITY = RecFile('city_video.h264')
|
||||
REC_CITY_PHOTO = RecFile('city_drawing.jpg')
|
||||
REC_MERGED_VIDEO = RecFile('video.mp4')
|
||||
|
||||
SFX_ERROR = SfxFile('error')
|
||||
SFX_ERROR_DE = SfxFile('error-de')
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# GPIO pin definitions
|
||||
|
||||
BTN_START = 26 # "Start" button (begin the performance) TODO review
|
||||
|
||||
BTN_BACK_GPIO = 14 # "Back" button (user input)
|
||||
BTN_FORWARD_GPIO = 18 # "Forward" button (user input)
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ from time import sleep
|
|||
from enum import Enum
|
||||
|
||||
from typing import Any, List
|
||||
from scipy.io.wavfile import write as writewav
|
||||
# from scipy.io.wavfile import write as writewav
|
||||
import pydub
|
||||
|
||||
import sounddevice as sd
|
||||
import soundfile as sf
|
||||
|
@ -13,7 +14,7 @@ import numpy as np
|
|||
from . import gpio_pins
|
||||
|
||||
from picamera import PiCamera
|
||||
from gpiozero import Button, OutputDevice, PWMOutputDevice, PWMLED
|
||||
from gpiozero import Button
|
||||
|
||||
import serial
|
||||
|
||||
|
@ -53,6 +54,8 @@ class PizzaHAL:
|
|||
def __init__(self, serialdev: str = SERIAL_DEV, baudrate: int = SERIAL_BAUDRATE):
|
||||
self.serialcon = serial.Serial(serialdev, baudrate=baudrate, timeout=None)
|
||||
|
||||
self.btn_start = Button(gpio_pins.BTN_START)
|
||||
|
||||
self.camera = None
|
||||
self.soundcache = {}
|
||||
|
||||
|
@ -120,7 +123,8 @@ def turn_off(hal: PizzaHAL):
|
|||
|
||||
|
||||
def wait_for_input(hal: PizzaHAL, go_callback: Any,
|
||||
back_callback: Any, **kwargs):
|
||||
back_callback: Any, to_callback: Any,
|
||||
timeout=120, **kwargs):
|
||||
"""
|
||||
Blink leds on buttons. Wait until the user presses a button, then execute
|
||||
the appropriate callback
|
||||
|
@ -128,12 +132,16 @@ def wait_for_input(hal: PizzaHAL, go_callback: Any,
|
|||
:param hal: The hardware abstraction object
|
||||
:param go_callback: called when button 'go' is pressed
|
||||
:param back_callback: called whan button 'back' is pressed
|
||||
:param to_callback: called on timeout
|
||||
:param timeout: inactivity timeout in seconds (default 120)
|
||||
"""
|
||||
resp = hal.send_cmd(SerialCommands.USER_INTERACTION)
|
||||
resp = hal.send_cmd(SerialCommands.USER_INTERACTION, timeout)
|
||||
if resp == 'B':
|
||||
go_callback(**kwargs)
|
||||
elif resp == 'R':
|
||||
back_callback(**kwargs)
|
||||
else:
|
||||
to_callback(**kwargs)
|
||||
|
||||
|
||||
@blocking
|
||||
|
@ -200,8 +208,13 @@ def record_sound(hal: PizzaHAL, filename: Any, duration: int,
|
|||
myrecording = sd.rec(int(duration * AUDIO_REC_SR),
|
||||
samplerate=AUDIO_REC_SR,
|
||||
channels=2)
|
||||
# TODO user interaction instead
|
||||
sd.wait() # Wait until recording is finished
|
||||
writewav(str(filename), AUDIO_REC_SR, myrecording)
|
||||
# TODO test
|
||||
myrecording = np.int16(myrecording)
|
||||
song = pydub.AudioSegment(myrecording.tobytes(), frame_rate=AUDIO_REC_SR, sample_width=2, channels=2)
|
||||
song.export(str(filename), format="mp3", bitrate="320k")
|
||||
# ALTERNATIVE writewav(str(filename), AUDIO_REC_SR, myrecording)
|
||||
if cache:
|
||||
hal.soundcache[str(filename)] = (myrecording, AUDIO_REC_SR)
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import logging
|
||||
import os.path
|
||||
import threading
|
||||
|
||||
from typing import Any
|
||||
|
||||
from time import sleep
|
||||
|
||||
from enum import Enum, auto
|
||||
from subprocess import call
|
||||
|
||||
from pizzactrl import fs_names, sb_dummy, sb_de_alt
|
||||
from pizzactrl import fs_names, sb_dummy
|
||||
from .storyboard import Activity
|
||||
|
||||
from .hal_serial import play_sound, take_photo, record_video, record_sound, turn_off, \
|
||||
PizzaHAL, init_camera, init_sounds, wait_for_input, \
|
||||
light_layer, backlight, move_vert, move_hor, rewind
|
||||
|
@ -24,6 +23,7 @@ class State(Enum):
|
|||
IDLE_START = auto()
|
||||
PLAY = auto()
|
||||
PAUSE = auto()
|
||||
REWIND = auto()
|
||||
IDLE_END = auto()
|
||||
SHUTDOWN = auto()
|
||||
ERROR = -1
|
||||
|
@ -54,7 +54,8 @@ class Statemachine:
|
|||
def __init__(self,
|
||||
story_de: Any=None,
|
||||
story_en: Any=None,
|
||||
move: bool = False):
|
||||
move: bool = False,
|
||||
loop: bool = True):
|
||||
self.state = State.POWER_ON
|
||||
self.hal = PizzaHAL()
|
||||
self.story = None
|
||||
|
@ -64,6 +65,7 @@ class Statemachine:
|
|||
self.lang = Language.NOT_SET
|
||||
self.move = move
|
||||
self.test = False
|
||||
self.loop = loop
|
||||
|
||||
def run(self):
|
||||
logger.debug(f'Run(state={self.state})')
|
||||
|
@ -118,66 +120,50 @@ class Statemachine:
|
|||
# check scroll positions and rewind if necessary
|
||||
turn_off(self.hal)
|
||||
|
||||
#rewind(self.hal.motor_ud, self.hal.ud_sensor)
|
||||
|
||||
if not os.path.exists(fs_names.USB_STICK):
|
||||
logger.warning('USB-Stick not found.')
|
||||
self.state = State.ERROR
|
||||
return
|
||||
|
||||
# Callback for start when blue button is held
|
||||
self.hal.btn_start.when_activated = self._start_or_rewind
|
||||
|
||||
# play a sound if everything is alright
|
||||
play_sound(self.hal, fs_names.SFX_POST_OK)
|
||||
|
||||
# Callback for start when blue button is held
|
||||
# TODO add start button
|
||||
#self.hal.btn_forward.when_deactivated = self._start
|
||||
|
||||
self.state = State.IDLE_START
|
||||
|
||||
def _idle_start(self):
|
||||
"""
|
||||
Device is armed. Wait for user to hold blue button to start
|
||||
Device is armed. Wait for user to press start button
|
||||
"""
|
||||
pass
|
||||
|
||||
def _start(self):
|
||||
def _start_or_rewind(self):
|
||||
"""
|
||||
Start playback when blue button is held for 3s
|
||||
"""
|
||||
t = 0.
|
||||
while (self.hal.btn_forward.inactive_time < 3.0) and \
|
||||
not self.hal.btn_forward.is_active:
|
||||
t = self.hal.btn_forward.inactive_time
|
||||
Callback function.
|
||||
|
||||
if t > 3.0:
|
||||
self.hal.btn_forward.when_deactivated = None
|
||||
if not self.hal.btn_back.is_active:
|
||||
self.alt = True
|
||||
If statemachine is in idle state, start playback when start
|
||||
button is pressed (released).
|
||||
|
||||
If statemachine is playing, trigger rewind and start fresh
|
||||
"""
|
||||
if self.state == State.IDLE_START:
|
||||
self.state = State.PLAY
|
||||
return
|
||||
if self.state == State.PLAY:
|
||||
self.state = State.REWIND
|
||||
|
||||
def _play(self):
|
||||
"""
|
||||
Run the storyboard
|
||||
"""
|
||||
logger.debug(f'play')
|
||||
if not self.alt:
|
||||
play_sound(self.hal, fs_names.SND_SELECT_LANG)
|
||||
wait_for_input(self.hal,
|
||||
self._lang_de,
|
||||
self._lang_en)
|
||||
|
||||
sleep(0.5)
|
||||
if self.test:
|
||||
self.story = sb_dummy.STORYBOARD
|
||||
else:
|
||||
self.story = sb_de_alt.STORYBOARD
|
||||
|
||||
try:
|
||||
if self.story is None:
|
||||
self.story = sb_dummy.STORYBOARD
|
||||
except AttributeError:
|
||||
pass
|
||||
finally:
|
||||
if self.test:
|
||||
self.story = sb_dummy.STORYBOARD
|
||||
# TODO reenable language selection
|
||||
self.story = self.story_en
|
||||
|
||||
for chapter in iter(self.story):
|
||||
logger.debug(f'playing chapter {chapter}')
|
||||
|
@ -187,16 +173,17 @@ class Statemachine:
|
|||
if act.activity is Activity.WAIT_FOR_INPUT:
|
||||
wait_for_input(hal=self.hal,
|
||||
go_callback=chapter.mobilize,
|
||||
back_callback=chapter.rewind)
|
||||
elif act.activity is Activity.ADVANCE_UP:
|
||||
if chapter.move and self.move:
|
||||
logger.debug(
|
||||
f'advance({self.hal.motor_ud}, '
|
||||
f'{self.hal.ud_sensor})')
|
||||
advance(motor=self.hal.motor_ud,
|
||||
sensor=self.hal.ud_sensor)
|
||||
elif not self.move:
|
||||
play_sound(self.hal, fs_names.StoryFile('stop'))
|
||||
back_callback=chapter.rewind,
|
||||
to_callback=self._start_or_rewind)
|
||||
# elif act.activity is Activity.ADVANCE_UP:
|
||||
# if chapter.move and self.move:
|
||||
# logger.debug(
|
||||
# f'advance({self.hal.motor_ud}, '
|
||||
# f'{self.hal.ud_sensor})')
|
||||
# advance(motor=self.hal.motor_ud,
|
||||
# sensor=self.hal.ud_sensor)
|
||||
# elif not self.move:
|
||||
# play_sound(self.hal, fs_names.StoryFile('stop'))
|
||||
else:
|
||||
try:
|
||||
{
|
||||
|
@ -206,22 +193,35 @@ class Statemachine:
|
|||
Activity.TAKE_PHOTO: take_photo,
|
||||
Activity.LIGHT_LAYER: light_layer,
|
||||
Activity.LIGHT_BACK: backlight,
|
||||
# Activity.ADVANCE_UP: noop
|
||||
|
||||
Activity.ADVANCE_UP: move_vert,
|
||||
Activity.ADVANCE_LEFT: move_hor
|
||||
}[act.activity](self.hal, **act.values)
|
||||
except KeyError:
|
||||
logger.exception('Caught KeyError, ignoring...')
|
||||
pass
|
||||
|
||||
self.state = State.IDLE_END
|
||||
self.state = State.REWIND
|
||||
|
||||
def _rewind(self):
|
||||
"""
|
||||
Rewind all scrolls, post-process videos
|
||||
"""
|
||||
# postprocessing
|
||||
cmdstring = f'MP4Box -add {fs_names.REC_CITY_DESC} -add{fs_names.REC_DRAW_CITY} {fs_names.REC_MERGED_VIDEO}'
|
||||
call([cmdstring], shell=True)
|
||||
|
||||
if self.move:
|
||||
rewind(self.hal)
|
||||
|
||||
if self.loop:
|
||||
self.state = State.IDLE_START
|
||||
else:
|
||||
self.state = State.IDLE_END
|
||||
|
||||
def _idle_end(self):
|
||||
"""
|
||||
Initialize shutdown
|
||||
"""
|
||||
if self.move:
|
||||
rewind(self.hal.motor_ud, self.hal.ud_sensor)
|
||||
|
||||
self.state = State.SHUTDOWN
|
||||
|
||||
def _shutdown(self):
|
||||
|
|
|
@ -7,7 +7,7 @@ class Activity(Enum):
|
|||
RECORD_SOUND = {'duration': 0.0, 'filename': '', 'cache': False}
|
||||
RECORD_VIDEO = {'duration': 0.0, 'filename': ''}
|
||||
TAKE_PHOTO = {'filename': ''}
|
||||
ADVANCE_UP = {'speed': 0.3, 'direction': True}
|
||||
ADVANCE_UP = {'speed': 0.3, 'direction': True, 'steps': 100}
|
||||
ADVANCE_LEFT = {'speed': 0.3, 'direction': True, 'steps': 180} # TODO set right number of steps
|
||||
LIGHT_LAYER = {'intensity': 1.0, 'fade': 0.0, 'layer': True}
|
||||
LIGHT_BACK = {'intensity': 1.0, 'fade': 0.0}
|
||||
|
@ -40,8 +40,6 @@ class Chapter:
|
|||
if self.pos >= len(self.activities):
|
||||
raise StopIteration
|
||||
act = self.activities[self.pos]
|
||||
if act.activity is Activity.ADVANCE_UP: # TODO add ADVANCE_LEFT
|
||||
self.move_ud += 1
|
||||
self.pos += 1
|
||||
return act
|
||||
|
||||
|
@ -50,7 +48,6 @@ class Chapter:
|
|||
|
||||
def rewind(self, **kwargs):
|
||||
self.move = False
|
||||
self.move_ud = 0
|
||||
self.pos = 0
|
||||
|
||||
def mobilize(self, **kwargs):
|
||||
|
|
|
@ -6,3 +6,4 @@ sounddevice
|
|||
soundfile
|
||||
scipy
|
||||
pyserial
|
||||
pydub
|
3
setup.py
3
setup.py
|
@ -30,7 +30,8 @@ with open('pizzactrl/__init__.py', 'rb') as f:
|
|||
'sounddevice',
|
||||
'soundfile',
|
||||
'scipy',
|
||||
'pyserial'
|
||||
'pyserial',
|
||||
'pydub'
|
||||
],
|
||||
|
||||
entry_points='''
|
||||
|
|
Loading…
Reference in a new issue