Implemented serial handshake with HELO pins.
Implemented stop action on lid close. Untested: statemachine rewind on lid close.
This commit is contained in:
parent
2670c3c714
commit
4f8d97a7ff
3 changed files with 132 additions and 67 deletions
|
@ -1,6 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import functools
|
|
||||||
import threading
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ import soundfile as sf
|
||||||
import pygame.mixer as mx
|
import pygame.mixer as mx
|
||||||
|
|
||||||
from picamera import PiCamera
|
from picamera import PiCamera
|
||||||
from gpiozero import Button
|
from gpiozero import Button, DigitalOutputDevice, DigitalInputDevice
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
|
@ -29,7 +28,8 @@ AUDIO_REC_SR = 44100 # Audio Recording Samplerate
|
||||||
|
|
||||||
SERIAL_DEV = '/dev/serial0' # Serial port to use
|
SERIAL_DEV = '/dev/serial0' # Serial port to use
|
||||||
SERIAL_BAUDRATE = 115200 # Serial connection baud rate
|
SERIAL_BAUDRATE = 115200 # Serial connection baud rate
|
||||||
SERIAL_CONN_TIMEOUT = 60 # Serial connection read timeout
|
SERIAL_CONN_TIMEOUT = 0.2 # Serial connection read timeout
|
||||||
|
HELO_TIMEOUT = 20
|
||||||
|
|
||||||
|
|
||||||
class Lights(Enum):
|
class Lights(Enum):
|
||||||
|
@ -47,6 +47,7 @@ class SerialCommands(Enum):
|
||||||
ALREADY_CONNECTED = b'\x01'
|
ALREADY_CONNECTED = b'\x01'
|
||||||
ERROR = b'\x02'
|
ERROR = b'\x02'
|
||||||
RECEIVED = b'\x03'
|
RECEIVED = b'\x03'
|
||||||
|
ABORT = b'\x63' # 99 decimal
|
||||||
|
|
||||||
SET_MOVEMENT = b'M'
|
SET_MOVEMENT = b'M'
|
||||||
SET_LIGHT = b'L'
|
SET_LIGHT = b'L'
|
||||||
|
@ -64,6 +65,10 @@ class SerialCommands(Enum):
|
||||||
EOT = b'\n'
|
EOT = b'\n'
|
||||||
|
|
||||||
|
|
||||||
|
class CommunicationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SerialCommunicationError(Exception):
|
class SerialCommunicationError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -84,16 +89,43 @@ class PizzaHAL:
|
||||||
def __init__(self, serialdev: str = SERIAL_DEV, baudrate: int = SERIAL_BAUDRATE, timeout: float = SERIAL_CONN_TIMEOUT):
|
def __init__(self, serialdev: str = SERIAL_DEV, baudrate: int = SERIAL_BAUDRATE, timeout: float = SERIAL_CONN_TIMEOUT):
|
||||||
self.serialcon = serial.Serial(serialdev, baudrate=baudrate, timeout=timeout)
|
self.serialcon = serial.Serial(serialdev, baudrate=baudrate, timeout=timeout)
|
||||||
|
|
||||||
|
# Lid switch with pull-up. is_pressed = True when lid is open
|
||||||
self.lid_switch = Button(LID_SWITCH)
|
self.lid_switch = Button(LID_SWITCH)
|
||||||
|
self.pin_helo1 = DigitalOutputDevice(HELO1)
|
||||||
|
self.pin_helo2 = DigitalInputDevice(HELO2)
|
||||||
|
|
||||||
self.camera = None
|
self.camera = None
|
||||||
self.soundcache = {}
|
self.soundcache = {}
|
||||||
|
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lid_open(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True when the lid is open
|
||||||
|
"""
|
||||||
|
return self.lid_switch.is_pressed
|
||||||
|
|
||||||
def init_connection(self):
|
def init_connection(self):
|
||||||
|
"""
|
||||||
|
Set HELO1 pin to `High`, wait for HELO2 to be set `High` by microcontroller.
|
||||||
|
|
||||||
|
Then perform serial handshake.
|
||||||
|
"""
|
||||||
|
self.pin_helo1.on()
|
||||||
|
timer = 0
|
||||||
|
while (not self.pin_helo2.value) and (timer < HELO_TIMEOUT):
|
||||||
|
sleep(0.1)
|
||||||
|
timer += 1
|
||||||
|
if not (timer % 100):
|
||||||
|
logger.info(f'Waiting for connection ({timer / 10}s)')
|
||||||
|
|
||||||
|
if not self.pin_helo2.value:
|
||||||
|
raise CommunicationError('Microcontroller did not respond to HELO pin.')
|
||||||
|
|
||||||
self.serialcon.write(SerialCommands.HELLO.value + SerialCommands.EOT.value)
|
self.serialcon.write(SerialCommands.HELLO.value + SerialCommands.EOT.value)
|
||||||
resp = self.serialcon.read_until()
|
resp = self.serialcon.read_until()
|
||||||
|
|
||||||
if resp == (SerialCommands.HELLO.value + SerialCommands.EOT.value):
|
if resp == (SerialCommands.HELLO.value + SerialCommands.EOT.value):
|
||||||
self.serialcon.write(SerialCommands.ALREADY_CONNECTED.value + SerialCommands.EOT.value)
|
self.serialcon.write(SerialCommands.ALREADY_CONNECTED.value + SerialCommands.EOT.value)
|
||||||
resp = self.serialcon.read_until()
|
resp = self.serialcon.read_until()
|
||||||
|
@ -109,6 +141,7 @@ class PizzaHAL:
|
||||||
raise SerialCommunicationError('Timeout on initializing connection.')
|
raise SerialCommunicationError('Timeout on initializing connection.')
|
||||||
else:
|
else:
|
||||||
raise SerialCommunicationError(f'Serial Connection received invalid response to HELLO: {resp}')
|
raise SerialCommunicationError(f'Serial Connection received invalid response to HELLO: {resp}')
|
||||||
|
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
|
||||||
def init_sounds(self, sounds: List=None):
|
def init_sounds(self, sounds: List=None):
|
||||||
|
@ -118,18 +151,15 @@ class PizzaHAL:
|
||||||
:param hal:
|
:param hal:
|
||||||
:param sounds: A list of sound files
|
:param sounds: A list of sound files
|
||||||
"""
|
"""
|
||||||
# if self.soundcache is None:
|
if self.soundcache is None:
|
||||||
# self.soundcache = {}
|
self.soundcache = {}
|
||||||
|
|
||||||
if not mx.get_init():
|
if not mx.get_init():
|
||||||
mx.init()
|
mx.init()
|
||||||
|
|
||||||
# if sounds is not None:
|
if sounds is not None:
|
||||||
# for sound in sounds:
|
for sound in sounds:
|
||||||
# # Extract data and sampling rate from file
|
self.soundcache[str(sound)] = mx.Sound(str(sound))
|
||||||
# # 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):
|
def init_camera(self):
|
||||||
if self.camera is None:
|
if self.camera is None:
|
||||||
|
@ -143,28 +173,48 @@ class PizzaHAL:
|
||||||
if mx.get_busy():
|
if mx.get_busy():
|
||||||
mx.stop()
|
mx.stop()
|
||||||
|
|
||||||
def send_cmd(self, command: SerialCommands, *options):
|
def send_cmd(self, command: SerialCommands, *options, ignore_lid: bool=False):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
|
This function is blocking.
|
||||||
|
|
||||||
|
Returns the response from the microcontroller or `None` if the lid was closed and `ignore_lid` is `False`.
|
||||||
|
|
||||||
|
Raises a SerialCommunicationError if serial connection was not initialized or a response other than
|
||||||
|
SerialCommands.RECEIVED was received.
|
||||||
|
|
||||||
|
Raises a CommunicationError if the HELO2 pin goes low while waiting for response.
|
||||||
"""
|
"""
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
raise SerialCommunicationError("Serial Communication not initialized. Call `init_connection()` before `send_cmd()`.")
|
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)
|
||||||
self.serialcon.write(SerialCommands.EOT.value)
|
|
||||||
resp = self.serialcon.read_until()
|
|
||||||
|
|
||||||
while resp == b'':
|
self.serialcon.write(SerialCommands.EOT.value)
|
||||||
|
resp = b''
|
||||||
|
while resp is b'':
|
||||||
# If serial communication timeout occurs, response is empty.
|
# If serial communication timeout occurs, response is empty.
|
||||||
# Read again to allow for longer waiting times
|
# Read again to allow for longer waiting times
|
||||||
|
if not self.pin_helo2.value:
|
||||||
|
raise CommunicationError('Pin HELO2 LOW. Microcontroller in error state or lost connection.')
|
||||||
|
if (not ignore_lid) and (not self.lid_open):
|
||||||
|
logger.info('Lid closed while processing command. Returning None.')
|
||||||
|
return None
|
||||||
resp = self.serialcon.read_until()
|
resp = self.serialcon.read_until()
|
||||||
|
|
||||||
|
logger.debug(f'hal.send_cmd() received {resp}')
|
||||||
|
|
||||||
if not resp.startswith(SerialCommands.RECEIVED.value):
|
if not resp.startswith(SerialCommands.RECEIVED.value):
|
||||||
raise SerialCommunicationError(f'Serial Communication received unexpected response: {resp}')
|
raise SerialCommunicationError(f'Serial Communication received unexpected response: {resp}')
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
def flush_serial(self):
|
||||||
|
self.serialcon.read_all()
|
||||||
|
|
||||||
|
|
||||||
def set_movement(hal: PizzaHAL,
|
def set_movement(hal: PizzaHAL,
|
||||||
scroll: Scrolls,
|
scroll: Scrolls,
|
||||||
|
@ -187,16 +237,16 @@ def rewind(hal: PizzaHAL, **kwargs):
|
||||||
Rewind both scrolls.
|
Rewind both scrolls.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
hal.send_cmd(SerialCommands.REWIND)
|
hal.send_cmd(SerialCommands.REWIND, ignore_lid=True)
|
||||||
|
|
||||||
|
|
||||||
def turn_off(hal: PizzaHAL, **kwargs):
|
def turn_off(hal: PizzaHAL, **kwargs):
|
||||||
"""
|
"""
|
||||||
Turn off the lights.
|
Turn off the lights.
|
||||||
"""
|
"""
|
||||||
set_light(hal, Lights.BACKLIGHT, 0, 0, 0, 0, 0)
|
set_light(hal, Lights.BACKLIGHT, 0, 0, 0, 0, 0, ignore_lid=True)
|
||||||
set_light(hal, Lights.FRONTLIGHT, 0, 0, 0, 0, 0)
|
set_light(hal, Lights.FRONTLIGHT, 0, 0, 0, 0, 0, ignore_lid=True)
|
||||||
do_it(hal)
|
do_it(hal, ignore_lid=True)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_input(hal: PizzaHAL,
|
def wait_for_input(hal: PizzaHAL,
|
||||||
|
@ -241,6 +291,13 @@ def wait_for_input(hal: PizzaHAL,
|
||||||
if sound is not None:
|
if sound is not None:
|
||||||
hal.stop_sound()
|
hal.stop_sound()
|
||||||
|
|
||||||
|
if resp is None:
|
||||||
|
# lid was closed by user
|
||||||
|
logger.info('Lid closed during wait_for_input(). Sending ABORT.')
|
||||||
|
hal.send_cmd(SerialCommands.ABORT, ignore_lid=True)
|
||||||
|
hal.flush_serial()
|
||||||
|
return
|
||||||
|
|
||||||
if len(resp) != 3:
|
if len(resp) != 3:
|
||||||
raise SerialCommunicationError(f'USER_INTERACTION expects 3 bytes, received {resp}')
|
raise SerialCommunicationError(f'USER_INTERACTION expects 3 bytes, received {resp}')
|
||||||
|
|
||||||
|
@ -278,14 +335,18 @@ def set_light(hal: PizzaHAL,
|
||||||
hal.send_cmd(SerialCommands.SET_LIGHT,
|
hal.send_cmd(SerialCommands.SET_LIGHT,
|
||||||
int(light.value).to_bytes(1, 'little'),
|
int(light.value).to_bytes(1, 'little'),
|
||||||
int(color).to_bytes(4, 'little'),
|
int(color).to_bytes(4, 'little'),
|
||||||
int(fade * 1000).to_bytes(4, 'little'))
|
int(fade * 1000).to_bytes(4, 'little'),
|
||||||
|
ignore_lid=kwargs.get('ignore_lid', False))
|
||||||
|
|
||||||
|
|
||||||
def do_it(hal: PizzaHAL):
|
def do_it(hal: PizzaHAL, ignore_lid: bool=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Execute set commands
|
Execute set commands
|
||||||
"""
|
"""
|
||||||
hal.send_cmd(SerialCommands.DO_IT)
|
if hal.send_cmd(SerialCommands.DO_IT, ignore_lid=ignore_lid) is None:
|
||||||
|
logger.info('Lid closed during do_it(). Sending ABORT.')
|
||||||
|
hal.send_cmd(SerialCommands.ABORT, ignore_lid=True)
|
||||||
|
hal.flush_serial()
|
||||||
|
|
||||||
|
|
||||||
def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
||||||
|
@ -298,10 +359,13 @@ def play_sound(hal: PizzaHAL, sound: Any, **kwargs):
|
||||||
# Extract data and sampling rate from file
|
# Extract data and sampling rate from file
|
||||||
try:
|
try:
|
||||||
hal.play_sound(str(sound))
|
hal.play_sound(str(sound))
|
||||||
while mx.get_busy():
|
while mx.get_busy() and hal.lid_open:
|
||||||
pass
|
pass
|
||||||
|
if not hal.lid_open:
|
||||||
|
hal.stop_sound()
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
mx.stop()
|
hal.stop_sound()
|
||||||
logger.debug('skipped playback')
|
logger.debug('skipped playback')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
|
||||||
|
|
||||||
from . import hal_serial
|
from . import hal_serial
|
||||||
|
|
||||||
hal = hal_serial.PizzaHAL()
|
hal = hal_serial.PizzaHAL()
|
||||||
|
|
|
@ -5,18 +5,21 @@ from enum import Enum, auto
|
||||||
|
|
||||||
from pizzactrl import fs_names
|
from pizzactrl import fs_names
|
||||||
from .storyboard import Language, Storyboard
|
from .storyboard import Language, Storyboard
|
||||||
from .hal_serial import SerialCommunicationError, PizzaHAL, wait_for_input, play_sound, turn_off
|
from .hal_serial import SerialCommunicationError, CommunicationError, PizzaHAL, wait_for_input, play_sound, turn_off
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
POWER_ON = auto()
|
POWER_ON = auto()
|
||||||
POST = auto()
|
POST = auto()
|
||||||
IDLE_START = auto()
|
IDLE_START = auto()
|
||||||
LANGUAGE_SELECT = auto()
|
LANGUAGE_SELECT = auto()
|
||||||
PLAY = auto()
|
PLAY = auto()
|
||||||
PAUSE = auto()
|
|
||||||
POST_PROCESS = auto()
|
POST_PROCESS = auto()
|
||||||
REWIND = auto()
|
REWIND = auto()
|
||||||
IDLE_END = auto()
|
IDLE_END = auto()
|
||||||
|
@ -43,7 +46,16 @@ class Statemachine:
|
||||||
self.test = test
|
self.test = test
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
|
|
||||||
self.state = State.POWER_ON
|
self.state = State.POWER_ON
|
||||||
|
|
||||||
|
def _next_state(self):
|
||||||
|
"""
|
||||||
|
Set `self.state` to the next state
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.state = State(self.state.value + 1)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
logger.debug(f'Starting Statemachine...')
|
logger.debug(f'Starting Statemachine...')
|
||||||
|
@ -62,7 +74,14 @@ class Statemachine:
|
||||||
while (self.state is not State.ERROR) and \
|
while (self.state is not State.ERROR) and \
|
||||||
(self.state is not State.SHUTDOWN):
|
(self.state is not State.SHUTDOWN):
|
||||||
logger.debug(f'Run(state={self.state})')
|
logger.debug(f'Run(state={self.state})')
|
||||||
choice[self.state]()
|
try:
|
||||||
|
choice[self.state]()
|
||||||
|
except (CommunicationError, SerialCommunicationError) as e:
|
||||||
|
self.state = State.ERROR
|
||||||
|
logger.error('Communication with microcontroller failed.', e)
|
||||||
|
except Exception as e:
|
||||||
|
self.state = State.ERROR
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
if self.state is State.ERROR:
|
if self.state is State.ERROR:
|
||||||
logger.debug('An error occurred. Trying to notify user...')
|
logger.debug('An error occurred. Trying to notify user...')
|
||||||
|
@ -75,63 +94,38 @@ class Statemachine:
|
||||||
|
|
||||||
self._shutdown()
|
self._shutdown()
|
||||||
|
|
||||||
def _lid_open(self):
|
|
||||||
# TODO implement
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _lid_closed(self):
|
|
||||||
# TODO implement
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _power_on(self):
|
def _power_on(self):
|
||||||
"""
|
"""
|
||||||
Initialize hal callbacks, load sounds
|
Initialize hal callbacks, load sounds
|
||||||
"""
|
"""
|
||||||
# TODO enable lid sensor
|
|
||||||
self.hal.lid_switch.when_pressed = self._lid_open
|
|
||||||
self.hal.lid_switch.when_released = self._lid_closed
|
|
||||||
|
|
||||||
self.hal.init_sounds()
|
self.hal.init_sounds()
|
||||||
self.hal.init_camera()
|
self.hal.init_camera()
|
||||||
|
|
||||||
self.state = State.POST
|
self._next_state()
|
||||||
|
|
||||||
def _post(self):
|
def _post(self):
|
||||||
"""
|
"""
|
||||||
Power on self test.
|
Power on self test.
|
||||||
"""
|
"""
|
||||||
if (not self.test) and (not os.path.exists(fs_names.USB_STICK)):
|
if (not self.test) and (not os.path.exists(fs_names.USB_STICK)):
|
||||||
logger.warning('USB-Stick not found.')
|
raise FileSystemException('USB Stick not present!')
|
||||||
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
|
|
||||||
|
|
||||||
|
self.hal.init_connection()
|
||||||
|
|
||||||
# play a sound if everything is alright
|
# play a sound if everything is alright
|
||||||
play_sound(self.hal, fs_names.SFX_POST_OK)
|
play_sound(self.hal, fs_names.SFX_POST_OK)
|
||||||
|
|
||||||
if self.test:
|
if self.test:
|
||||||
self.state = State.LANGUAGE_SELECT
|
self.state = State.LANGUAGE_SELECT
|
||||||
else:
|
else:
|
||||||
self.state = State.IDLE_START
|
self._next_state()
|
||||||
|
|
||||||
|
|
||||||
def _idle_start(self):
|
def _idle_start(self):
|
||||||
"""
|
"""
|
||||||
Device is armed. Wait for user to press start button
|
Device is armed. Wait for user to open the lid
|
||||||
"""
|
"""
|
||||||
pass
|
if self.hal.lid_open:
|
||||||
|
self._next_state()
|
||||||
|
|
||||||
def _lang_select(self):
|
def _lang_select(self):
|
||||||
"""
|
"""
|
||||||
|
@ -158,7 +152,7 @@ class Statemachine:
|
||||||
self.story.language = self.lang
|
self.story.language = self.lang
|
||||||
|
|
||||||
logger.debug(f'User selected language={self.lang}')
|
logger.debug(f'User selected language={self.lang}')
|
||||||
self.state = State.PLAY
|
self._next_state()
|
||||||
|
|
||||||
def _play(self):
|
def _play(self):
|
||||||
"""
|
"""
|
||||||
|
@ -170,7 +164,7 @@ class Statemachine:
|
||||||
self.story.play_chapter()
|
self.story.play_chapter()
|
||||||
self.story.advance_chapter()
|
self.story.advance_chapter()
|
||||||
|
|
||||||
self.state = State.POST_PROCESS
|
self._next_state()
|
||||||
|
|
||||||
def _post_process(self):
|
def _post_process(self):
|
||||||
"""
|
"""
|
||||||
|
@ -181,7 +175,7 @@ class Statemachine:
|
||||||
# cmdstring = f'MP4Box -add {fs_names.REC_DRAW_CITY} {fs_names.REC_MERGED_VIDEO}'
|
# cmdstring = f'MP4Box -add {fs_names.REC_DRAW_CITY} {fs_names.REC_MERGED_VIDEO}'
|
||||||
# call([cmdstring], shell=True)
|
# call([cmdstring], shell=True)
|
||||||
|
|
||||||
self.state = State.REWIND
|
self._next_state()
|
||||||
|
|
||||||
def _rewind(self):
|
def _rewind(self):
|
||||||
"""
|
"""
|
||||||
|
@ -193,16 +187,18 @@ class Statemachine:
|
||||||
if self.loop:
|
if self.loop:
|
||||||
self.state = State.IDLE_START
|
self.state = State.IDLE_START
|
||||||
else:
|
else:
|
||||||
self.state = State.IDLE_END
|
self._next_state()
|
||||||
|
|
||||||
def _idle_end(self):
|
def _idle_end(self):
|
||||||
"""
|
"""
|
||||||
Initialize shutdown
|
Initialize shutdown
|
||||||
"""
|
"""
|
||||||
self.state = State.SHUTDOWN
|
self._next_state()
|
||||||
|
|
||||||
def _shutdown(self):
|
def _shutdown(self):
|
||||||
"""
|
"""
|
||||||
Clean up, end execution
|
Clean up, end execution
|
||||||
"""
|
"""
|
||||||
|
self.hal.pin_helo1.off()
|
||||||
del self.hal
|
del self.hal
|
||||||
|
del self.story
|
||||||
|
|
Loading…
Reference in a new issue