diff --git a/ui/driver/driver.py b/ui/driver/driver.py index 14e3ff4..0be8818 100644 --- a/ui/driver/driver.py +++ b/ui/driver/driver.py @@ -46,6 +46,11 @@ def get_data(self, expected_cmd): def send_data(self, cmd, data=[]): return + def restarting(self, restart): + # indicates we're starting a restart to reset the display / re-read + # data / reconnect after timeout + return + @abc.abstractmethod async def async_get_data(self, cmd, data=[]): return diff --git a/ui/driver/driver_pi.py b/ui/driver/driver_pi.py index 885618d..c7e0929 100644 --- a/ui/driver/driver_pi.py +++ b/ui/driver/driver_pi.py @@ -1,3 +1,4 @@ +import sys import time from .driver import Driver import asyncio @@ -83,7 +84,8 @@ def setup_serial(self, port): return serial_port except IOError as e: log.error('check usb connection to arduino %s', e) - exit(1) + self.restarting('no-connection') + sys.exit(1) def is_ok(self): """checks the serial connection. There doesn't seem to be a specific @@ -176,6 +178,10 @@ def _publish_line(self, row, content): self.socket.send_pyobj( {'line_number': row, 'visual': brf, 'braille': brl}) + def restarting(self, reason): + if hasattr(self, 'context'): + self.socket.send_pyobj({'restarting': reason}) + async def async_get_data(self, expected_cmd): """gets 2 bytes of data from the hardware @@ -213,6 +219,9 @@ def get_data(self, expected_cmd): # where it lands, since this interface makes no allowance # for failure. raise e + except OSError as e: + # Input/output error + raise e except RuntimeError as e: # No complete frame within timeout. Same again. raise e diff --git a/ui/main.py b/ui/main.py index 6741b0b..5891a90 100644 --- a/ui/main.py +++ b/ui/main.py @@ -84,7 +84,18 @@ def main(): try: debounce = config.get('hardware', {}).get('button_debounce', 1) with Pi(port=args.tty, timeout=timeout, button_threshold=debounce) as driver: - run(driver, config) + try: + run(driver, config) + except ValueError as err: + if err.args[0] == 'Invalid Frame (CRC FAIL)': + log.info('USB B I/O error, canute probably switched off') + driver.restarting('disconnected') + sys.exit(0) + except OSError as err: + if err.errno == 5: + log.info('USB B I/O error, canute probably switched off') + driver.restarting('disconnected') + sys.exit(0) except RuntimeError as err: if err.args[0] == 'readFrame timeout': log.info( @@ -113,7 +124,7 @@ async def run_async_timeout(driver, config, duration, loop): # This is a task in its own right that listens to an external process for media # change notifications, and handles them. -async def handle_media_changes(media_helper): +async def handle_media_changes(media_helper, driver): proc = await asyncio.create_subprocess_exec( media_helper, stdout=asyncio.subprocess.PIPE) @@ -136,6 +147,7 @@ def stop_helper(*args): # For now we do nothing more sophisticated than die and allow # supervision to restart us, at which point we'll rescan the # library. + driver.restarting('media') sys.exit(0) @@ -159,9 +171,14 @@ async def run_async(driver, config, loop): media_helper = config.get('filese', {}).get('media_helper') if media_helper is not None: - media_handler = asyncio.ensure_future(handle_media_changes(media_helper)) + media_handler = asyncio.ensure_future(handle_media_changes(media_helper, driver)) else: media_handler = None + def sighup_helper(*args): + log.debug('shutting down for library rescan') + driver.restarting('media') + sys.exit(0) + signal.signal(signal.SIGHUP, sighup_helper) if config.get('hardware', {}).get('log_duty', False): duty_logger = asyncio.ensure_future(driver.track_duty()) @@ -309,6 +326,7 @@ async def handle_hardware(driver, state, media_dir): return True elif state.hardware.resetting_display == 'start': log.warning('long-press of square: exiting to cause reset') + driver.restarting('reset') sys.exit(0) elif state.hardware.warming_up == 'start': state.hardware.warm_up('in progress')