Implemented serial communication handshake.
Added button response commands.
This commit is contained in:
parent
fec7aa6d97
commit
3b60d7511e
3 changed files with 51 additions and 33 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -130,3 +130,4 @@ dmypy.json
|
||||||
|
|
||||||
# Pycharm settings
|
# Pycharm settings
|
||||||
.idea
|
.idea
|
||||||
|
workspace.code-workspace
|
||||||
|
|
|
@ -23,8 +23,10 @@ logger = logging.getLogger(__name__)
|
||||||
VIDEO_RES = (1920, 1080) # Video Resolution
|
VIDEO_RES = (1920, 1080) # Video Resolution
|
||||||
PHOTO_RES = (2592, 1944) # Photo Resolution
|
PHOTO_RES = (2592, 1944) # Photo Resolution
|
||||||
AUDIO_REC_SR = 44100 # Audio Recording Samplerate
|
AUDIO_REC_SR = 44100 # Audio Recording Samplerate
|
||||||
SERIAL_DEV = '/dev/serial0'
|
|
||||||
SERIAL_BAUDRATE = 115200
|
SERIAL_DEV = '/dev/serial0' # Serial port to use
|
||||||
|
SERIAL_BAUDRATE = 115200 # Serial connection baud rate
|
||||||
|
SERIAL_CONN_TIMEOUT = 60 # Serial connection read timeout
|
||||||
|
|
||||||
|
|
||||||
class SerialCommands(Enum):
|
class SerialCommands(Enum):
|
||||||
|
@ -40,6 +42,11 @@ class SerialCommands(Enum):
|
||||||
FRONTLIGHT = b'F'
|
FRONTLIGHT = b'F'
|
||||||
|
|
||||||
USER_INTERACT = b'U'
|
USER_INTERACT = b'U'
|
||||||
|
|
||||||
|
RESP_BLUE = b'X'
|
||||||
|
RESP_RED = b'O'
|
||||||
|
RESP_YELLOW = b'Y'
|
||||||
|
RESP_GREEN = b'N'
|
||||||
|
|
||||||
RECORD = b'C'
|
RECORD = b'C'
|
||||||
REWIND = b'R'
|
REWIND = b'R'
|
||||||
|
@ -47,6 +54,10 @@ class SerialCommands(Enum):
|
||||||
EOT = b'\n'
|
EOT = b'\n'
|
||||||
|
|
||||||
|
|
||||||
|
class SerialCommunicationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PizzaHAL:
|
class PizzaHAL:
|
||||||
"""
|
"""
|
||||||
This class holds a represenation of the pizza box hardware and provides
|
This class holds a represenation of the pizza box hardware and provides
|
||||||
|
@ -60,18 +71,44 @@ class PizzaHAL:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, serialdev: str = SERIAL_DEV, baudrate: int = SERIAL_BAUDRATE):
|
def __init__(self, serialdev: str = SERIAL_DEV, baudrate: int = SERIAL_BAUDRATE, timeout: float = SERIAL_CONN_TIMEOUT):
|
||||||
self.serialcon = serial.Serial(serialdev, baudrate=baudrate, timeout=None)
|
self.serialcon = serial.Serial(serialdev, baudrate=baudrate, timeout=timeout)
|
||||||
|
|
||||||
self.btn_start = Button(gpio_pins.BTN_START)
|
self.btn_start = Button(gpio_pins.BTN_START)
|
||||||
|
|
||||||
self.camera = None
|
self.camera = None
|
||||||
self.soundcache = {}
|
self.soundcache = {}
|
||||||
|
|
||||||
def send_cmd(self, command: SerialCommands, *options):
|
self.connected = False
|
||||||
|
|
||||||
|
def init_connection(self):
|
||||||
|
self.serialcon.write(SerialCommands.HELLO.value + SerialCommands.EOT.value)
|
||||||
|
resp = self.serialcon.read_until()
|
||||||
|
if resp == (SerialCommands.HELLO.value + SerialCommands.EOT.value):
|
||||||
|
self.serialcon.write(SerialCommands.ALREADY_CONNECTED.value + SerialCommands.EOT.value)
|
||||||
|
elif resp == (SerialCommands.ALREADY_CONNECTED.value + SerialCommands.EOT.value):
|
||||||
|
logger.warn('Serial Connection received ALREADY CONNECTED as response to HELLO. Assuming connection ok.')
|
||||||
|
self.connected = True
|
||||||
|
return
|
||||||
|
elif resp == b'':
|
||||||
|
raise SerialCommunicationError('Timeout on initializing connection.')
|
||||||
|
else:
|
||||||
|
raise SerialCommunicationError(f'Serial Connection received invalid response to HELLO: {resp}')
|
||||||
|
resp = self.serialcon.read_until()
|
||||||
|
if resp == (SerialCommands.ALREADY_CONNECTED.value + SerialCommands.EOT.value):
|
||||||
|
self.connected == True
|
||||||
|
logger.info('Serial Connection established')
|
||||||
|
elif resp == b'':
|
||||||
|
raise SerialCommunicationError('Timeout on initializing connection.')
|
||||||
|
else:
|
||||||
|
raise SerialCommunicationError(f'Serial Connection received invalid response to ALREADY CONNECTED: {resp}')
|
||||||
|
|
||||||
|
def send_cmd(self, command: SerialCommands, *options: bytes):
|
||||||
"""
|
"""
|
||||||
Send a command and optional options. Options need to be encoded as bytes before passing.
|
Send a command and optional options. Options need to be encoded as bytes before passing.
|
||||||
"""
|
"""
|
||||||
|
if not self.connected:
|
||||||
|
raise SerialCommunicationError("Serial Communication not initialized. Call `init_connection()` before `send_cmd()`.")
|
||||||
self.serialcon.write(command.value)
|
self.serialcon.write(command.value)
|
||||||
for o in options:
|
for o in options:
|
||||||
self.serialcon.write(o)
|
self.serialcon.write(o)
|
||||||
|
@ -81,24 +118,6 @@ class PizzaHAL:
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def blocking(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def _wrapper(*args, **kwargs):
|
|
||||||
hal = kwargs.get('hal', None)
|
|
||||||
if hal is not None:
|
|
||||||
logger.debug('blocking...')
|
|
||||||
while hal.blocked:
|
|
||||||
pass
|
|
||||||
hal.blocked = True
|
|
||||||
func(*args, **kwargs)
|
|
||||||
if hal is not None:
|
|
||||||
logger.debug('unblocking')
|
|
||||||
hal.blocked = False
|
|
||||||
sleep(0.1)
|
|
||||||
return _wrapper
|
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def move_vert(hal: PizzaHAL, steps: int):
|
def move_vert(hal: PizzaHAL, steps: int):
|
||||||
"""
|
"""
|
||||||
Move the motor controlling the vertical scroll a given distance.
|
Move the motor controlling the vertical scroll a given distance.
|
||||||
|
@ -115,7 +134,6 @@ def move_hor(hal: PizzaHAL, steps: int):
|
||||||
hal.send_cmd(SerialCommands.MOTOR_H, steps.to_bytes(2, 'little', signed=True))
|
hal.send_cmd(SerialCommands.MOTOR_H, steps.to_bytes(2, 'little', signed=True))
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def rewind(hal: PizzaHAL):
|
def rewind(hal: PizzaHAL):
|
||||||
"""
|
"""
|
||||||
Rewind both scrolls.
|
Rewind both scrolls.
|
||||||
|
@ -154,7 +172,6 @@ def wait_for_input(hal: PizzaHAL, go_callback: Any,
|
||||||
to_callback(**kwargs)
|
to_callback(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def light_layer(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: float = 0.0, **kwargs):
|
def light_layer(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: float = 0.0, **kwargs):
|
||||||
"""
|
"""
|
||||||
Turn on the light to illuminate the upper scroll
|
Turn on the light to illuminate the upper scroll
|
||||||
|
@ -175,7 +192,6 @@ def light_layer(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: flo
|
||||||
int(fade * 1000).to_bytes(4, 'little'))
|
int(fade * 1000).to_bytes(4, 'little'))
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def backlight(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: float = 0.0, **kwargs):
|
def backlight(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: float = 0.0, **kwargs):
|
||||||
"""
|
"""
|
||||||
Turn on the backlight
|
Turn on the backlight
|
||||||
|
@ -195,7 +211,7 @@ def backlight(hal: PizzaHAL, r: float, g: float, b: float, w: float, fade: float
|
||||||
int(w * 255).to_bytes(1, 'little'),
|
int(w * 255).to_bytes(1, 'little'),
|
||||||
int(fade * 1000).to_bytes(4, 'little'))
|
int(fade * 1000).to_bytes(4, 'little'))
|
||||||
|
|
||||||
@blocking
|
|
||||||
def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
||||||
"""
|
"""
|
||||||
Play a sound.
|
Play a sound.
|
||||||
|
@ -213,7 +229,6 @@ def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
||||||
# sd.stop()
|
# sd.stop()
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def record_sound(hal: PizzaHAL, filename: Any, duration: float,
|
def record_sound(hal: PizzaHAL, filename: Any, duration: float,
|
||||||
cache: bool = False, **kwargs):
|
cache: bool = False, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -237,7 +252,6 @@ def record_sound(hal: PizzaHAL, filename: Any, duration: float,
|
||||||
hal.soundcache[str(filename)] = (myrecording, AUDIO_REC_SR)
|
hal.soundcache[str(filename)] = (myrecording, AUDIO_REC_SR)
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def record_video(hal: PizzaHAL, filename: Any, duration: float, **kwargs):
|
def record_video(hal: PizzaHAL, filename: Any, duration: float, **kwargs):
|
||||||
"""
|
"""
|
||||||
Record video using the camera
|
Record video using the camera
|
||||||
|
@ -252,7 +266,6 @@ def record_video(hal: PizzaHAL, filename: Any, duration: float, **kwargs):
|
||||||
hal.camera.stop_recording()
|
hal.camera.stop_recording()
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def take_photo(hal: PizzaHAL, filename: Any, **kwargs):
|
def take_photo(hal: PizzaHAL, filename: Any, **kwargs):
|
||||||
"""
|
"""
|
||||||
Take a foto with the camera
|
Take a foto with the camera
|
||||||
|
@ -264,7 +277,6 @@ def take_photo(hal: PizzaHAL, filename: Any, **kwargs):
|
||||||
hal.camera.capture(str(filename))
|
hal.camera.capture(str(filename))
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def init_sounds(hal: PizzaHAL, sounds: List):
|
def init_sounds(hal: PizzaHAL, sounds: List):
|
||||||
"""
|
"""
|
||||||
Load prerecorded Sounds into memory
|
Load prerecorded Sounds into memory
|
||||||
|
@ -281,7 +293,6 @@ def init_sounds(hal: PizzaHAL, sounds: List):
|
||||||
hal.soundcache[str(sound)] = (data, fs)
|
hal.soundcache[str(sound)] = (data, fs)
|
||||||
|
|
||||||
|
|
||||||
@blocking
|
|
||||||
def init_camera(hal: PizzaHAL):
|
def init_camera(hal: PizzaHAL):
|
||||||
if hal.camera is None:
|
if hal.camera is None:
|
||||||
hal.camera = PiCamera(sensor_mode=5)
|
hal.camera = PiCamera(sensor_mode=5)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from subprocess import call
|
||||||
|
|
||||||
from pizzactrl import fs_names, sb_dummy
|
from pizzactrl import fs_names, sb_dummy
|
||||||
from .storyboard import Activity
|
from .storyboard import Activity
|
||||||
from .hal_serial import play_sound, take_photo, record_video, record_sound, turn_off, \
|
from .hal_serial import SerialCommunicationError, play_sound, take_photo, record_video, record_sound, turn_off, \
|
||||||
PizzaHAL, init_camera, init_sounds, wait_for_input, \
|
PizzaHAL, init_camera, init_sounds, wait_for_input, \
|
||||||
light_layer, backlight, move_vert, move_hor, rewind
|
light_layer, backlight, move_vert, move_hor, rewind
|
||||||
|
|
||||||
|
@ -109,6 +109,12 @@ class Statemachine:
|
||||||
logger.debug(f'power on')
|
logger.debug(f'power on')
|
||||||
# self.hal.lid_sensor.when_pressed = self._lid_open
|
# self.hal.lid_sensor.when_pressed = self._lid_open
|
||||||
# self.hal.lid_sensor.when_released = self._lid_closed
|
# self.hal.lid_sensor.when_released = self._lid_closed
|
||||||
|
try:
|
||||||
|
self.hal.init_connection()
|
||||||
|
except SerialCommunicationError as e:
|
||||||
|
self.state = State.ERROR
|
||||||
|
logger.exception(e)
|
||||||
|
return
|
||||||
init_sounds(self.hal, load_sounds())
|
init_sounds(self.hal, load_sounds())
|
||||||
init_camera(self.hal)
|
init_camera(self.hal)
|
||||||
self.state = State.POST
|
self.state = State.POST
|
||||||
|
|
Loading…
Reference in a new issue