diff --git a/device-backend/control/planktoscopehat/main.py b/device-backend/control/planktoscopehat/main.py index a65f7b83c..d3d87e76c 100644 --- a/device-backend/control/planktoscopehat/main.py +++ b/device-backend/control/planktoscopehat/main.py @@ -3,6 +3,7 @@ import time import signal import os +import json from loguru import logger @@ -53,7 +54,7 @@ def handler_stop_signals(signum, frame): if __name__ == "__main__": logger.info("Welcome!") logger.info( - "Initialising signals handling and sanitizing the directories (step 1/5)" + "Initialising configuration, signals handling and sanitizing the directories (step 1/5)" ) signal.signal(signal.SIGINT, handler_stop_signals) signal.signal(signal.SIGTERM, handler_stop_signals) @@ -84,25 +85,34 @@ def handler_stop_signals(signum, frame): f"This PlanktoScope's machine name is {planktoscope.identity.load_machine_name()}" ) + try: + with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: + configuration = json.load(config_file) + except FileNotFoundError: + logger.info( + "The hardware configuration file doesn't exists, using defaults" + ) + configuration = {} + # Prepare the event for a graceful shutdown shutdown_event = multiprocessing.Event() shutdown_event.clear() # Starts the pump process logger.info("Starting the pump control process (step 2/5)") - pump_thread = planktoscope.pump.PumpProcess(shutdown_event) + pump_thread = planktoscope.pump.PumpProcess(shutdown_event, configuration) pump_thread.start() # Starts the focus process logger.info("Starting the focus control process (step 3/5)") - focus_thread = planktoscope.focus.FocusProcess(shutdown_event) + focus_thread = planktoscope.focus.FocusProcess(shutdown_event, configuration) focus_thread.start() # TODO try to isolate the imager thread (or another thread) # Starts the imager control process logger.info("Starting the imager control process (step 4/5)") try: - imager_thread = imager.Worker(shutdown_event) + imager_thread = imager.Worker(shutdown_event, configuration) except Exception as e: logger.error(f"The imager control process could not be started: {e}") imager_thread = None @@ -112,7 +122,7 @@ def handler_stop_signals(signum, frame): # Starts the light process logger.info("Starting the light control process (step 5/5)") try: - light_thread = planktoscope.light.LightProcess(shutdown_event) + light_thread = planktoscope.light.LightProcess(shutdown_event, configuration) except Exception: logger.error("The light control process could not be started") light_thread = None diff --git a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py index 2a00b4bc4..4ce1a8bc0 100644 --- a/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/camera/mqtt.py @@ -1,7 +1,6 @@ """mqtt provides an MJPEG+MQTT API for camera supervision and interaction.""" import json -import os import threading import time import typing @@ -17,7 +16,11 @@ class Worker(threading.Thread): """Runs a camera with live MJPEG preview and an MQTT API for adjusting camera settings.""" - def __init__(self, mjpeg_server_address: tuple[str, int] = ("", 8000)) -> None: + def __init__( + self, + configuration: dict[str, typing.Any], + mjpeg_server_address: tuple[str, int] = ("", 8000), + ) -> None: """Initialize the backend. Args: @@ -50,19 +53,7 @@ def __init__(self, mjpeg_server_address: tuple[str, int] = ("", 8000)) -> None: sharpness=0, # disable the default "normal" sharpening level jpeg_quality=95, # maximize image quality ) - if os.path.exists("/home/pi/PlanktoScope/hardware.json"): - # load hardware.json - with open("/home/pi/PlanktoScope/hardware.json", "r", encoding="utf-8") as config_file: - hardware_config = json.load(config_file) - loguru.logger.debug( - f"Loaded hardware configuration file: {hardware_config}", - ) - settings = settings.overlay(hardware.config_to_settings_values(hardware_config)) - else: - loguru.logger.info( - "The hardware configuration file doesn't exist, using default settings: " - + f"{settings}" - ) + settings = settings.overlay(hardware.config_to_settings_values(configuration)) # I/O self._preview_stream: hardware.PreviewStream = hardware.PreviewStream() diff --git a/device-backend/control/planktoscopehat/planktoscope/focus.py b/device-backend/control/planktoscopehat/planktoscope/focus.py index 13f3319d9..7169e3411 100644 --- a/device-backend/control/planktoscopehat/planktoscope/focus.py +++ b/device-backend/control/planktoscopehat/planktoscope/focus.py @@ -1,4 +1,3 @@ -import json import multiprocessing import os import time @@ -22,23 +21,13 @@ class FocusProcess(multiprocessing.Process): # focus max speed is in mm/sec and is limited by the maximum number of pulses per second the PlanktoScope can send focus_max_speed = 5 - def __init__(self, event): + def __init__(self, event, configuration): super(FocusProcess, self).__init__() logger.info("Initialising the focus process") self.stop_event = event self.focus_started = False - if os.path.exists("/home/pi/PlanktoScope/hardware.json"): - # load hardware.json - with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: - # TODO #100 insert guard for config_file empty - configuration = json.load(config_file) - logger.debug(f"Hardware configuration loaded is {configuration}") - else: - logger.info("The hardware configuration file doesn't exists, using defaults") - configuration = {} - # parse the config data. If the key is absent, we are using the default value self.focus_steps_per_mm = configuration.get("focus_steps_per_mm", self.focus_steps_per_mm) self.focus_max_speed = configuration.get("focus_max_speed", self.focus_max_speed) diff --git a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py index c7516d2ae..db5e7c0d6 100644 --- a/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py +++ b/device-backend/control/planktoscopehat/planktoscope/imager/mqtt.py @@ -29,7 +29,7 @@ class Worker(multiprocessing.Process): # TODO(ethanjli): instead of passing in a stop_event, just expose a `close()` method! This # way, we don't give any process the ability to stop all other processes watching the same # stop_event! - def __init__(self, stop_event: threading.Event) -> None: + def __init__(self, stop_event: threading.Event, configuration) -> None: """Initialize the worker's internal state, but don't start anything yet. Args: @@ -52,6 +52,8 @@ def __init__(self, stop_event: threading.Event) -> None: # constructor. self._camera: typing.Optional[camera.Worker] = None + self.configuration = configuration + loguru.logger.success("planktoscope.imager is initialized and ready to go!") @loguru.logger.catch @@ -74,7 +76,7 @@ def run(self) -> None: loguru.logger.success("Pump RPC client is ready!") loguru.logger.info("Starting the camera...") - self._camera = camera.Worker() + self._camera = camera.Worker(self.configuration) self._camera.start() if self._camera.camera is None: loguru.logger.error( diff --git a/device-backend/control/planktoscopehat/planktoscope/light.py b/device-backend/control/planktoscopehat/planktoscope/light.py index 882ed57ef..e5671072f 100644 --- a/device-backend/control/planktoscopehat/planktoscope/light.py +++ b/device-backend/control/planktoscopehat/planktoscope/light.py @@ -3,7 +3,6 @@ ################################################################################ # Logger library compatible with multiprocessing from loguru import logger -import json from gpiozero import DigitalOutputDevice @@ -40,22 +39,13 @@ class Register(enum.IntEnum): # This constant defines the current (mA) sent to the LED, 10 allows the use of the full ISO scale and results in a voltage of 2.77v DEFAULT_CURRENT = 10 - def __init__(self): - try: - with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: - configuration = json.load(config_file) - except FileNotFoundError: - logger.info( - "The hardware configuration file doesn't exists, using defaults" - ) - configuration = {} - + def __init__(self, configuration): hat_type = configuration.get("hat_type") or "" hat_version = float(configuration.get("hat_version") or 0) # The led is controlled by LM36011 # but on version 1.2 of the PlanktoScope HAT (PlanktoScope v2.6) - # the circuit is connected to that pin so it needs to be high + # the circuit is connected to the pin 18 so it needs to be high # pin is assigned to self to prevent gpiozero from immediately releasing it if hat_type != "planktoscope" or hat_version < 3.1: self.__pin = DigitalOutputDevice(pin=18, initial_value=True) @@ -176,7 +166,7 @@ def _read_byte(self, address): class LightProcess(multiprocessing.Process): """This class contains the main definitions for the light of the PlanktoScope""" - def __init__(self, event): + def __init__(self, event, configuration): """Initialize the Light class Args: @@ -189,7 +179,7 @@ def __init__(self, event): self.stop_event = event self.light_client = None try: - self.led = i2c_led() + self.led = i2c_led(configuration) self.led.set_torch_current(self.led.DEFAULT_CURRENT) self.led.activate_torch_ramp() self.led.activate_torch() diff --git a/device-backend/control/planktoscopehat/planktoscope/pump.py b/device-backend/control/planktoscopehat/planktoscope/pump.py index 1e4b666f6..053bb7fbd 100644 --- a/device-backend/control/planktoscopehat/planktoscope/pump.py +++ b/device-backend/control/planktoscopehat/planktoscope/pump.py @@ -1,4 +1,3 @@ -import json import multiprocessing import os import time @@ -24,23 +23,13 @@ class PumpProcess(multiprocessing.Process): # pump max speed is in ml/min pump_max_speed = 50 - def __init__(self, event): + def __init__(self, event, configuration): super(PumpProcess, self).__init__() logger.info("Initialising the pump process") self.stop_event = event self.pump_started = False - if os.path.exists("/home/pi/PlanktoScope/hardware.json"): - # load hardware.json - with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: - # TODO #100 insert guard for config_file empty - configuration = json.load(config_file) - logger.debug(f"Hardware configuration loaded is {configuration}") - else: - logger.info("The hardware configuration file doesn't exists, using defaults") - configuration = {} - # parse the config data. If the key is absent, we are using the default value self.pump_steps_per_ml = configuration.get("pump_steps_per_ml", self.pump_steps_per_ml) self.pump_max_speed = configuration.get("pump_max_speed", self.pump_max_speed)