From 3c3890e8ec44b32c0df112b5c1ba6ada97fc4c13 Mon Sep 17 00:00:00 2001 From: Théo de la Hogue Date: Mon, 24 Jun 2024 17:06:44 +0200 Subject: Catching context starting error to quit when it happens. --- src/argaze/DataFeatures.py | 32 ++++++++++++++++++++++++--- src/argaze/__main__.py | 6 ++++- src/argaze/utils/UtilsFeatures.py | 16 ++++++++++++++ src/argaze/utils/contexts/TobiiProGlasses2.py | 31 +++++++++++++++++++++----- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/argaze/DataFeatures.py b/src/argaze/DataFeatures.py index 72f1d26..fa9b52e 100644 --- a/src/argaze/DataFeatures.py +++ b/src/argaze/DataFeatures.py @@ -658,6 +658,15 @@ class PipelineStepLoadingFailed(Exception): def __init__(self, message): super().__init__(message) + +class PipelineStepEnterFailed(Exception): + """ + Exception raised when pipeline step object context fails to enter. + """ + + def __init__(self, message): + super().__init__(message) + @timestamp class TimestampedImage(numpy.ndarray): """Wrap numpy.array to timestamp image.""" @@ -753,7 +762,16 @@ def PipelineStepEnter(method): PipelineStepObject.__enter__(self) - method(self) + try: + + method(self) + + self._starting_error = None + + except Exception as e: + + self._starting_error = e + logging.error('%s.__enter__: %s', get_class_path(self), e) return self @@ -976,11 +994,13 @@ class PipelineStepObject(): """ __initialized = False + _starting_error = None def __init__(self): """Initialize PipelineStepObject.""" if not self.__initialized: + logging.debug('%s.__init__', get_class_path(self)) # Init private attributes @@ -1013,6 +1033,8 @@ class PipelineStepObject(): for observer in self.observers: observer.__enter__() + # Context starting error is catched in @PipelineStepEnter decorator wrapper + return self def __exit__(self, exception_type, exception_value, exception_traceback): @@ -1033,8 +1055,7 @@ class PipelineStepObject(): if hasattr(self, key): - logging.debug('%s.update_attributes > update %s with %s value', get_class_path(self), key, - type(value).__name__) + logging.debug('%s.update_attributes > update %s with %s value', get_class_path(self), key, type(value).__name__) setattr(self, key, value) @@ -1043,6 +1064,11 @@ class PipelineStepObject(): raise (AttributeError(f'{get_class_path(self)} has not {key} attribute.')) @property + def starting_error(self) -> Exception: + """Get pipeline step object's context stating error.""" + return self._starting_error + + @property def name(self) -> str: """Get pipeline step object's name.""" return self.__name diff --git a/src/argaze/__main__.py b/src/argaze/__main__.py index 12d654c..df3d338 100644 --- a/src/argaze/__main__.py +++ b/src/argaze/__main__.py @@ -72,10 +72,14 @@ def load_context(args): # Load context from JSON file with load(args.context_file) as context: + if context.starting_error is not None: + + exit( RuntimeError(f'Context fails to start: {context.starting_error}') ) + # Loaded object must be a subclass of ArContext if not issubclass(type(context), ArContext): - raise TypeError('Loaded object is not a subclass of ArContext') + exit( TypeError('Loaded object is not a subclass of ArContext') ) if args.verbose: diff --git a/src/argaze/utils/UtilsFeatures.py b/src/argaze/utils/UtilsFeatures.py index 695f372..ff2bee6 100644 --- a/src/argaze/utils/UtilsFeatures.py +++ b/src/argaze/utils/UtilsFeatures.py @@ -79,6 +79,22 @@ def import_from_test_package(module: str) -> types.ModuleType: return TestModule +def ping(host): + """ + Returns True if host (str) responds to a ping request. + Remember that a host may not respond to a ping (ICMP) request even if the host name is valid. + """ + import platform + import subprocess + + # Option for the number of packets as a function of + param = '-n' if platform.system().lower()=='windows' else '-c' + + # Building the command. Ex: "ping -c 1 google.com" + command = ['ping', param, '1', host] + + return subprocess.call(command) == 0 + class TimeProbe(): """ Assess temporal performance. diff --git a/src/argaze/utils/contexts/TobiiProGlasses2.py b/src/argaze/utils/contexts/TobiiProGlasses2.py index 061813e..80487f4 100644 --- a/src/argaze/utils/contexts/TobiiProGlasses2.py +++ b/src/argaze/utils/contexts/TobiiProGlasses2.py @@ -43,6 +43,7 @@ except ImportError: from urllib2 import urlopen, Request, HTTPError, URLError from argaze import ArFeatures, DataFeatures +from argaze.utils import UtilsFeatures import numpy import cv2 @@ -360,6 +361,12 @@ class LiveStream(ArFeatures.LiveProcessingContext): self.__parser = TobiiJsonDataParser() + self.__data_thread = None + self.__video_thread = None + self.__video_buffer_read_thread = None + self.__keep_alive_thread = None + self.__check_battery_thread = None + @property def address(self) -> str: """Network address where to find the device.""" @@ -514,7 +521,12 @@ class LiveStream(ArFeatures.LiveProcessingContext): @DataFeatures.PipelineStepEnter def __enter__(self): - logging.info('Tobii Pro Glasses 2 connexion starts...') + logging.info(f'Tobii Pro Glasses 2 connexion starts: trying to reach {self.__address} host...') + + # Check that host exists + if not UtilsFeatures.ping(self.__address): + + raise DataFeatures.PipelineStepEnterFailed(f'Tobii Pro Glasses 2 connexion fails: {self.__address} host unreachable.') # Update current configuration with configuration patch logging.debug('> updating configuration') @@ -598,16 +610,25 @@ class LiveStream(ArFeatures.LiveProcessingContext): self._stop_event.set() # Stop keeping connection alive - threading.Thread.join(self.__keep_alive_thread) + if self.__keep_alive_thread is not None: + + threading.Thread.join(self.__keep_alive_thread) # Stop data streaming - threading.Thread.join(self.__data_thread) + if self.__data_thread is not None: + + threading.Thread.join(self.__data_thread) # Stop video buffer reading - threading.Thread.join(self.__video_buffer_read_thread) + if self.__video_buffer_read_thread is not None: + + threading.Thread.join(self.__video_buffer_read_thread) # Stop video streaming - threading.Thread.join(self.__video_thread) + if self.__video_thread is not None: + + threading.Thread.join(self.__video_thread) + def __make_socket(self): """Create a socket to enable network communication.""" -- cgit v1.1