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:
jpunkt 2021-11-01 17:00:40 +01:00
parent 16165b61bd
commit 711454a06c
7 changed files with 86 additions and 71 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -5,4 +5,5 @@ click
sounddevice
soundfile
scipy
pyserial
pyserial
pydub

View File

@ -30,7 +30,8 @@ with open('pizzactrl/__init__.py', 'rb') as f:
'sounddevice',
'soundfile',
'scipy',
'pyserial'
'pyserial',
'pydub'
],
entry_points='''