From e1c02a7d6ab13cdc7aee2b92b964ff6b698879f8 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 29 Apr 2024 09:59:32 +0100 Subject: [PATCH 01/35] Made modifications to main.py in the adafruithat and planktoscopehat --- control/adafruithat/main.py | 20 +++++++++++++++----- control/planktoscopehat/main.py | 21 ++++++++++++++++----- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/control/adafruithat/main.py b/control/adafruithat/main.py index f8540c01..4c50b670 100644 --- a/control/adafruithat/main.py +++ b/control/adafruithat/main.py @@ -126,14 +126,24 @@ def handler_stop_signals(signum, _): logger.success("Looks like the controller is set up and running, have fun!") planktoscope.light.ready() + # With the creation of this dictionary to keep track of running threads, we can easily + # update the code if we decide to add another process, such as for the pump. + running_threads = { + "stepper": stepper_thread, + "imager": imager_thread + } + while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") - if not stepper_thread.is_alive(): - logger.error("The stepper process died unexpectedly! Oh no!") - break - if not imager_thread or not imager_thread.is_alive(): - logger.error("The imager process died unexpectedly! Oh no!") + # Check if any threads have terminated unexpectedly and log the error without exiting + for thread_name, thread in running_threads.items(): + if not thread or not thread.is_alive(): + logger.error(f"The {thread_name} process terminated unexpectedly!") + del running_threads[thread_name] # Remove the dead thread from the dictionary + # Check if all threads have terminated so we can exit the program + if not running_threads: #checks if there is no running thread left + logger.error("All processes terminated unexpectedly! Exiting...") break time.sleep(1) diff --git a/control/planktoscopehat/main.py b/control/planktoscopehat/main.py index 45a91fbe..1a999a31 100644 --- a/control/planktoscopehat/main.py +++ b/control/planktoscopehat/main.py @@ -118,14 +118,25 @@ def handler_stop_signals(signum, frame): logger.success("Looks like everything is set up and running, have fun!") + + # With the creation of this dictionary to keep track of running threads, we can easily + # update the code if we decide to add another process, such as for the pump. + running_threads = { + "stepper": stepper_thread, + "imager": imager_thread + } + while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") - if not stepper_thread.is_alive(): - logger.error("The stepper process died unexpectedly! Oh no!") - break - if not imager_thread or not imager_thread.is_alive(): - logger.error("The imager process died unexpectedly! Oh no!") + # Check if any threads have terminated unexpectedly and log the error without exiting + for thread_name, thread in running_threads.items(): + if not thread or not thread.is_alive(): + logger.error(f"The {thread_name} process terminated unexpectedly!") + del running_threads[thread_name] # Remove the dead thread from the dictionary + # Check if all threads have terminated so we can exit the program + if not running_threads: #checks if there is no running thread left + logger.error("All processes terminated unexpectedly! Exiting...") break time.sleep(1) From ffd5b135a1e56b878313e4476cbc63e713c29f12 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 29 Apr 2024 10:44:06 +0100 Subject: [PATCH 02/35] On this branch, the main.py will remain unchanged --- control/adafruithat/main.py | 20 +++++-------------- control/planktoscopehat/main.py | 20 +++++-------------- control/planktoscopehat/planktoscope/focus.py | 0 control/planktoscopehat/planktoscope/pump.py | 0 4 files changed, 10 insertions(+), 30 deletions(-) create mode 100644 control/planktoscopehat/planktoscope/focus.py create mode 100644 control/planktoscopehat/planktoscope/pump.py diff --git a/control/adafruithat/main.py b/control/adafruithat/main.py index 4c50b670..f8540c01 100644 --- a/control/adafruithat/main.py +++ b/control/adafruithat/main.py @@ -126,24 +126,14 @@ def handler_stop_signals(signum, _): logger.success("Looks like the controller is set up and running, have fun!") planktoscope.light.ready() - # With the creation of this dictionary to keep track of running threads, we can easily - # update the code if we decide to add another process, such as for the pump. - running_threads = { - "stepper": stepper_thread, - "imager": imager_thread - } - while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") - # Check if any threads have terminated unexpectedly and log the error without exiting - for thread_name, thread in running_threads.items(): - if not thread or not thread.is_alive(): - logger.error(f"The {thread_name} process terminated unexpectedly!") - del running_threads[thread_name] # Remove the dead thread from the dictionary - # Check if all threads have terminated so we can exit the program - if not running_threads: #checks if there is no running thread left - logger.error("All processes terminated unexpectedly! Exiting...") + if not stepper_thread.is_alive(): + logger.error("The stepper process died unexpectedly! Oh no!") + break + if not imager_thread or not imager_thread.is_alive(): + logger.error("The imager process died unexpectedly! Oh no!") break time.sleep(1) diff --git a/control/planktoscopehat/main.py b/control/planktoscopehat/main.py index 1a999a31..d0a02841 100644 --- a/control/planktoscopehat/main.py +++ b/control/planktoscopehat/main.py @@ -119,24 +119,14 @@ def handler_stop_signals(signum, frame): logger.success("Looks like everything is set up and running, have fun!") - # With the creation of this dictionary to keep track of running threads, we can easily - # update the code if we decide to add another process, such as for the pump. - running_threads = { - "stepper": stepper_thread, - "imager": imager_thread - } - while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") - # Check if any threads have terminated unexpectedly and log the error without exiting - for thread_name, thread in running_threads.items(): - if not thread or not thread.is_alive(): - logger.error(f"The {thread_name} process terminated unexpectedly!") - del running_threads[thread_name] # Remove the dead thread from the dictionary - # Check if all threads have terminated so we can exit the program - if not running_threads: #checks if there is no running thread left - logger.error("All processes terminated unexpectedly! Exiting...") + if not stepper_thread.is_alive(): + logger.error("The stepper process died unexpectedly! Oh no!") + break + if not imager_thread or not imager_thread.is_alive(): + logger.error("The imager process died unexpectedly! Oh no!") break time.sleep(1) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py new file mode 100644 index 00000000..e69de29b diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py new file mode 100644 index 00000000..e69de29b From 252f8c2061c6658863e72e12f08773c794d51830 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 2 May 2024 09:35:57 +0100 Subject: [PATCH 03/35] this is not a final version of the pump.py and focus.py --- control/planktoscopehat/planktoscope/focus.py | 365 ++++++++++++++++++ control/planktoscopehat/planktoscope/pump.py | 347 +++++++++++++++++ 2 files changed, 712 insertions(+) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index e69de29b..505b19ea 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -0,0 +1,365 @@ +# Libraries to control the steppers for focusing and pumping +import time +import json +import os +import planktoscope.mqtt +import multiprocessing +import RPi.GPIO + +import shush + +# Logger library compatible with multiprocessing +from loguru import logger + +logger.info("planktoscope.stepper is loaded") + + +"""Step forward""" +FORWARD = 1 +""""Step backward""" +BACKWARD = 2 +"""Stepper controller 1""" +STEPPER1 = 0 +""""Stepper controller 2""" +STEPPER2 = 1 + + +class stepper: + def __init__(self, stepper, size=0): + """Initialize the stepper class + + Args: + stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper + size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable + """ + self.__stepper = shush.Motor(stepper) + self.__size = size + self.__goal = 0 + self.__direction = "" + self.__stepper.disable_motor() + + def at_goal(self): + """Is the motor at its goal + + Returns: + Bool: True if position and goal are identical + """ + return self.__stepper.get_position() == self.__goal + + def is_moving(self): + """is the stepper in movement? + + Returns: + Bool: True if the stepper is moving + """ + return self.__stepper.get_velocity() != 0 + + def go(self, direction, distance): + """move in the given direction for the given distance + + Args: + direction: gives the movement direction + distance: + """ + self.__direction = direction + if self.__direction == FORWARD: + self.__goal = int(self.__stepper.get_position() + distance) + elif self.__direction == BACKWARD: + self.__goal = int(self.__stepper.get_position() - distance) + else: + logger.error(f"The given direction is wrong {direction}") + self.__stepper.enable_motor() + self.__stepper.go_to(self.__goal) + + def shutdown(self): + """Shutdown everything ASAP""" + self.__stepper.stop_motor() + self.__stepper.disable_motor() + self.__goal = self.__stepper.get_position() + + def release(self): + self.__stepper.disable_motor() + + @property + def speed(self): + return self.__stepper.ramp_VMAX + + @speed.setter + def speed(self, speed): + """Change the stepper speed + + Args: + speed (int): speed of the movement by the stepper, in microsteps unit/s + """ + logger.debug(f"Setting stepper speed to {speed}") + self.__stepper.ramp_VMAX = int(speed) + + @property + def acceleration(self): + return self.__stepper.ramp_AMAX + + @acceleration.setter + def acceleration(self, acceleration): + """Change the stepper acceleration + + Args: + acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² + """ + logger.debug(f"Setting stepper acceleration to {acceleration}") + self.__stepper.ramp_AMAX = int(acceleration) + + @property + def deceleration(self): + return self.__stepper.ramp_DMAX + + @deceleration.setter + def deceleration(self, deceleration): + """Change the stepper deceleration + + Args: + deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² + """ + logger.debug(f"Setting stepper deceleration to {deceleration}") + self.__stepper.ramp_DMAX = int(deceleration) + +class FocusProcess(multiprocessing.Process): + focus_steps_per_mm = 40 + # 507 steps per ml for PlanktoScope standard + + # 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): + super(FocusProcess, self).__init__() + logger.info("Initialising the stepper process") + + self.stop_event = event + self.focus_started = False + 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 = {} + + reverse = False + + # parse the config data. If the key is absent, we are using the default value + reverse = configuration.get("stepper_reverse", reverse) + 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 + ) + + # define the names for the 2 exsting steppers + if reverse: + self.pump_stepper = stepper(STEPPER2) + self.focus_stepper = stepper(STEPPER1, size=45) + else: + self.pump_stepper = stepper(STEPPER1) + self.focus_stepper = stepper(STEPPER2, size=45) + + # Set stepper controller max speed + + self.focus_stepper.acceleration = 1000 + self.focus_stepper.deceleration = self.focus_stepper.acceleration + self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 + + self.pump_stepper.acceleration = 2000 + self.pump_stepper.deceleration = self.pump_stepper.acceleration + self.pump_stepper.speed = ( + self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 + ) + + logger.info("Stepper initialisation is over") + + + def __message_focus(self, last_message): + logger.debug("We have received a focusing request") + # If a new received command is "focus" but args contains "stop" we stop! + if last_message["action"] == "stop": + logger.debug("We have received a stop focus command") + self.focus_stepper.shutdown() + + # Print status + logger.info("The focus has been interrupted") + + # Publish the status "Interrupted" to via MQTT to Node-RED + self.actuator_client.client.publish( + "status/focus", '{"status":"Interrupted"}' + ) + + elif last_message["action"] == "move": + logger.debug("We have received a move focus command") + + if "direction" not in last_message or "distance" not in last_message: + logger.error( + f"The received message has the wrong argument {last_message}" + ) + self.actuator_client.client.publish( + "status/focus", '{"status":"Error"}' + ) + # Get direction from the different received arguments + direction = last_message["direction"] + # Get number of steps from the different received arguments + distance = float(last_message["distance"]) + + speed = float(last_message["speed"]) if "speed" in last_message else 0 + + # Print status + logger.info("The focus movement is started.") + if speed: + self.focus(direction, distance, speed) + else: + self.focus(direction, distance) + else: + logger.warning(f"The received message was not understood {last_message}") + + def treat_command(self): + command = "" + logger.info("We received a new message") + last_message = self.actuator_client.msg["payload"] + logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + logger.debug(command) + self.actuator_client.read_message() + + if command == "pump": + self.__message_pump(last_message) + elif command == "focus": + self.__message_focus(last_message) + elif command != "": + logger.warning( + f"We did not understand the received request {command} - {last_message}" + ) + + def focus(self, direction, distance, speed=focus_max_speed): + """Moves the focus stepper + + direction is either UP or DOWN + distance is received in mm + speed is in mm/sec + + Args: + direction (string): either UP or DOWN + distance (int): distance to move the stage, in mm + speed (int, optional): max speed of the stage, in mm/sec. Defaults to focus_max_speed. + """ + + logger.info( + f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec" + ) + + # Validation of inputs + if direction not in ["UP", "DOWN"]: + logger.error("The direction command is not recognised") + logger.error("It should be either UP or DOWN") + return + + if distance > 45: + logger.error("You are trying to move more than the stage physical size") + return + + # We are going to use 256 microsteps, so we need to multiply by 256 the steps number + nb_steps = round(self.focus_steps_per_mm * distance * 256, 0) + logger.debug(f"The number of microsteps that will be applied is {nb_steps}") + if speed > self.focus_max_speed: + speed = self.focus_max_speed + logger.warning( + f"Focus stage speed has been clamped to a maximum safe speed of {speed} mm/sec" + ) + steps_per_second = speed * self.focus_steps_per_mm * 256 + logger.debug(f"There will be a speed of {steps_per_second} steps per second") + self.focus_stepper.speed = int(steps_per_second) + + # Publish the status "Started" to via MQTT to Node-RED + self.actuator_client.client.publish( + "status/focus", + f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', + ) + + # Depending on direction, select the right direction for the focus + if direction == "UP": + self.focus_started = True + self.focus_stepper.go(FORWARD, nb_steps) + return + + if direction == "DOWN": + self.focus_started = True + self.focus_stepper.go(BACKWARD, nb_steps) + return + + # The pump max speed will be at about 400 full steps per second + # This amounts to 0.9mL per seconds maximum, or 54mL/min + # NEMA14 pump with 3 rollers is 0.509 mL per round, actual calculation at + # Stepper is 200 steps/round, or 393steps/ml + # https://www.wolframalpha.com/input/?i=pi+*+%280.8mm%29%C2%B2+*+54mm+*+3 + + @logger.catch + def run(self): + """This is the function that needs to be started to create a thread""" + logger.info( + f"The stepper control process has been started in process {os.getpid()}" + ) + + # Creates the MQTT Client + # We have to create it here, otherwise when the process running run is started + # it doesn't see changes and calls made by self.actuator_client because this one + # only exist in the master process + # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class + self.actuator_client = planktoscope.mqtt.MQTT_Client( + topic="actuator/#", name="actuator_client" + ) + # Publish the status "Ready" to via MQTT to Node-RED + self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') + # Publish the status "Ready" to via MQTT to Node-RED + self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') + + logger.success("Stepper is READY!") + while not self.stop_event.is_set(): + if self.actuator_client.new_message_received(): + self.treat_command() + if self.pump_started and self.pump_stepper.at_goal(): + logger.success("The pump movement is over!") + self.actuator_client.client.publish( + "status/pump", + '{"status":"Done"}', + ) + self.pump_started = False + self.pump_stepper.release() + if self.focus_started and self.focus_stepper.at_goal(): + logger.success("The focus movement is over!") + self.actuator_client.client.publish( + "status/focus", + '{"status":"Done"}', + ) + self.focus_started = False + self.pump_stepper.release() + time.sleep(0.01) + logger.info("Shutting down the stepper process") + self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') + self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') + self.pump_stepper.shutdown() + self.focus_stepper.shutdown() + self.actuator_client.shutdown() + logger.success("Stepper process shut down! See you!") + + +# This is called if this script is launched directly +if __name__ == "__main__": + # TODO This should be a test suite for this library + # Starts the stepper thread for actuators + # This needs to be in a threading or multiprocessing wrapper + focus_thread = FocusProcess() + focus_thread.start() + focus_thread.join() \ No newline at end of file diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index e69de29b..5c631a08 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -0,0 +1,347 @@ +# Libraries to control the steppers for focusing and pumping +import time +import json +import os +import planktoscope.mqtt +import multiprocessing +import RPi.GPIO + +import shush + +# Logger library compatible with multiprocessing +from loguru import logger + +logger.info("planktoscope.stepper is loaded") + + +"""Step forward""" +FORWARD = 1 +""""Step backward""" +BACKWARD = 2 +"""Stepper controller 1""" +STEPPER1 = 0 +""""Stepper controller 2""" +STEPPER2 = 1 + + +class stepper: + def __init__(self, stepper, size=0): + """Initialize the stepper class + + Args: + stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper + size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable + """ + self.__stepper = shush.Motor(stepper) + self.__size = size + self.__goal = 0 + self.__direction = "" + self.__stepper.disable_motor() + + def at_goal(self): + """Is the motor at its goal + + Returns: + Bool: True if position and goal are identical + """ + return self.__stepper.get_position() == self.__goal + + def is_moving(self): + """is the stepper in movement? + + Returns: + Bool: True if the stepper is moving + """ + return self.__stepper.get_velocity() != 0 + + def go(self, direction, distance): + """move in the given direction for the given distance + + Args: + direction: gives the movement direction + distance: + """ + self.__direction = direction + if self.__direction == FORWARD: + self.__goal = int(self.__stepper.get_position() + distance) + elif self.__direction == BACKWARD: + self.__goal = int(self.__stepper.get_position() - distance) + else: + logger.error(f"The given direction is wrong {direction}") + self.__stepper.enable_motor() + self.__stepper.go_to(self.__goal) + + def shutdown(self): + """Shutdown everything ASAP""" + self.__stepper.stop_motor() + self.__stepper.disable_motor() + self.__goal = self.__stepper.get_position() + + def release(self): + self.__stepper.disable_motor() + + @property + def speed(self): + return self.__stepper.ramp_VMAX + + @speed.setter + def speed(self, speed): + """Change the stepper speed + + Args: + speed (int): speed of the movement by the stepper, in microsteps unit/s + """ + logger.debug(f"Setting stepper speed to {speed}") + self.__stepper.ramp_VMAX = int(speed) + + @property + def acceleration(self): + return self.__stepper.ramp_AMAX + + @acceleration.setter + def acceleration(self, acceleration): + """Change the stepper acceleration + + Args: + acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² + """ + logger.debug(f"Setting stepper acceleration to {acceleration}") + self.__stepper.ramp_AMAX = int(acceleration) + + @property + def deceleration(self): + return self.__stepper.ramp_DMAX + + @deceleration.setter + def deceleration(self, deceleration): + """Change the stepper deceleration + + Args: + deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² + """ + logger.debug(f"Setting stepper deceleration to {deceleration}") + self.__stepper.ramp_DMAX = int(deceleration) + + +class PumpProcess(multiprocessing.Process): + + # 5200 for custom NEMA14 pump with 0.8mm ID Tube + pump_steps_per_ml = 507 + + # pump max speed is in ml/min + pump_max_speed = 50 + + def __init__(self, event): + super(PumpProcess, self).__init__() + logger.info("Initialising the stepper 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 = {} + + reverse = False + + # parse the config data. If the key is absent, we are using the default value + reverse = configuration.get("stepper_reverse", reverse) + + 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) + + # define the names for the 2 exsting steppers + if reverse: + self.pump_stepper = stepper(STEPPER2) + self.focus_stepper = stepper(STEPPER1, size=45) + else: + self.pump_stepper = stepper(STEPPER1) + self.focus_stepper = stepper(STEPPER2, size=45) + + # Set pump controller max speed + + self.focus_stepper.acceleration = 1000 + self.focus_stepper.deceleration = self.focus_stepper.acceleration + self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 + + self.pump_stepper.acceleration = 2000 + self.pump_stepper.deceleration = self.pump_stepper.acceleration + self.pump_stepper.speed = ( + self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 + ) + + logger.info("Stepper initialisation is over") + + def __message_pump(self, last_message): + logger.debug("We have received a pumping command") + if last_message["action"] == "stop": + logger.debug("We have received a stop pump command") + self.pump_stepper.shutdown() + + # Print status + logger.info("The pump has been interrupted") + + # Publish the status "Interrupted" to via MQTT to Node-RED + self.actuator_client.client.publish( + "status/pump", '{"status":"Interrupted"}' + ) + + elif last_message["action"] == "move": + logger.debug("We have received a move pump command") + + if ( + "direction" not in last_message + or "volume" not in last_message + or "flowrate" not in last_message + ): + logger.error( + f"The received message has the wrong argument {last_message}" + ) + self.actuator_client.client.publish( + "status/pump", + '{"status":"Error, the message is missing an argument"}', + ) + return + # Get direction from the different received arguments + direction = last_message["direction"] + # Get delay (in between steps) from the different received arguments + volume = float(last_message["volume"]) + # Get number of steps from the different received arguments + flowrate = float(last_message["flowrate"]) + if flowrate == 0: + logger.error("The flowrate should not be == 0") + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, The flowrate should not be == 0"}' + ) + return + + # Print status + logger.info("The pump is started.") + self.pump(direction, volume, flowrate) + else: + logger.warning(f"The received message was not understood {last_message}") + + + def treat_command(self): + command = "" + logger.info("We received a new message") + last_message = self.actuator_client.msg["payload"] + logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + logger.debug(command) + self.actuator_client.read_message() + + if command == "pump": + self.__message_pump(last_message) + elif command == "focus": + self.__message_focus(last_message) + elif command != "": + logger.warning( + f"We did not understand the received request {command} - {last_message}" + ) + + def pump(self, direction, volume, speed=pump_max_speed): + """Moves the pump stepper + + Args: + direction (string): direction of the pumping + volume (int): volume to pump, in mL + speed (int, optional): speed of pumping, in mL/min. Defaults to pump_max_speed. + """ + + logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") + + # Validation of inputs + if direction not in ["FORWARD", "BACKWARD"]: + logger.error("The direction command is not recognised") + logger.error("It should be either FORWARD or BACKWARD") + return + + # TMC5160 is configured for 256 microsteps + nb_steps = round(self.pump_steps_per_ml * volume * 256, 0) + logger.debug(f"The number of microsteps that will be applied is {nb_steps}") + if speed > self.pump_max_speed: + speed = self.pump_max_speed + logger.warning( + f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" + ) + steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 + logger.debug(f"There will be a speed of {steps_per_second} steps per second") + self.pump_stepper.speed = int(steps_per_second) + + # Publish the status "Started" to via MQTT to Node-RED + self.actuator_client.client.publish( + "status/pump", + f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', + ) + + # Depending on direction, select the right direction for the focus + if direction == "FORWARD": + self.pump_started = True + self.pump_stepper.go(FORWARD, nb_steps) + return + + if direction == "BACKWARD": + self.pump_started = True + self.pump_stepper.go(BACKWARD, nb_steps) + return + + @logger.catch + def run(self): + """This is the function that needs to be started to create a thread""" + logger.info( + f"The stepper control process has been started in process {os.getpid()}" + ) + + # Creates the MQTT Client + # We have to create it here, otherwise when the process running run is started + # it doesn't see changes and calls made by self.actuator_client because this one + # only exist in the master process + # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class + self.actuator_client = planktoscope.mqtt.MQTT_Client( + topic="actuator/#", name="actuator_client" + ) + # Publish the status "Ready" to via MQTT to Node-RED + self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') + + + logger.success("The pump is READY!") + while not self.stop_event.is_set(): + if self.actuator_client.new_message_received(): + self.treat_command() + if self.pump_started and self.pump_stepper.at_goal(): + logger.success("The pump movement is over!") + self.actuator_client.client.publish( + "status/pump", + '{"status":"Done"}', + ) + self.pump_started = False + self.pump_stepper.release() + + time.sleep(0.01) + logger.info("Shutting down the stepper process") + self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') + self.pump_stepper.shutdown() + + self.actuator_client.shutdown() + logger.success("Stepper process shut down! See you!") + + +# This is called if this script is launched directly +if __name__ == "__main__": + # TODO This should be a test suite for this library + # Starts the stepper thread for actuators + # This needs to be in a threading or multiprocessing wrapper + pump_thread = PumpProcess() + pump_thread.start() + pump_thread.join() \ No newline at end of file From a01c4cec7a9967dfb569324ac07d83d2572097f4 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 2 May 2024 09:38:34 +0100 Subject: [PATCH 04/35] deleting the older version of stepper --- .../planktoscopehat/planktoscope/stepper.py | 465 ------------------ 1 file changed, 465 deletions(-) delete mode 100644 control/planktoscopehat/planktoscope/stepper.py diff --git a/control/planktoscopehat/planktoscope/stepper.py b/control/planktoscopehat/planktoscope/stepper.py deleted file mode 100644 index 4c93b0f5..00000000 --- a/control/planktoscopehat/planktoscope/stepper.py +++ /dev/null @@ -1,465 +0,0 @@ -# Libraries to control the steppers for focusing and pumping -import time -import json -import os -import planktoscope.mqtt -import multiprocessing -import RPi.GPIO - -import shush - -# Logger library compatible with multiprocessing -from loguru import logger - -logger.info("planktoscope.stepper is loaded") - - -"""Step forward""" -FORWARD = 1 -""""Step backward""" -BACKWARD = 2 -"""Stepper controller 1""" -STEPPER1 = 0 -""""Stepper controller 2""" -STEPPER2 = 1 - - -class stepper: - def __init__(self, stepper, size=0): - """Initialize the stepper class - - Args: - stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper - size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable - """ - self.__stepper = shush.Motor(stepper) - self.__size = size - self.__goal = 0 - self.__direction = "" - self.__stepper.disable_motor() - - def at_goal(self): - """Is the motor at its goal - - Returns: - Bool: True if position and goal are identical - """ - return self.__stepper.get_position() == self.__goal - - def is_moving(self): - """is the stepper in movement? - - Returns: - Bool: True if the stepper is moving - """ - return self.__stepper.get_velocity() != 0 - - def go(self, direction, distance): - """move in the given direction for the given distance - - Args: - direction: gives the movement direction - distance: - """ - self.__direction = direction - if self.__direction == FORWARD: - self.__goal = int(self.__stepper.get_position() + distance) - elif self.__direction == BACKWARD: - self.__goal = int(self.__stepper.get_position() - distance) - else: - logger.error(f"The given direction is wrong {direction}") - self.__stepper.enable_motor() - self.__stepper.go_to(self.__goal) - - def shutdown(self): - """Shutdown everything ASAP""" - self.__stepper.stop_motor() - self.__stepper.disable_motor() - self.__goal = self.__stepper.get_position() - - def release(self): - self.__stepper.disable_motor() - - @property - def speed(self): - return self.__stepper.ramp_VMAX - - @speed.setter - def speed(self, speed): - """Change the stepper speed - - Args: - speed (int): speed of the movement by the stepper, in microsteps unit/s - """ - logger.debug(f"Setting stepper speed to {speed}") - self.__stepper.ramp_VMAX = int(speed) - - @property - def acceleration(self): - return self.__stepper.ramp_AMAX - - @acceleration.setter - def acceleration(self, acceleration): - """Change the stepper acceleration - - Args: - acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² - """ - logger.debug(f"Setting stepper acceleration to {acceleration}") - self.__stepper.ramp_AMAX = int(acceleration) - - @property - def deceleration(self): - return self.__stepper.ramp_DMAX - - @deceleration.setter - def deceleration(self, deceleration): - """Change the stepper deceleration - - Args: - deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² - """ - logger.debug(f"Setting stepper deceleration to {deceleration}") - self.__stepper.ramp_DMAX = int(deceleration) - - -class StepperProcess(multiprocessing.Process): - focus_steps_per_mm = 40 - # 507 steps per ml for PlanktoScope standard - # 5200 for custom NEMA14 pump with 0.8mm ID Tube - pump_steps_per_ml = 507 - # 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 - # pump max speed is in ml/min - pump_max_speed = 50 - - def __init__(self, event): - super(StepperProcess, self).__init__() - logger.info("Initialising the stepper process") - - self.stop_event = event - self.focus_started = False - 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 = {} - - reverse = False - - # parse the config data. If the key is absent, we are using the default value - reverse = configuration.get("stepper_reverse", reverse) - self.focus_steps_per_mm = configuration.get( - "focus_steps_per_mm", self.focus_steps_per_mm - ) - self.pump_steps_per_ml = configuration.get( - "pump_steps_per_ml", self.pump_steps_per_ml - ) - self.focus_max_speed = configuration.get( - "focus_max_speed", self.focus_max_speed - ) - self.pump_max_speed = configuration.get("pump_max_speed", self.pump_max_speed) - - # define the names for the 2 exsting steppers - if reverse: - self.pump_stepper = stepper(STEPPER2) - self.focus_stepper = stepper(STEPPER1, size=45) - else: - self.pump_stepper = stepper(STEPPER1) - self.focus_stepper = stepper(STEPPER2, size=45) - - # Set stepper controller max speed - - self.focus_stepper.acceleration = 1000 - self.focus_stepper.deceleration = self.focus_stepper.acceleration - self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 - - self.pump_stepper.acceleration = 2000 - self.pump_stepper.deceleration = self.pump_stepper.acceleration - self.pump_stepper.speed = ( - self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - ) - - logger.info("Stepper initialisation is over") - - def __message_pump(self, last_message): - logger.debug("We have received a pumping command") - if last_message["action"] == "stop": - logger.debug("We have received a stop pump command") - self.pump_stepper.shutdown() - - # Print status - logger.info("The pump has been interrupted") - - # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/pump", '{"status":"Interrupted"}' - ) - - elif last_message["action"] == "move": - logger.debug("We have received a move pump command") - - if ( - "direction" not in last_message - or "volume" not in last_message - or "flowrate" not in last_message - ): - logger.error( - f"The received message has the wrong argument {last_message}" - ) - self.actuator_client.client.publish( - "status/pump", - '{"status":"Error, the message is missing an argument"}', - ) - return - # Get direction from the different received arguments - direction = last_message["direction"] - # Get delay (in between steps) from the different received arguments - volume = float(last_message["volume"]) - # Get number of steps from the different received arguments - flowrate = float(last_message["flowrate"]) - if flowrate == 0: - logger.error("The flowrate should not be == 0") - self.actuator_client.client.publish( - "status/pump", '{"status":"Error, The flowrate should not be == 0"}' - ) - return - - # Print status - logger.info("The pump is started.") - self.pump(direction, volume, flowrate) - else: - logger.warning(f"The received message was not understood {last_message}") - - def __message_focus(self, last_message): - logger.debug("We have received a focusing request") - # If a new received command is "focus" but args contains "stop" we stop! - if last_message["action"] == "stop": - logger.debug("We have received a stop focus command") - self.focus_stepper.shutdown() - - # Print status - logger.info("The focus has been interrupted") - - # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/focus", '{"status":"Interrupted"}' - ) - - elif last_message["action"] == "move": - logger.debug("We have received a move focus command") - - if "direction" not in last_message or "distance" not in last_message: - logger.error( - f"The received message has the wrong argument {last_message}" - ) - self.actuator_client.client.publish( - "status/focus", '{"status":"Error"}' - ) - # Get direction from the different received arguments - direction = last_message["direction"] - # Get number of steps from the different received arguments - distance = float(last_message["distance"]) - - speed = float(last_message["speed"]) if "speed" in last_message else 0 - - # Print status - logger.info("The focus movement is started.") - if speed: - self.focus(direction, distance, speed) - else: - self.focus(direction, distance) - else: - logger.warning(f"The received message was not understood {last_message}") - - def treat_command(self): - command = "" - logger.info("We received a new message") - last_message = self.actuator_client.msg["payload"] - logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] - logger.debug(command) - self.actuator_client.read_message() - - if command == "pump": - self.__message_pump(last_message) - elif command == "focus": - self.__message_focus(last_message) - elif command != "": - logger.warning( - f"We did not understand the received request {command} - {last_message}" - ) - - def focus(self, direction, distance, speed=focus_max_speed): - """Moves the focus stepper - - direction is either UP or DOWN - distance is received in mm - speed is in mm/sec - - Args: - direction (string): either UP or DOWN - distance (int): distance to move the stage, in mm - speed (int, optional): max speed of the stage, in mm/sec. Defaults to focus_max_speed. - """ - - logger.info( - f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec" - ) - - # Validation of inputs - if direction not in ["UP", "DOWN"]: - logger.error("The direction command is not recognised") - logger.error("It should be either UP or DOWN") - return - - if distance > 45: - logger.error("You are trying to move more than the stage physical size") - return - - # We are going to use 256 microsteps, so we need to multiply by 256 the steps number - nb_steps = round(self.focus_steps_per_mm * distance * 256, 0) - logger.debug(f"The number of microsteps that will be applied is {nb_steps}") - if speed > self.focus_max_speed: - speed = self.focus_max_speed - logger.warning( - f"Focus stage speed has been clamped to a maximum safe speed of {speed} mm/sec" - ) - steps_per_second = speed * self.focus_steps_per_mm * 256 - logger.debug(f"There will be a speed of {steps_per_second} steps per second") - self.focus_stepper.speed = int(steps_per_second) - - # Publish the status "Started" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/focus", - f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', - ) - - # Depending on direction, select the right direction for the focus - if direction == "UP": - self.focus_started = True - self.focus_stepper.go(FORWARD, nb_steps) - return - - if direction == "DOWN": - self.focus_started = True - self.focus_stepper.go(BACKWARD, nb_steps) - return - - # The pump max speed will be at about 400 full steps per second - # This amounts to 0.9mL per seconds maximum, or 54mL/min - # NEMA14 pump with 3 rollers is 0.509 mL per round, actual calculation at - # Stepper is 200 steps/round, or 393steps/ml - # https://www.wolframalpha.com/input/?i=pi+*+%280.8mm%29%C2%B2+*+54mm+*+3 - def pump(self, direction, volume, speed=pump_max_speed): - """Moves the pump stepper - - Args: - direction (string): direction of the pumping - volume (int): volume to pump, in mL - speed (int, optional): speed of pumping, in mL/min. Defaults to pump_max_speed. - """ - - logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") - - # Validation of inputs - if direction not in ["FORWARD", "BACKWARD"]: - logger.error("The direction command is not recognised") - logger.error("It should be either FORWARD or BACKWARD") - return - - # TMC5160 is configured for 256 microsteps - nb_steps = round(self.pump_steps_per_ml * volume * 256, 0) - logger.debug(f"The number of microsteps that will be applied is {nb_steps}") - if speed > self.pump_max_speed: - speed = self.pump_max_speed - logger.warning( - f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" - ) - steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 - logger.debug(f"There will be a speed of {steps_per_second} steps per second") - self.pump_stepper.speed = int(steps_per_second) - - # Publish the status "Started" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/pump", - f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', - ) - - # Depending on direction, select the right direction for the focus - if direction == "FORWARD": - self.pump_started = True - self.pump_stepper.go(FORWARD, nb_steps) - return - - if direction == "BACKWARD": - self.pump_started = True - self.pump_stepper.go(BACKWARD, nb_steps) - return - - @logger.catch - def run(self): - """This is the function that needs to be started to create a thread""" - logger.info( - f"The stepper control process has been started in process {os.getpid()}" - ) - - # Creates the MQTT Client - # We have to create it here, otherwise when the process running run is started - # it doesn't see changes and calls made by self.actuator_client because this one - # only exist in the master process - # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class - self.actuator_client = planktoscope.mqtt.MQTT_Client( - topic="actuator/#", name="actuator_client" - ) - # Publish the status "Ready" to via MQTT to Node-RED - self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') - # Publish the status "Ready" to via MQTT to Node-RED - self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') - - logger.success("Stepper is READY!") - while not self.stop_event.is_set(): - if self.actuator_client.new_message_received(): - self.treat_command() - if self.pump_started and self.pump_stepper.at_goal(): - logger.success("The pump movement is over!") - self.actuator_client.client.publish( - "status/pump", - '{"status":"Done"}', - ) - self.pump_started = False - self.pump_stepper.release() - if self.focus_started and self.focus_stepper.at_goal(): - logger.success("The focus movement is over!") - self.actuator_client.client.publish( - "status/focus", - '{"status":"Done"}', - ) - self.focus_started = False - self.pump_stepper.release() - time.sleep(0.01) - logger.info("Shutting down the stepper process") - self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') - self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') - self.pump_stepper.shutdown() - self.focus_stepper.shutdown() - self.actuator_client.shutdown() - logger.success("Stepper process shut down! See you!") - - -# This is called if this script is launched directly -if __name__ == "__main__": - # TODO This should be a test suite for this library - # Starts the stepper thread for actuators - # This needs to be in a threading or multiprocessing wrapper - stepper_thread = StepperProcess() - stepper_thread.start() - stepper_thread.join() From 35c323b205cb98af49930da919817297aed64593 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Tue, 21 May 2024 08:48:22 +0100 Subject: [PATCH 05/35] modified comment --- control/planktoscopehat/planktoscope/focus.py | 60 +++++++------------ control/planktoscopehat/planktoscope/pump.py | 59 +++++++----------- 2 files changed, 41 insertions(+), 78 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 505b19ea..e033399f 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -1,16 +1,17 @@ # Libraries to control the steppers for focusing and pumping -import time import json -import os -import planktoscope.mqtt import multiprocessing -import RPi.GPIO +import os +import time -import shush +import RPi.GPIO # Logger library compatible with multiprocessing from loguru import logger +import planktoscope.mqtt +import shush + logger.info("planktoscope.stepper is loaded") @@ -122,13 +123,13 @@ def deceleration(self, deceleration): logger.debug(f"Setting stepper deceleration to {deceleration}") self.__stepper.ramp_DMAX = int(deceleration) + class FocusProcess(multiprocessing.Process): focus_steps_per_mm = 40 # 507 steps per ml for PlanktoScope standard - + # 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): super(FocusProcess, self).__init__() @@ -145,21 +146,15 @@ def __init__(self, event): 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" - ) + logger.info("The hardware configuration file doesn't exists, using defaults") configuration = {} reverse = False # parse the config data. If the key is absent, we are using the default value reverse = configuration.get("stepper_reverse", reverse) - 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 - ) + 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) # define the names for the 2 exsting steppers if reverse: @@ -177,13 +172,10 @@ def __init__(self, event): self.pump_stepper.acceleration = 2000 self.pump_stepper.deceleration = self.pump_stepper.acceleration - self.pump_stepper.speed = ( - self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - ) + self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 logger.info("Stepper initialisation is over") - def __message_focus(self, last_message): logger.debug("We have received a focusing request") # If a new received command is "focus" but args contains "stop" we stop! @@ -195,20 +187,14 @@ def __message_focus(self, last_message): logger.info("The focus has been interrupted") # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/focus", '{"status":"Interrupted"}' - ) + self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') elif last_message["action"] == "move": logger.debug("We have received a move focus command") if "direction" not in last_message or "distance" not in last_message: - logger.error( - f"The received message has the wrong argument {last_message}" - ) - self.actuator_client.client.publish( - "status/focus", '{"status":"Error"}' - ) + logger.error(f"The received message has the wrong argument {last_message}") + self.actuator_client.client.publish("status/focus", '{"status":"Error"}') # Get direction from the different received arguments direction = last_message["direction"] # Get number of steps from the different received arguments @@ -239,9 +225,7 @@ def treat_command(self): elif command == "focus": self.__message_focus(last_message) elif command != "": - logger.warning( - f"We did not understand the received request {command} - {last_message}" - ) + logger.warning(f"We did not understand the received request {command} - {last_message}") def focus(self, direction, distance, speed=focus_max_speed): """Moves the focus stepper @@ -256,9 +240,7 @@ def focus(self, direction, distance, speed=focus_max_speed): speed (int, optional): max speed of the stage, in mm/sec. Defaults to focus_max_speed. """ - logger.info( - f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec" - ) + logger.info(f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec") # Validation of inputs if direction not in ["UP", "DOWN"]: @@ -304,13 +286,11 @@ def focus(self, direction, distance, speed=focus_max_speed): # NEMA14 pump with 3 rollers is 0.509 mL per round, actual calculation at # Stepper is 200 steps/round, or 393steps/ml # https://www.wolframalpha.com/input/?i=pi+*+%280.8mm%29%C2%B2+*+54mm+*+3 - + @logger.catch def run(self): """This is the function that needs to be started to create a thread""" - logger.info( - f"The stepper control process has been started in process {os.getpid()}" - ) + logger.info(f"The stepper control process has been started in process {os.getpid()}") # Creates the MQTT Client # We have to create it here, otherwise when the process running run is started @@ -362,4 +342,4 @@ def run(self): # This needs to be in a threading or multiprocessing wrapper focus_thread = FocusProcess() focus_thread.start() - focus_thread.join() \ No newline at end of file + focus_thread.join() diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 5c631a08..0ca7af1a 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -1,16 +1,17 @@ -# Libraries to control the steppers for focusing and pumping -import time +# Libraries to control the steppers for pumping import json -import os -import planktoscope.mqtt import multiprocessing -import RPi.GPIO +import os +import time -import shush +import RPi.GPIO # Logger library compatible with multiprocessing from loguru import logger +import planktoscope.mqtt +import shush + logger.info("planktoscope.stepper is loaded") @@ -124,10 +125,10 @@ def deceleration(self, deceleration): class PumpProcess(multiprocessing.Process): - + # 5200 for custom NEMA14 pump with 0.8mm ID Tube pump_steps_per_ml = 507 - + # pump max speed is in ml/min pump_max_speed = 50 @@ -145,19 +146,15 @@ def __init__(self, event): 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" - ) + logger.info("The hardware configuration file doesn't exists, using defaults") configuration = {} reverse = False # parse the config data. If the key is absent, we are using the default value reverse = configuration.get("stepper_reverse", reverse) - - self.pump_steps_per_ml = configuration.get( - "pump_steps_per_ml", self.pump_steps_per_ml - ) + + 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) # define the names for the 2 exsting steppers @@ -176,9 +173,7 @@ def __init__(self, event): self.pump_stepper.acceleration = 2000 self.pump_stepper.deceleration = self.pump_stepper.acceleration - self.pump_stepper.speed = ( - self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - ) + self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 logger.info("Stepper initialisation is over") @@ -192,9 +187,7 @@ def __message_pump(self, last_message): logger.info("The pump has been interrupted") # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/pump", '{"status":"Interrupted"}' - ) + self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') elif last_message["action"] == "move": logger.debug("We have received a move pump command") @@ -204,9 +197,7 @@ def __message_pump(self, last_message): or "volume" not in last_message or "flowrate" not in last_message ): - logger.error( - f"The received message has the wrong argument {last_message}" - ) + logger.error(f"The received message has the wrong argument {last_message}") self.actuator_client.client.publish( "status/pump", '{"status":"Error, the message is missing an argument"}', @@ -231,7 +222,6 @@ def __message_pump(self, last_message): else: logger.warning(f"The received message was not understood {last_message}") - def treat_command(self): command = "" logger.info("We received a new message") @@ -246,9 +236,7 @@ def treat_command(self): elif command == "focus": self.__message_focus(last_message) elif command != "": - logger.warning( - f"We did not understand the received request {command} - {last_message}" - ) + logger.warning(f"We did not understand the received request {command} - {last_message}") def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper @@ -272,9 +260,7 @@ def pump(self, direction, volume, speed=pump_max_speed): logger.debug(f"The number of microsteps that will be applied is {nb_steps}") if speed > self.pump_max_speed: speed = self.pump_max_speed - logger.warning( - f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" - ) + logger.warning(f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min") steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 logger.debug(f"There will be a speed of {steps_per_second} steps per second") self.pump_stepper.speed = int(steps_per_second) @@ -299,9 +285,7 @@ def pump(self, direction, volume, speed=pump_max_speed): @logger.catch def run(self): """This is the function that needs to be started to create a thread""" - logger.info( - f"The stepper control process has been started in process {os.getpid()}" - ) + logger.info(f"The stepper control process has been started in process {os.getpid()}") # Creates the MQTT Client # We have to create it here, otherwise when the process running run is started @@ -313,7 +297,6 @@ def run(self): ) # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') - logger.success("The pump is READY!") while not self.stop_event.is_set(): @@ -327,12 +310,12 @@ def run(self): ) self.pump_started = False self.pump_stepper.release() - + time.sleep(0.01) logger.info("Shutting down the stepper process") self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') self.pump_stepper.shutdown() - + self.actuator_client.shutdown() logger.success("Stepper process shut down! See you!") @@ -344,4 +327,4 @@ def run(self): # This needs to be in a threading or multiprocessing wrapper pump_thread = PumpProcess() pump_thread.start() - pump_thread.join() \ No newline at end of file + pump_thread.join() From 02bf69fc2e2cd7f5ad9cc73e0a3a117a8e59b7a5 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 23 May 2024 15:13:14 +0100 Subject: [PATCH 06/35] changed the focus and pump process --- control/planktoscopehat/main.py | 19 +++++++---- control/planktoscopehat/planktoscope/focus.py | 33 +++++-------------- control/planktoscopehat/planktoscope/pump.py | 13 ++------ 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/control/planktoscopehat/main.py b/control/planktoscopehat/main.py index d0a02841..8866852a 100644 --- a/control/planktoscopehat/main.py +++ b/control/planktoscopehat/main.py @@ -6,10 +6,12 @@ from loguru import logger +import planktoscope.focus import planktoscope.mqtt -import planktoscope.stepper + import planktoscope.light # Fan HAT LEDs import planktoscope.identity +import planktoscope.pump import planktoscope.uuidName # Note: this is deprecated. import planktoscope.display # Fan HAT OLED screen from planktoscope.imagernew import mqtt as imagernew @@ -87,10 +89,15 @@ def handler_stop_signals(signum, frame): shutdown_event = multiprocessing.Event() shutdown_event.clear() - # Starts the stepper process for actuators - logger.info("Starting the stepper control process (step 2/5)") - stepper_thread = planktoscope.stepper.StepperProcess(shutdown_event) - stepper_thread.start() + # Starts the focus process for actuators + logger.info("Starting the focus control process (step 2/5)") + focus_thread = planktoscope.focus.FocusProcess(shutdown_event) + focus_thread.start() + + # Starts the pump process for actuators + logger.info("Starting the focus control process (step 2/5)") + pump_thread = planktoscope.pump.PumpProcess(shutdown_event) + pump_thread.start() # TODO try to isolate the imager thread (or another thread) # Starts the imager control process @@ -118,7 +125,7 @@ def handler_stop_signals(signum, frame): logger.success("Looks like everything is set up and running, have fun!") - + #add the implemented code in the main branch while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index e033399f..f635874b 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -1,4 +1,4 @@ -# Libraries to control the steppers for focusing and pumping +# Libraries to control the steppers for focusing import json import multiprocessing import os @@ -137,7 +137,7 @@ def __init__(self, event): self.stop_event = event self.focus_started = False - self.pump_started = False + if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json @@ -158,10 +158,10 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: - self.pump_stepper = stepper(STEPPER2) + self.focus_stepper = stepper(STEPPER1, size=45) else: - self.pump_stepper = stepper(STEPPER1) + self.focus_stepper = stepper(STEPPER2, size=45) # Set stepper controller max speed @@ -170,11 +170,7 @@ def __init__(self, event): self.focus_stepper.deceleration = self.focus_stepper.acceleration self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 - self.pump_stepper.acceleration = 2000 - self.pump_stepper.deceleration = self.pump_stepper.acceleration - self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - - logger.info("Stepper initialisation is over") + logger.info("the focus stepper initialisation is over") def __message_focus(self, last_message): logger.debug("We have received a focusing request") @@ -220,9 +216,8 @@ def treat_command(self): logger.debug(command) self.actuator_client.read_message() - if command == "pump": - self.__message_pump(last_message) - elif command == "focus": + + if command == "focus": self.__message_focus(last_message) elif command != "": logger.warning(f"We did not understand the received request {command} - {last_message}") @@ -301,22 +296,12 @@ def run(self): topic="actuator/#", name="actuator_client" ) # Publish the status "Ready" to via MQTT to Node-RED - self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') - # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') logger.success("Stepper is READY!") while not self.stop_event.is_set(): if self.actuator_client.new_message_received(): self.treat_command() - if self.pump_started and self.pump_stepper.at_goal(): - logger.success("The pump movement is over!") - self.actuator_client.client.publish( - "status/pump", - '{"status":"Done"}', - ) - self.pump_started = False - self.pump_stepper.release() if self.focus_started and self.focus_stepper.at_goal(): logger.success("The focus movement is over!") self.actuator_client.client.publish( @@ -324,12 +309,10 @@ def run(self): '{"status":"Done"}', ) self.focus_started = False - self.pump_stepper.release() + self.stop_event.set() # Signal pump to stop time.sleep(0.01) logger.info("Shutting down the stepper process") - self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') - self.pump_stepper.shutdown() self.focus_stepper.shutdown() self.actuator_client.shutdown() logger.success("Stepper process shut down! See you!") diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 0ca7af1a..26ece84b 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -160,17 +160,12 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: self.pump_stepper = stepper(STEPPER2) - self.focus_stepper = stepper(STEPPER1, size=45) + else: self.pump_stepper = stepper(STEPPER1) - self.focus_stepper = stepper(STEPPER2, size=45) + # Set pump controller max speed - - self.focus_stepper.acceleration = 1000 - self.focus_stepper.deceleration = self.focus_stepper.acceleration - self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 - self.pump_stepper.acceleration = 2000 self.pump_stepper.deceleration = self.pump_stepper.acceleration self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 @@ -233,8 +228,6 @@ def treat_command(self): if command == "pump": self.__message_pump(last_message) - elif command == "focus": - self.__message_focus(last_message) elif command != "": logger.warning(f"We did not understand the received request {command} - {last_message}") @@ -271,7 +264,7 @@ def pump(self, direction, volume, speed=pump_max_speed): f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', ) - # Depending on direction, select the right direction for the focus + # Depending on direction, select the right direction for the pump if direction == "FORWARD": self.pump_started = True self.pump_stepper.go(FORWARD, nb_steps) From 3b1c9c60da9bcba560d3e962f1f8a6321fc13a2b Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 27 May 2024 09:16:09 +0100 Subject: [PATCH 07/35] some changes in the main.py --- control/planktoscopehat/main.py | 6 ++++-- control/planktoscopehat/planktoscope/focus.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/control/planktoscopehat/main.py b/control/planktoscopehat/main.py index 8866852a..32916ecb 100644 --- a/control/planktoscopehat/main.py +++ b/control/planktoscopehat/main.py @@ -142,13 +142,15 @@ def handler_stop_signals(signum, frame): shutdown_event.set() time.sleep(1) - stepper_thread.join() + focus_thread.join() + pump_thread.join() if imager_thread: imager_thread.join() if light_thread: light_thread.join() - stepper_thread.close() + focus_thread.close() + pump_thread.close() if imager_thread: imager_thread.close() if light_thread: diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index f635874b..2ab75e61 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -309,7 +309,7 @@ def run(self): '{"status":"Done"}', ) self.focus_started = False - self.stop_event.set() # Signal pump to stop + self.focus_stepper.release() time.sleep(0.01) logger.info("Shutting down the stepper process") self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') From 16cf0cda43c3288b0d4868a0bb39fbb5820aded8 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 27 May 2024 09:38:35 +0100 Subject: [PATCH 08/35] small changes --- control/planktoscopehat/main.py | 23 ++++++++++++++----- control/planktoscopehat/planktoscope/focus.py | 8 +++---- control/planktoscopehat/planktoscope/pump.py | 3 +-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/control/planktoscopehat/main.py b/control/planktoscopehat/main.py index 32916ecb..4533ec66 100644 --- a/control/planktoscopehat/main.py +++ b/control/planktoscopehat/main.py @@ -125,18 +125,29 @@ def handler_stop_signals(signum, frame): logger.success("Looks like everything is set up and running, have fun!") - #add the implemented code in the main branch + # With the creation of this dictionary to keep track of running threads, we can easily + running_threads = { + "pump": pump_thread, + "focus": focus_thread, + "light": light_thread, + "imager": imager_thread + } + while run: # TODO look into ways of restarting the dead threads logger.trace("Running around in circles while waiting for someone to die!") - if not stepper_thread.is_alive(): - logger.error("The stepper process died unexpectedly! Oh no!") - break - if not imager_thread or not imager_thread.is_alive(): - logger.error("The imager process died unexpectedly! Oh no!") + # Check if any threads have terminated unexpectedly and log the error without exiting + for thread_name, thread in running_threads.items(): + if not thread or not thread.is_alive(): + logger.error(f"The {thread_name} process terminated unexpectedly!") + del running_threads[thread_name] # Remove the dead thread from the dictionary + # Check if all threads have terminated so we can exit the program + if not running_threads: #checks if there is no running thread left + logger.error("All processes terminated unexpectedly! Exiting...") break time.sleep(1) + display.display_text("Bye Bye!") logger.info("Shutting down the shop") shutdown_event.set() diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 2ab75e61..bb6ba850 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -1,4 +1,4 @@ -# Libraries to control the steppers for focusing +# Libraries to control the steppers for focusing import json import multiprocessing import os @@ -137,7 +137,6 @@ def __init__(self, event): self.stop_event = event self.focus_started = False - if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json @@ -158,10 +157,10 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: - + self.focus_stepper = stepper(STEPPER1, size=45) else: - + self.focus_stepper = stepper(STEPPER2, size=45) # Set stepper controller max speed @@ -216,7 +215,6 @@ def treat_command(self): logger.debug(command) self.actuator_client.read_message() - if command == "focus": self.__message_focus(last_message) elif command != "": diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 26ece84b..7eb0da5c 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -160,10 +160,9 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: self.pump_stepper = stepper(STEPPER2) - + else: self.pump_stepper = stepper(STEPPER1) - # Set pump controller max speed self.pump_stepper.acceleration = 2000 From 0286d2c73977074965f531079a49f6b53f83f12e Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 27 May 2024 09:59:31 +0100 Subject: [PATCH 09/35] type error resolved --- control/planktoscopehat/planktoscope/focus.py | 13 +++++++++---- control/planktoscopehat/planktoscope/pump.py | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index bb6ba850..3b30f5d5 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -209,9 +209,9 @@ def __message_focus(self, last_message): def treat_command(self): command = "" logger.info("We received a new message") - last_message = self.actuator_client.msg["payload"] + last_message = self.actuator_client.msg["payload"] # type: ignore logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] + command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore logger.debug(command) self.actuator_client.read_message() @@ -321,6 +321,11 @@ def run(self): # TODO This should be a test suite for this library # Starts the stepper thread for actuators # This needs to be in a threading or multiprocessing wrapper - focus_thread = FocusProcess() + stop_event = multiprocessing.Event() + focus_thread = FocusProcess(event=stop_event) focus_thread.start() - focus_thread.join() + try: + focus_thread.join() + except KeyboardInterrupt: + stop_event.set() + focus_thread.join() diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 7eb0da5c..a3159a1e 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -219,9 +219,9 @@ def __message_pump(self, last_message): def treat_command(self): command = "" logger.info("We received a new message") - last_message = self.actuator_client.msg["payload"] + last_message = self.actuator_client.msg["payload"] # type: ignore logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] + command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore logger.debug(command) self.actuator_client.read_message() @@ -317,6 +317,11 @@ def run(self): # TODO This should be a test suite for this library # Starts the stepper thread for actuators # This needs to be in a threading or multiprocessing wrapper - pump_thread = PumpProcess() + stop_event = multiprocessing.Event() + pump_thread = PumpProcess(event=stop_event) pump_thread.start() - pump_thread.join() + try: + pump_thread.join() + except KeyboardInterrupt: + stop_event.set() + pump_thread.join() From cd6bfb9e0b88e93a75107abffb695fb6e7a65529 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Tue, 28 May 2024 21:09:11 -0700 Subject: [PATCH 10/35] Suppress type-checking for external packages --- control/planktoscopehat/planktoscope/focus.py | 2 +- control/planktoscopehat/planktoscope/pump.py | 2 +- control/pyproject.toml | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 3b30f5d5..47c1acdd 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -4,7 +4,7 @@ import os import time -import RPi.GPIO +import RPi.GPIO # type: ignore # Logger library compatible with multiprocessing from loguru import logger diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index a3159a1e..a04d27a6 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -4,7 +4,7 @@ import os import time -import RPi.GPIO +import RPi.GPIO # type: ignore # Logger library compatible with multiprocessing from loguru import logger diff --git a/control/pyproject.toml b/control/pyproject.toml index 3a3b658f..89e8ef21 100644 --- a/control/pyproject.toml +++ b/control/pyproject.toml @@ -240,6 +240,13 @@ exclude = [ 'planktoscopehat/planktoscope/imager/.*', ] +[[tool.mypy.overrides]] +# the skip module is an externally-provided package which we don't want to touch: +module = [ + 'shush.*', +] +follow_imports = 'skip' + [tool.pylama] # We are gradually introducing linting as we rewrite each module; we haven't rewritten the following # files yet: From c528c56bfb20dfc6629568494e332415347f403f Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Tue, 28 May 2024 21:19:00 -0700 Subject: [PATCH 11/35] Resolve remaining type-checker and formatter complaints --- control/planktoscopehat/planktoscope/focus.py | 15 ++++++++------- control/planktoscopehat/planktoscope/pump.py | 12 ++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 47c1acdd..692809a5 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -3,8 +3,7 @@ import multiprocessing import os import time - -import RPi.GPIO # type: ignore +import typing # Logger library compatible with multiprocessing from loguru import logger @@ -31,12 +30,13 @@ def __init__(self, stepper, size=0): Args: stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper - size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable + size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not + applicable """ self.__stepper = shush.Motor(stepper) self.__size = size self.__goal = 0 - self.__direction = "" + self.__direction: typing.Optional[int] = None self.__stepper.disable_motor() def at_goal(self): @@ -128,7 +128,8 @@ class FocusProcess(multiprocessing.Process): focus_steps_per_mm = 40 # 507 steps per ml for PlanktoScope standard - # 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 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): @@ -288,8 +289,8 @@ def run(self): # Creates the MQTT Client # We have to create it here, otherwise when the process running run is started # it doesn't see changes and calls made by self.actuator_client because this one - # only exist in the master process - # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class + # only exist in the master process. See + # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class self.actuator_client = planktoscope.mqtt.MQTT_Client( topic="actuator/#", name="actuator_client" ) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index a04d27a6..4a5bb80c 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -3,8 +3,7 @@ import multiprocessing import os import time - -import RPi.GPIO # type: ignore +import typing # Logger library compatible with multiprocessing from loguru import logger @@ -31,12 +30,13 @@ def __init__(self, stepper, size=0): Args: stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper - size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable + size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not + applicable """ self.__stepper = shush.Motor(stepper) self.__size = size self.__goal = 0 - self.__direction = "" + self.__direction: typing.Optional[int] = None self.__stepper.disable_motor() def at_goal(self): @@ -282,8 +282,8 @@ def run(self): # Creates the MQTT Client # We have to create it here, otherwise when the process running run is started # it doesn't see changes and calls made by self.actuator_client because this one - # only exist in the master process - # see https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class + # only exist in the master process. See + # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class self.actuator_client = planktoscope.mqtt.MQTT_Client( topic="actuator/#", name="actuator_client" ) From a266248c1ac1a5685c09038ad265f0e9a51eb7d0 Mon Sep 17 00:00:00 2001 From: Ethan Li Date: Tue, 28 May 2024 21:25:55 -0700 Subject: [PATCH 12/35] Resolve another linter error --- control/planktoscopehat/planktoscope/focus.py | 6 ++---- control/planktoscopehat/planktoscope/pump.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 692809a5..d6b96e4a 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -8,8 +8,8 @@ # Logger library compatible with multiprocessing from loguru import logger -import planktoscope.mqtt import shush +from planktoscope import mqtt logger.info("planktoscope.stepper is loaded") @@ -291,9 +291,7 @@ def run(self): # it doesn't see changes and calls made by self.actuator_client because this one # only exist in the master process. See # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class - self.actuator_client = planktoscope.mqtt.MQTT_Client( - topic="actuator/#", name="actuator_client" - ) + self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 4a5bb80c..6b974928 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -8,8 +8,8 @@ # Logger library compatible with multiprocessing from loguru import logger -import planktoscope.mqtt import shush +from planktoscope import mqtt logger.info("planktoscope.stepper is loaded") @@ -284,9 +284,7 @@ def run(self): # it doesn't see changes and calls made by self.actuator_client because this one # only exist in the master process. See # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class - self.actuator_client = planktoscope.mqtt.MQTT_Client( - topic="actuator/#", name="actuator_client" - ) + self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') From 2b5d9d230ea83523afed395e5cbdf03557ef307c Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 30 May 2024 14:34:22 +0100 Subject: [PATCH 13/35] fixed errors --- control/planktoscopehat/planktoscope/focus.py | 52 ++++++++++++++----- control/planktoscopehat/planktoscope/pump.py | 50 +++++++++++++----- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index d6b96e4a..b8685af5 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -1,3 +1,8 @@ +""" +This module provides the functionality to control the focus mechanism +of the Planktoscope. +""" + # Libraries to control the steppers for focusing import json import multiprocessing @@ -5,7 +10,6 @@ import time import typing -# Logger library compatible with multiprocessing from loguru import logger import shush @@ -24,8 +28,12 @@ STEPPER2 = 1 -class stepper: - def __init__(self, stepper, size=0): +class Stepper: + """ + This class controls the stepper motor used for adjusting the focus. + """ + + def __init__(self, stepper, size): """Initialize the stepper class Args: @@ -33,11 +41,10 @@ def __init__(self, stepper, size=0): size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable """ - self.__stepper = shush.Motor(stepper) + self.__stepper = shush.Motor(stepper).disable_motor() self.__size = size self.__goal = 0 self.__direction: typing.Optional[int] = None - self.__stepper.disable_motor() def at_goal(self): """Is the motor at its goal @@ -56,11 +63,12 @@ def is_moving(self): return self.__stepper.get_velocity() != 0 def go(self, direction, distance): - """move in the given direction for the given distance + """ + Move in the given direction for the given distance. Args: - direction: gives the movement direction - distance: + direction (int): The movement direction (FORWARD or BACKWARD). + distance (int): The distance to move. """ self.__direction = direction if self.__direction == FORWARD: @@ -73,12 +81,17 @@ def go(self, direction, distance): self.__stepper.go_to(self.__goal) def shutdown(self): - """Shutdown everything ASAP""" + """ + Shutdown everything ASAP. + """ self.__stepper.stop_motor() self.__stepper.disable_motor() self.__goal = self.__stepper.get_position() def release(self): + """ + Disable the stepper motor. + """ self.__stepper.disable_motor() @property @@ -133,6 +146,12 @@ class FocusProcess(multiprocessing.Process): focus_max_speed = 5 def __init__(self, event): + """ + Initialize the FocusProcess. + + Args: + event (multiprocessing.Event): Event to signal the process to stop. + """ super(FocusProcess, self).__init__() logger.info("Initialising the stepper process") @@ -141,7 +160,7 @@ def __init__(self, event): if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json - with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: + with open("/home/pi/PlanktoScope/hardware.json", "r", encoding="utf-8") as config_file: # TODO #100 insert guard for config_file empty configuration = json.load(config_file) logger.debug(f"Hardware configuration loaded is {configuration}") @@ -159,10 +178,10 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: - self.focus_stepper = stepper(STEPPER1, size=45) + self.focus_stepper = Stepper(STEPPER1, size=45) else: - self.focus_stepper = stepper(STEPPER2, size=45) + self.focus_stepper = Stepper(STEPPER2, size=45) # Set stepper controller max speed @@ -173,6 +192,12 @@ def __init__(self, event): logger.info("the focus stepper initialisation is over") def __message_focus(self, last_message): + """ + Handle a focusing request. + + Args: + last_message (dict): The last received message containing the focus action and parameters. + """ logger.debug("We have received a focusing request") # If a new received command is "focus" but args contains "stop" we stop! if last_message["action"] == "stop": @@ -208,6 +233,9 @@ def __message_focus(self, last_message): logger.warning(f"The received message was not understood {last_message}") def treat_command(self): + """ + Process a received command. + """ command = "" logger.info("We received a new message") last_message = self.actuator_client.msg["payload"] # type: ignore diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 6b974928..7bfa009b 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -1,3 +1,8 @@ +""" +This module provides the functionality to control the pump mechanism +of the Planktoscope. +""" + # Libraries to control the steppers for pumping import json import multiprocessing @@ -5,7 +10,6 @@ import time import typing -# Logger library compatible with multiprocessing from loguru import logger import shush @@ -24,8 +28,12 @@ STEPPER2 = 1 -class stepper: - def __init__(self, stepper, size=0): +class Stepper: + """ + This class controls the stepper motor used for adjusting the pump. + """ + + def __init__(self, stepper): """Initialize the stepper class Args: @@ -33,11 +41,9 @@ def __init__(self, stepper, size=0): size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable """ - self.__stepper = shush.Motor(stepper) - self.__size = size + self.__stepper = shush.Motor(stepper).disable_motor() self.__goal = 0 self.__direction: typing.Optional[int] = None - self.__stepper.disable_motor() def at_goal(self): """Is the motor at its goal @@ -56,11 +62,12 @@ def is_moving(self): return self.__stepper.get_velocity() != 0 def go(self, direction, distance): - """move in the given direction for the given distance + """ + Move in the given direction for the given distance. Args: - direction: gives the movement direction - distance: + direction (int): The movement direction (FORWARD or BACKWARD). + distance (int): The distance to move. """ self.__direction = direction if self.__direction == FORWARD: @@ -73,7 +80,9 @@ def go(self, direction, distance): self.__stepper.go_to(self.__goal) def shutdown(self): - """Shutdown everything ASAP""" + """ + Shutdown everything ASAP. + """ self.__stepper.stop_motor() self.__stepper.disable_motor() self.__goal = self.__stepper.get_position() @@ -133,6 +142,12 @@ class PumpProcess(multiprocessing.Process): pump_max_speed = 50 def __init__(self, event): + """ + Initialize the pump process. + + Args: + event (multiprocessing.Event): Event to control the stopping of the process + """ super(PumpProcess, self).__init__() logger.info("Initialising the stepper process") @@ -141,7 +156,7 @@ def __init__(self, event): if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json - with open("/home/pi/PlanktoScope/hardware.json", "r") as config_file: + with open("/home/pi/PlanktoScope/hardware.json", "r", encoding="utf-8") as config_file: # TODO #100 insert guard for config_file empty configuration = json.load(config_file) logger.debug(f"Hardware configuration loaded is {configuration}") @@ -159,10 +174,10 @@ def __init__(self, event): # define the names for the 2 exsting steppers if reverse: - self.pump_stepper = stepper(STEPPER2) + self.pump_stepper = Stepper(STEPPER2) else: - self.pump_stepper = stepper(STEPPER1) + self.pump_stepper = Stepper(STEPPER1) # Set pump controller max speed self.pump_stepper.acceleration = 2000 @@ -172,6 +187,12 @@ def __init__(self, event): logger.info("Stepper initialisation is over") def __message_pump(self, last_message): + """ + Handle pump commands from received messages. + + Args: + last_message (dict): The last received message containing pump commands + """ logger.debug("We have received a pumping command") if last_message["action"] == "stop": logger.debug("We have received a stop pump command") @@ -217,6 +238,9 @@ def __message_pump(self, last_message): logger.warning(f"The received message was not understood {last_message}") def treat_command(self): + """ + Treat the received command. + """ command = "" logger.info("We received a new message") last_message = self.actuator_client.msg["payload"] # type: ignore From 80e90ba48405edb4ae75d7aa96f5999d082dfccb Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 30 May 2024 15:15:12 +0100 Subject: [PATCH 14/35] fixed errors --- control/planktoscopehat/planktoscope/focus.py | 2 +- control/planktoscopehat/planktoscope/pump.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index b8685af5..572c14b0 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -196,7 +196,7 @@ def __message_focus(self, last_message): Handle a focusing request. Args: - last_message (dict): The last received message containing the focus action and parameters. + last_message (dict): The last received message. """ logger.debug("We have received a focusing request") # If a new received command is "focus" but args contains "stop" we stop! diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 7bfa009b..a9e0bbe5 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -191,7 +191,7 @@ def __message_pump(self, last_message): Handle pump commands from received messages. Args: - last_message (dict): The last received message containing pump commands + last_message (dict): The last received message containing pump commands. """ logger.debug("We have received a pumping command") if last_message["action"] == "stop": From 00bfaa5b41d50546d403df4a95afb86d3fe6f518 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 3 Jun 2024 10:33:30 +0100 Subject: [PATCH 15/35] resolving some errors --- control/planktoscopehat/planktoscope/focus.py | 23 ++++++++++++++----- control/planktoscopehat/planktoscope/pump.py | 8 +++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 572c14b0..b6f44c78 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -17,14 +17,9 @@ logger.info("planktoscope.stepper is loaded") - -"""Step forward""" FORWARD = 1 -""""Step backward""" BACKWARD = 2 -"""Stepper controller 1""" STEPPER1 = 0 -""""Stepper controller 2""" STEPPER2 = 1 @@ -96,6 +91,10 @@ def release(self): @property def speed(self): + """ + Returns: + int: The maximum speed (ramp_VMAX) of the stepper motor. + """ return self.__stepper.ramp_VMAX @speed.setter @@ -110,6 +109,10 @@ def speed(self, speed): @property def acceleration(self): + """ + Returns: + int: The maximum acceleration (ramp_AMAX) of the stepper motor. + """ return self.__stepper.ramp_AMAX @acceleration.setter @@ -124,6 +127,10 @@ def acceleration(self, acceleration): @property def deceleration(self): + """ + Returns: + int: The maximum deceleration (ramp_DMAX) of the stepper motor. + """ return self.__stepper.ramp_DMAX @deceleration.setter @@ -138,8 +145,12 @@ def deceleration(self, deceleration): class FocusProcess(multiprocessing.Process): - focus_steps_per_mm = 40 + """ + This class manages the focusing process using a stepper motor. + """ + # 507 steps per ml for PlanktoScope standard + focus_steps_per_mm = 40 # focus max speed is in mm/sec and is limited by the maximum number of pulses per second the # PlanktoScope can send diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index a9e0bbe5..30e7f747 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -17,14 +17,9 @@ logger.info("planktoscope.stepper is loaded") - -"""Step forward""" FORWARD = 1 -""""Step backward""" BACKWARD = 2 -"""Stepper controller 1""" STEPPER1 = 0 -""""Stepper controller 2""" STEPPER2 = 1 @@ -134,6 +129,9 @@ def deceleration(self, deceleration): class PumpProcess(multiprocessing.Process): + """ + This class manages the pumping process using a stepper motor. + """ # 5200 for custom NEMA14 pump with 0.8mm ID Tube pump_steps_per_ml = 507 From 75c2811954a089450f1fa5e1577da331f98f96de Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 6 Jun 2024 09:07:18 +0100 Subject: [PATCH 16/35] change of one line --- control/planktoscopehat/planktoscope/focus.py | 3 ++- control/planktoscopehat/planktoscope/pump.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index b6f44c78..461010de 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -36,7 +36,8 @@ def __init__(self, stepper, size): size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable """ - self.__stepper = shush.Motor(stepper).disable_motor() + self.__stepper = shush.Motor(stepper) + self.__stepper.disable_motor() self.__size = size self.__goal = 0 self.__direction: typing.Optional[int] = None diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 30e7f747..659dc176 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -36,7 +36,8 @@ def __init__(self, stepper): size (int): maximum number of steps of this stepper (aka stage size). Can be 0 if not applicable """ - self.__stepper = shush.Motor(stepper).disable_motor() + self.__stepper = shush.Motor(stepper) + self.__stepper.disable_motor() self.__goal = 0 self.__direction: typing.Optional[int] = None From 99b623af62b1606bf82a46d6a08e302211c89237 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Thu, 6 Jun 2024 09:16:51 +0100 Subject: [PATCH 17/35] change of one line --- control/planktoscopehat/planktoscope/pump.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 659dc176..7772d71e 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -84,10 +84,17 @@ def shutdown(self): self.__goal = self.__stepper.get_position() def release(self): + """ + Disable the stepper motor. + """ self.__stepper.disable_motor() @property def speed(self): + """ + Returns: + int: The maximum speed (ramp_VMAX) of the stepper motor. + """ return self.__stepper.ramp_VMAX @speed.setter @@ -102,6 +109,10 @@ def speed(self, speed): @property def acceleration(self): + """ + Returns: + int: The maximum acceleration (ramp_AMAX) of the stepper motor. + """ return self.__stepper.ramp_AMAX @acceleration.setter @@ -116,6 +127,10 @@ def acceleration(self, acceleration): @property def deceleration(self): + """ + Returns: + int: The maximum deceleration (ramp_DMAX) of the stepper motor. + """ return self.__stepper.ramp_DMAX @deceleration.setter From 3231f265b0aeaa30ca7a0144fcad83a11313e77e Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 19:43:17 +0100 Subject: [PATCH 18/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 77 ++++++++++---------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 7772d71e..516d28c1 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -10,12 +10,12 @@ import time import typing -from loguru import logger +from loguru import logger as loguru_logger import shush from planktoscope import mqtt -logger.info("planktoscope.stepper is loaded") +loguru_logger.info("planktoscope.stepper is loaded") FORWARD = 1 BACKWARD = 2 @@ -71,7 +71,7 @@ def go(self, direction, distance): elif self.__direction == BACKWARD: self.__goal = int(self.__stepper.get_position() - distance) else: - logger.error(f"The given direction is wrong {direction}") + loguru_logger.error(f"The given direction is wrong {direction}") self.__stepper.enable_motor() self.__stepper.go_to(self.__goal) @@ -104,7 +104,7 @@ def speed(self, speed): Args: speed (int): speed of the movement by the stepper, in microsteps unit/s """ - logger.debug(f"Setting stepper speed to {speed}") + loguru_logger.debug(f"Setting stepper speed to {speed}") self.__stepper.ramp_VMAX = int(speed) @property @@ -122,7 +122,7 @@ def acceleration(self, acceleration): Args: acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² """ - logger.debug(f"Setting stepper acceleration to {acceleration}") + loguru_logger.debug(f"Setting stepper acceleration to {acceleration}") self.__stepper.ramp_AMAX = int(acceleration) @property @@ -140,7 +140,7 @@ def deceleration(self, deceleration): Args: deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² """ - logger.debug(f"Setting stepper deceleration to {deceleration}") + loguru_logger.debug(f"Setting stepper deceleration to {deceleration}") self.__stepper.ramp_DMAX = int(deceleration) @@ -162,20 +162,21 @@ def __init__(self, event): Args: event (multiprocessing.Event): Event to control the stopping of the process """ - super(PumpProcess, self).__init__() - logger.info("Initialising the stepper process") + super().__init__() + loguru_logger.info("Initialising the stepper process") self.stop_event = event self.pump_started = False + self.actuator_client = None # Initialize actuator_client to None 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: # TODO #100 insert guard for config_file empty configuration = json.load(config_file) - logger.debug(f"Hardware configuration loaded is {configuration}") + loguru_logger.debug(f"Hardware configuration loaded is {configuration}") else: - logger.info("The hardware configuration file doesn't exists, using defaults") + loguru_logger.info("The hardware configuration file doesn't exists, using defaults") configuration = {} reverse = False @@ -198,7 +199,7 @@ def __init__(self, event): self.pump_stepper.deceleration = self.pump_stepper.acceleration self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - logger.info("Stepper initialisation is over") + loguru_logger.info("Stepper initialisation is over") def __message_pump(self, last_message): """ @@ -209,24 +210,24 @@ def __message_pump(self, last_message): """ logger.debug("We have received a pumping command") if last_message["action"] == "stop": - logger.debug("We have received a stop pump command") + loguru_logger.debug("We have received a stop pump command") self.pump_stepper.shutdown() # Print status - logger.info("The pump has been interrupted") + loguru_logger.info("The pump has been interrupted") # Publish the status "Interrupted" to via MQTT to Node-RED self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') elif last_message["action"] == "move": - logger.debug("We have received a move pump command") + loguru_logger.debug("We have received a move pump command") if ( "direction" not in last_message or "volume" not in last_message or "flowrate" not in last_message ): - logger.error(f"The received message has the wrong argument {last_message}") + loguru_logger.error(f"The received message has the wrong argument {last_message}") self.actuator_client.client.publish( "status/pump", '{"status":"Error, the message is missing an argument"}', @@ -238,35 +239,37 @@ def __message_pump(self, last_message): volume = float(last_message["volume"]) # Get number of steps from the different received arguments flowrate = float(last_message["flowrate"]) - if flowrate == 0: - logger.error("The flowrate should not be == 0") + if (flowrate := float(last_message["flowrate"])) == 0: + loguru_logger.error("The flowrate should not be == 0") self.actuator_client.client.publish( "status/pump", '{"status":"Error, The flowrate should not be == 0"}' ) return # Print status - logger.info("The pump is started.") + loguru_logger.info("The pump is started.") self.pump(direction, volume, flowrate) else: - logger.warning(f"The received message was not understood {last_message}") + loguru_logger.warning(f"The received message was not understood {last_message}") def treat_command(self): """ Treat the received command. """ command = "" - logger.info("We received a new message") + loguru_logger.info("We received a new message") last_message = self.actuator_client.msg["payload"] # type: ignore - logger.debug(last_message) + loguru_logger.debug(last_message) command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore - logger.debug(command) + loguru_logger.debug(command) self.actuator_client.read_message() if command == "pump": self.__message_pump(last_message) elif command != "": - logger.warning(f"We did not understand the received request {command} - {last_message}") + loguru_logger.warning( + f"We did not understand the received request {command} - {last_message}" + ) def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper @@ -277,22 +280,24 @@ def pump(self, direction, volume, speed=pump_max_speed): speed (int, optional): speed of pumping, in mL/min. Defaults to pump_max_speed. """ - logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") + loguru_logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") # Validation of inputs if direction not in ["FORWARD", "BACKWARD"]: - logger.error("The direction command is not recognised") - logger.error("It should be either FORWARD or BACKWARD") + loguru_logger.error("The direction command is not recognised") + loguru_logger.error("It should be either FORWARD or BACKWARD") return # TMC5160 is configured for 256 microsteps nb_steps = round(self.pump_steps_per_ml * volume * 256, 0) - logger.debug(f"The number of microsteps that will be applied is {nb_steps}") + loguru_logger.debug(f"The number of microsteps that will be applied is {nb_steps}") if speed > self.pump_max_speed: speed = self.pump_max_speed - logger.warning(f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min") + loguru_logger.warning( + f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" + ) steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 - logger.debug(f"There will be a speed of {steps_per_second} steps per second") + loguru_logger.debug(f"There will be a speed of {steps_per_second} steps per second") self.pump_stepper.speed = int(steps_per_second) # Publish the status "Started" to via MQTT to Node-RED @@ -315,23 +320,19 @@ def pump(self, direction, volume, speed=pump_max_speed): @logger.catch def run(self): """This is the function that needs to be started to create a thread""" - logger.info(f"The stepper control process has been started in process {os.getpid()}") + loguru_logger.info(f"The stepper control process has been started in process {os.getpid()}") # Creates the MQTT Client - # We have to create it here, otherwise when the process running run is started - # it doesn't see changes and calls made by self.actuator_client because this one - # only exist in the master process. See - # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') - logger.success("The pump is READY!") + loguru_logger.success("The pump is READY!") while not self.stop_event.is_set(): if self.actuator_client.new_message_received(): self.treat_command() if self.pump_started and self.pump_stepper.at_goal(): - logger.success("The pump movement is over!") + loguru_logger.success("The pump movement is over!") self.actuator_client.client.publish( "status/pump", '{"status":"Done"}', @@ -340,12 +341,12 @@ def run(self): self.pump_stepper.release() time.sleep(0.01) - logger.info("Shutting down the stepper process") + loguru_logger.info("Shutting down the stepper process") self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') self.pump_stepper.shutdown() self.actuator_client.shutdown() - logger.success("Stepper process shut down! See you!") + loguru_logger.success("Stepper process shut down! See you!") # This is called if this script is launched directly From 2e281463ace9edd96147160d239dd6f5ec514ea8 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 19:54:18 +0100 Subject: [PATCH 19/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 83 +++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 516d28c1..c4a82d3e 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -202,22 +202,14 @@ def __init__(self, event): loguru_logger.info("Stepper initialisation is over") def __message_pump(self, last_message): - """ - Handle pump commands from received messages. - - Args: - last_message (dict): The last received message containing pump commands. - """ - logger.debug("We have received a pumping command") + loguru_logger.debug("We have received a pumping command") if last_message["action"] == "stop": loguru_logger.debug("We have received a stop pump command") self.pump_stepper.shutdown() - # Print status loguru_logger.info("The pump has been interrupted") - - # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') + if self.actuator_client: + self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') elif last_message["action"] == "move": loguru_logger.debug("We have received a move pump command") @@ -228,48 +220,44 @@ def __message_pump(self, last_message): or "flowrate" not in last_message ): loguru_logger.error(f"The received message has the wrong argument {last_message}") - self.actuator_client.client.publish( - "status/pump", - '{"status":"Error, the message is missing an argument"}', - ) + if self.actuator_client: + self.actuator_client.client.publish( + "status/pump", + '{"status":"Error, the message is missing an argument"}', + ) return - # Get direction from the different received arguments direction = last_message["direction"] - # Get delay (in between steps) from the different received arguments volume = float(last_message["volume"]) - # Get number of steps from the different received arguments - flowrate = float(last_message["flowrate"]) if (flowrate := float(last_message["flowrate"])) == 0: loguru_logger.error("The flowrate should not be == 0") - self.actuator_client.client.publish( - "status/pump", '{"status":"Error, The flowrate should not be == 0"}' - ) + if self.actuator_client: + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, The flowrate should not be == 0"}' + ) return - # Print status loguru_logger.info("The pump is started.") self.pump(direction, volume, flowrate) else: loguru_logger.warning(f"The received message was not understood {last_message}") - def treat_command(self): - """ - Treat the received command. - """ - command = "" - loguru_logger.info("We received a new message") - last_message = self.actuator_client.msg["payload"] # type: ignore - loguru_logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore - loguru_logger.debug(command) - self.actuator_client.read_message() - - if command == "pump": - self.__message_pump(last_message) - elif command != "": - loguru_logger.warning( - f"We did not understand the received request {command} - {last_message}" - ) +def treat_command(self): + loguru_logger.info("We received a new message") + if not self.actuator_client: + loguru_logger.error("Actuator client is not initialized") + return + + last_message = self.actuator_client.msg["payload"] # type: ignore + loguru_logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore + loguru_logger.debug(command) + self.actuator_client.read_message() + + if command == "pump": + self.__message_pump(last_message) + elif command != "": + loguru_logger.warning(f"We did not understand the received request {command} - {last_message}") + def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper @@ -317,19 +305,16 @@ def pump(self, direction, volume, speed=pump_max_speed): self.pump_stepper.go(BACKWARD, nb_steps) return - @logger.catch + @loguru_logger.catch def run(self): - """This is the function that needs to be started to create a thread""" loguru_logger.info(f"The stepper control process has been started in process {os.getpid()}") - # Creates the MQTT Client self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") - # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') loguru_logger.success("The pump is READY!") while not self.stop_event.is_set(): - if self.actuator_client.new_message_received(): + if self.actuator_client and self.actuator_client.new_message_received(): self.treat_command() if self.pump_started and self.pump_stepper.at_goal(): loguru_logger.success("The pump movement is over!") @@ -342,10 +327,12 @@ def run(self): time.sleep(0.01) loguru_logger.info("Shutting down the stepper process") - self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') + if self.actuator_client: + self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') self.pump_stepper.shutdown() - self.actuator_client.shutdown() + if self.actuator_client: + self.actuator_client.shutdown() loguru_logger.success("Stepper process shut down! See you!") From 2940a21f43257fda4862a9f3fe06d214345be0a3 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:16:07 +0100 Subject: [PATCH 20/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 49 +++++++++----------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index c4a82d3e..ff555b92 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -241,23 +241,24 @@ def __message_pump(self, last_message): else: loguru_logger.warning(f"The received message was not understood {last_message}") -def treat_command(self): - loguru_logger.info("We received a new message") - if not self.actuator_client: - loguru_logger.error("Actuator client is not initialized") - return - - last_message = self.actuator_client.msg["payload"] # type: ignore - loguru_logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore - loguru_logger.debug(command) - self.actuator_client.read_message() + def treat_command(self): + loguru_logger.info("We received a new message") + if not self.actuator_client: + loguru_logger.error("Actuator client is not initialized") + return - if command == "pump": - self.__message_pump(last_message) - elif command != "": - loguru_logger.warning(f"We did not understand the received request {command} - {last_message}") + last_message = self.actuator_client.msg["payload"] + loguru_logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + loguru_logger.debug(command) + self.actuator_client.read_message() + if command == "pump": + self.__message_pump(last_message) + elif command != "": + loguru_logger.warning( + f"We did not understand the received request {command} - {last_message}" + ) def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper @@ -270,31 +271,26 @@ def pump(self, direction, volume, speed=pump_max_speed): loguru_logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") - # Validation of inputs if direction not in ["FORWARD", "BACKWARD"]: loguru_logger.error("The direction command is not recognised") loguru_logger.error("It should be either FORWARD or BACKWARD") return - # TMC5160 is configured for 256 microsteps nb_steps = round(self.pump_steps_per_ml * volume * 256, 0) loguru_logger.debug(f"The number of microsteps that will be applied is {nb_steps}") if speed > self.pump_max_speed: speed = self.pump_max_speed - loguru_logger.warning( - f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" - ) + loguru_logger.warning(f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min") steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 loguru_logger.debug(f"There will be a speed of {steps_per_second} steps per second") self.pump_stepper.speed = int(steps_per_second) - # Publish the status "Started" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/pump", - f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', - ) + if self.actuator_client: + self.actuator_client.client.publish( + "status/pump", + f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', + ) - # Depending on direction, select the right direction for the pump if direction == "FORWARD": self.pump_started = True self.pump_stepper.go(FORWARD, nb_steps) @@ -305,6 +301,7 @@ def pump(self, direction, volume, speed=pump_max_speed): self.pump_stepper.go(BACKWARD, nb_steps) return + @loguru_logger.catch def run(self): loguru_logger.info(f"The stepper control process has been started in process {os.getpid()}") From ed2d0e86a59e875cb208cc0bed1b6fbb4f42631c Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:25:06 +0100 Subject: [PATCH 21/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 77 ++++++++++---------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index ff555b92..9de788ef 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -10,12 +10,12 @@ import time import typing -from loguru import logger as loguru_logger +import loguru import shush from planktoscope import mqtt -loguru_logger.info("planktoscope.stepper is loaded") +loguru.logger.info("planktoscope.stepper is loaded") FORWARD = 1 BACKWARD = 2 @@ -71,7 +71,7 @@ def go(self, direction, distance): elif self.__direction == BACKWARD: self.__goal = int(self.__stepper.get_position() - distance) else: - loguru_logger.error(f"The given direction is wrong {direction}") + loguru.logger.error(f"The given direction is wrong {direction}") self.__stepper.enable_motor() self.__stepper.go_to(self.__goal) @@ -104,7 +104,7 @@ def speed(self, speed): Args: speed (int): speed of the movement by the stepper, in microsteps unit/s """ - loguru_logger.debug(f"Setting stepper speed to {speed}") + loguru.logger.debug(f"Setting stepper speed to {speed}") self.__stepper.ramp_VMAX = int(speed) @property @@ -122,7 +122,7 @@ def acceleration(self, acceleration): Args: acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² """ - loguru_logger.debug(f"Setting stepper acceleration to {acceleration}") + loguru.logger.debug(f"Setting stepper acceleration to {acceleration}") self.__stepper.ramp_AMAX = int(acceleration) @property @@ -140,7 +140,7 @@ def deceleration(self, deceleration): Args: deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² """ - loguru_logger.debug(f"Setting stepper deceleration to {deceleration}") + loguru.logger.debug(f"Setting stepper deceleration to {deceleration}") self.__stepper.ramp_DMAX = int(deceleration) @@ -163,7 +163,7 @@ def __init__(self, event): event (multiprocessing.Event): Event to control the stopping of the process """ super().__init__() - loguru_logger.info("Initialising the stepper process") + loguru.logger.info("Initialising the stepper process") self.stop_event = event self.pump_started = False @@ -174,9 +174,9 @@ def __init__(self, event): with open("/home/pi/PlanktoScope/hardware.json", "r", encoding="utf-8") as config_file: # TODO #100 insert guard for config_file empty configuration = json.load(config_file) - loguru_logger.debug(f"Hardware configuration loaded is {configuration}") + loguru.logger.debug(f"Hardware configuration loaded is {configuration}") else: - loguru_logger.info("The hardware configuration file doesn't exists, using defaults") + loguru.logger.info("The hardware configuration file doesn't exists, using defaults") configuration = {} reverse = False @@ -199,27 +199,27 @@ def __init__(self, event): self.pump_stepper.deceleration = self.pump_stepper.acceleration self.pump_stepper.speed = self.pump_max_speed * self.pump_steps_per_ml * 256 / 60 - loguru_logger.info("Stepper initialisation is over") + loguru.logger.info("Stepper initialisation is over") def __message_pump(self, last_message): - loguru_logger.debug("We have received a pumping command") + loguru.logger.debug("We have received a pumping command") if last_message["action"] == "stop": - loguru_logger.debug("We have received a stop pump command") + loguru.logger.debug("We have received a stop pump command") self.pump_stepper.shutdown() - loguru_logger.info("The pump has been interrupted") + loguru.logger.info("The pump has been interrupted") if self.actuator_client: self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') elif last_message["action"] == "move": - loguru_logger.debug("We have received a move pump command") + loguru.logger.debug("We have received a move pump command") if ( "direction" not in last_message or "volume" not in last_message or "flowrate" not in last_message ): - loguru_logger.error(f"The received message has the wrong argument {last_message}") + loguru.logger.error(f"The received message has the wrong argument {last_message}") if self.actuator_client: self.actuator_client.client.publish( "status/pump", @@ -229,34 +229,34 @@ def __message_pump(self, last_message): direction = last_message["direction"] volume = float(last_message["volume"]) if (flowrate := float(last_message["flowrate"])) == 0: - loguru_logger.error("The flowrate should not be == 0") + loguru.logger.error("The flowrate should not be == 0") if self.actuator_client: self.actuator_client.client.publish( "status/pump", '{"status":"Error, The flowrate should not be == 0"}' ) return - loguru_logger.info("The pump is started.") + loguru.logger.info("The pump is started.") self.pump(direction, volume, flowrate) else: - loguru_logger.warning(f"The received message was not understood {last_message}") + loguru.logger.warning(f"The received message was not understood {last_message}") def treat_command(self): - loguru_logger.info("We received a new message") + loguru.logger.info("We received a new message") if not self.actuator_client: - loguru_logger.error("Actuator client is not initialized") + loguru.logger.error("Actuator client is not initialized") return - last_message = self.actuator_client.msg["payload"] - loguru_logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] - loguru_logger.debug(command) + last_message = self.actuator_client.msg["payload"] + loguru.logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + loguru.logger.debug(command) self.actuator_client.read_message() if command == "pump": self.__message_pump(last_message) elif command != "": - loguru_logger.warning( + loguru.logger.warning( f"We did not understand the received request {command} - {last_message}" ) @@ -269,20 +269,22 @@ def pump(self, direction, volume, speed=pump_max_speed): speed (int, optional): speed of pumping, in mL/min. Defaults to pump_max_speed. """ - loguru_logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") + loguru.logger.info(f"The pump will move {direction} for {volume}mL at {speed}mL/min") if direction not in ["FORWARD", "BACKWARD"]: - loguru_logger.error("The direction command is not recognised") - loguru_logger.error("It should be either FORWARD or BACKWARD") + loguru.logger.error("The direction command is not recognised") + loguru.logger.error("It should be either FORWARD or BACKWARD") return nb_steps = round(self.pump_steps_per_ml * volume * 256, 0) - loguru_logger.debug(f"The number of microsteps that will be applied is {nb_steps}") + loguru.logger.debug(f"The number of microsteps that will be applied is {nb_steps}") if speed > self.pump_max_speed: speed = self.pump_max_speed - loguru_logger.warning(f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min") + loguru.logger.warning( + f"Pump speed has been clamped to a maximum safe speed of {speed}mL/min" + ) steps_per_second = speed * self.pump_steps_per_ml * 256 / 60 - loguru_logger.debug(f"There will be a speed of {steps_per_second} steps per second") + loguru.logger.debug(f"There will be a speed of {steps_per_second} steps per second") self.pump_stepper.speed = int(steps_per_second) if self.actuator_client: @@ -301,20 +303,19 @@ def pump(self, direction, volume, speed=pump_max_speed): self.pump_stepper.go(BACKWARD, nb_steps) return - - @loguru_logger.catch + @loguru.logger.catch def run(self): - loguru_logger.info(f"The stepper control process has been started in process {os.getpid()}") + loguru.logger.info(f"The stepper control process has been started in process {os.getpid()}") self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") self.actuator_client.client.publish("status/pump", '{"status":"Ready"}') - loguru_logger.success("The pump is READY!") + loguru.logger.success("The pump is READY!") while not self.stop_event.is_set(): if self.actuator_client and self.actuator_client.new_message_received(): self.treat_command() if self.pump_started and self.pump_stepper.at_goal(): - loguru_logger.success("The pump movement is over!") + loguru.logger.success("The pump movement is over!") self.actuator_client.client.publish( "status/pump", '{"status":"Done"}', @@ -323,14 +324,14 @@ def run(self): self.pump_stepper.release() time.sleep(0.01) - loguru_logger.info("Shutting down the stepper process") + loguru.logger.info("Shutting down the stepper process") if self.actuator_client: self.actuator_client.client.publish("status/pump", '{"status":"Dead"}') self.pump_stepper.shutdown() if self.actuator_client: self.actuator_client.shutdown() - loguru_logger.success("Stepper process shut down! See you!") + loguru.logger.success("Stepper process shut down! See you!") # This is called if this script is launched directly From 8b248b96e15b105168d649947183befd16df2f49 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:34:41 +0100 Subject: [PATCH 22/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 9de788ef..330f0113 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -10,7 +10,7 @@ import time import typing -import loguru +import loguru import shush from planktoscope import mqtt @@ -202,6 +202,12 @@ def __init__(self, event): loguru.logger.info("Stepper initialisation is over") def __message_pump(self, last_message): + """ + Handle pump commands from received messages. + + Args: + last_message (dict): The last received message containing pump commands. + """ loguru.logger.debug("We have received a pumping command") if last_message["action"] == "stop": loguru.logger.debug("We have received a stop pump command") @@ -226,21 +232,23 @@ def __message_pump(self, last_message): '{"status":"Error, the message is missing an argument"}', ) return - direction = last_message["direction"] - volume = float(last_message["volume"]) - if (flowrate := float(last_message["flowrate"])) == 0: - loguru.logger.error("The flowrate should not be == 0") - if self.actuator_client: - self.actuator_client.client.publish( - "status/pump", '{"status":"Error, The flowrate should not be == 0"}' - ) - return - - loguru.logger.info("The pump is started.") - self.pump(direction, volume, flowrate) + else: + direction = last_message["direction"] + volume = float(last_message["volume"]) + if (flowrate := float(last_message["flowrate"])) == 0: + loguru.logger.error("The flowrate should not be == 0") + if self.actuator_client: + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, The flowrate should not be == 0"}' + ) + return + + loguru.logger.info("The pump is started.") + self.pump(direction, volume, flowrate) else: loguru.logger.warning(f"The received message was not understood {last_message}") + def treat_command(self): loguru.logger.info("We received a new message") if not self.actuator_client: From 0adabc4580376789f91dd9acce7dd6ac908e69f0 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:39:41 +0100 Subject: [PATCH 23/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 330f0113..7713442b 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -232,24 +232,28 @@ def __message_pump(self, last_message): '{"status":"Error, the message is missing an argument"}', ) return - else: - direction = last_message["direction"] - volume = float(last_message["volume"]) - if (flowrate := float(last_message["flowrate"])) == 0: - loguru.logger.error("The flowrate should not be == 0") - if self.actuator_client: - self.actuator_client.client.publish( - "status/pump", '{"status":"Error, The flowrate should not be == 0"}' - ) - return - - loguru.logger.info("The pump is started.") - self.pump(direction, volume, flowrate) + + direction = last_message["direction"] + volume = float(last_message["volume"]) + if (flowrate := float(last_message["flowrate"])) == 0: + loguru.logger.error("The flowrate should not be == 0") + if self.actuator_client: + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, The flowrate should not be == 0"}' + ) + return + + loguru.logger.info("The pump is started.") + self.pump(direction, volume, flowrate) + else: loguru.logger.warning(f"The received message was not understood {last_message}") def treat_command(self): + """ + Treat the received command. + """ loguru.logger.info("We received a new message") if not self.actuator_client: loguru.logger.error("Actuator client is not initialized") From 2be5aebbf8ffead7addd426aa7a258daffcc4e5c Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:46:48 +0100 Subject: [PATCH 24/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 113 ++++++++++--------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 7713442b..90428c4d 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -203,74 +203,79 @@ def __init__(self, event): def __message_pump(self, last_message): """ - Handle pump commands from received messages. + Handle the pump message received from the actuator client. Args: - last_message (dict): The last received message containing pump commands. + last_message (dict): The last message received. """ loguru.logger.debug("We have received a pumping command") - if last_message["action"] == "stop": - loguru.logger.debug("We have received a stop pump command") - self.pump_stepper.shutdown() - - loguru.logger.info("The pump has been interrupted") - if self.actuator_client: - self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') - - elif last_message["action"] == "move": - loguru.logger.debug("We have received a move pump command") - - if ( - "direction" not in last_message - or "volume" not in last_message - or "flowrate" not in last_message - ): - loguru.logger.error(f"The received message has the wrong argument {last_message}") - if self.actuator_client: - self.actuator_client.client.publish( - "status/pump", - '{"status":"Error, the message is missing an argument"}', - ) - return - - direction = last_message["direction"] - volume = float(last_message["volume"]) - if (flowrate := float(last_message["flowrate"])) == 0: - loguru.logger.error("The flowrate should not be == 0") - if self.actuator_client: - self.actuator_client.client.publish( - "status/pump", '{"status":"Error, The flowrate should not be == 0"}' - ) - return - - loguru.logger.info("The pump is started.") - self.pump(direction, volume, flowrate) + action = last_message.get("action") + if action == "stop": + self._handle_stop_action() + elif action == "move": + self._handle_move_action(last_message) else: loguru.logger.warning(f"The received message was not understood {last_message}") + def _handle_stop_action(self): + """ + Handle the 'stop' action for the pump. + """ + loguru.logger.debug("We have received a stop pump command") + self.pump_stepper.shutdown() + loguru.logger.info("The pump has been interrupted") + if self.actuator_client: + self.actuator_client.client.publish("status/pump", '{"status":"Interrupted"}') - def treat_command(self): + def _handle_move_action(self, last_message): """ - Treat the received command. + Handle the 'move' action for the pump. + + Args: + last_message (dict): The last message received. """ - loguru.logger.info("We received a new message") - if not self.actuator_client: - loguru.logger.error("Actuator client is not initialized") + loguru.logger.debug("We have received a move pump command") + if "direction" not in last_message or "volume" not in last_message or "flowrate" not in last_message: + loguru.logger.error(f"The received message has the wrong argument {last_message}") + if self.actuator_client: + self.actuator_client.client.publish("status/pump", '{"status":"Error, the message is missing an argument"}') return - last_message = self.actuator_client.msg["payload"] - loguru.logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] - loguru.logger.debug(command) - self.actuator_client.read_message() + direction = last_message["direction"] + volume = float(last_message["volume"]) + flowrate = float(last_message["flowrate"]) - if command == "pump": - self.__message_pump(last_message) - elif command != "": - loguru.logger.warning( - f"We did not understand the received request {command} - {last_message}" - ) + if flowrate == 0: + loguru.logger.error("The flowrate should not be == 0") + if self.actuator_client: + self.actuator_client.client.publish("status/pump", '{"status":"Error, The flowrate should not be == 0"}') + return + + loguru.logger.info("The pump is started.") + self.pump(direction, volume, flowrate) + + + def treat_command(self): + """ + Treat the received command. + """ + loguru.logger.info("We received a new message") + if not self.actuator_client: + loguru.logger.error("Actuator client is not initialized") + return + + last_message = self.actuator_client.msg["payload"] + loguru.logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + loguru.logger.debug(command) + self.actuator_client.read_message() + + if command == "pump": + self.__message_pump(last_message) + elif command != "": + loguru.logger.warning( + f"We did not understand the received request {command} - {last_message}") def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper From 117177e38799ede085bb87594ef393291f8528ce Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:49:20 +0100 Subject: [PATCH 25/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 90428c4d..c7adf1a3 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -256,26 +256,26 @@ def _handle_move_action(self, last_message): self.pump(direction, volume, flowrate) - def treat_command(self): - """ - Treat the received command. - """ - loguru.logger.info("We received a new message") - if not self.actuator_client: - loguru.logger.error("Actuator client is not initialized") - return - - last_message = self.actuator_client.msg["payload"] - loguru.logger.debug(last_message) - command = self.actuator_client.msg["topic"].split("/", 1)[1] - loguru.logger.debug(command) - self.actuator_client.read_message() - - if command == "pump": - self.__message_pump(last_message) - elif command != "": - loguru.logger.warning( - f"We did not understand the received request {command} - {last_message}") + def treat_command(self): + """ + Treat the received command. + """ + loguru.logger.info("We received a new message") + if not self.actuator_client: + loguru.logger.error("Actuator client is not initialized") + return + + last_message = self.actuator_client.msg["payload"] + loguru.logger.debug(last_message) + command = self.actuator_client.msg["topic"].split("/", 1)[1] + loguru.logger.debug(command) + self.actuator_client.read_message() + + if command == "pump": + self.__message_pump(last_message) + elif command != "": + loguru.logger.warning( + f"We did not understand the received request {command} - {last_message}") def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper From fdae3c2e106ed52a2692a46111a35312949d1862 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 20:52:44 +0100 Subject: [PATCH 26/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index c7adf1a3..632bce81 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -236,26 +236,33 @@ def _handle_move_action(self, last_message): last_message (dict): The last message received. """ loguru.logger.debug("We have received a move pump command") - if "direction" not in last_message or "volume" not in last_message or "flowrate" not in last_message: + if ( + "direction" not in last_message + or "volume" not in last_message + or "flowrate" not in last_message + ): loguru.logger.error(f"The received message has the wrong argument {last_message}") if self.actuator_client: - self.actuator_client.client.publish("status/pump", '{"status":"Error, the message is missing an argument"}') + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, the message is missing an argument"}' + ) return direction = last_message["direction"] volume = float(last_message["volume"]) flowrate = float(last_message["flowrate"]) - if flowrate == 0: + if (flowrate := float(last_message["flowrate"])) == 0 : loguru.logger.error("The flowrate should not be == 0") if self.actuator_client: - self.actuator_client.client.publish("status/pump", '{"status":"Error, The flowrate should not be == 0"}') + self.actuator_client.client.publish( + "status/pump", '{"status":"Error, The flowrate should not be == 0"}' + ) return loguru.logger.info("The pump is started.") self.pump(direction, volume, flowrate) - def treat_command(self): """ Treat the received command. @@ -275,7 +282,8 @@ def treat_command(self): self.__message_pump(last_message) elif command != "": loguru.logger.warning( - f"We did not understand the received request {command} - {last_message}") + f"We did not understand the received request {command} - {last_message}" + ) def pump(self, direction, volume, speed=pump_max_speed): """Moves the pump stepper From 8a2abe03eec3062d562f686bdd12af3bffdf2591 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 21:17:32 +0100 Subject: [PATCH 27/35] changes made to focus.py --- control/planktoscopehat/planktoscope/focus.py | 72 +++++++++---------- control/planktoscopehat/planktoscope/pump.py | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 461010de..c6406937 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -10,12 +10,12 @@ import time import typing -from loguru import logger +import loguru import shush from planktoscope import mqtt -logger.info("planktoscope.stepper is loaded") +loguru.logger.info("planktoscope.stepper is loaded") FORWARD = 1 BACKWARD = 2 @@ -38,7 +38,6 @@ def __init__(self, stepper, size): """ self.__stepper = shush.Motor(stepper) self.__stepper.disable_motor() - self.__size = size self.__goal = 0 self.__direction: typing.Optional[int] = None @@ -72,7 +71,7 @@ def go(self, direction, distance): elif self.__direction == BACKWARD: self.__goal = int(self.__stepper.get_position() - distance) else: - logger.error(f"The given direction is wrong {direction}") + loguru.logger.error(f"The given direction is wrong {direction}") self.__stepper.enable_motor() self.__stepper.go_to(self.__goal) @@ -105,7 +104,7 @@ def speed(self, speed): Args: speed (int): speed of the movement by the stepper, in microsteps unit/s """ - logger.debug(f"Setting stepper speed to {speed}") + loguru.logger.debug(f"Setting stepper speed to {speed}") self.__stepper.ramp_VMAX = int(speed) @property @@ -123,7 +122,7 @@ def acceleration(self, acceleration): Args: acceleration (int): acceleration reachable by the stepper, in microsteps unit/s² """ - logger.debug(f"Setting stepper acceleration to {acceleration}") + loguru.logger.debug(f"Setting stepper acceleration to {acceleration}") self.__stepper.ramp_AMAX = int(acceleration) @property @@ -141,7 +140,7 @@ def deceleration(self, deceleration): Args: deceleration (int): deceleration reachable by the stepper, in microsteps unit/s² """ - logger.debug(f"Setting stepper deceleration to {deceleration}") + loguru.logger.debug(f"Setting stepper deceleration to {deceleration}") self.__stepper.ramp_DMAX = int(deceleration) @@ -164,20 +163,21 @@ def __init__(self, event): Args: event (multiprocessing.Event): Event to signal the process to stop. """ - super(FocusProcess, self).__init__() - logger.info("Initialising the stepper process") + super().__init__() + loguru.logger.info("Initialising the stepper process") self.stop_event = event self.focus_started = False + self.actuator_client = None # Initialize actuator_client to None 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: # TODO #100 insert guard for config_file empty configuration = json.load(config_file) - logger.debug(f"Hardware configuration loaded is {configuration}") + loguru.logger.debug(f"Hardware configuration loaded is {configuration}") else: - logger.info("The hardware configuration file doesn't exists, using defaults") + loguru.logger.info("The hardware configuration file doesn't exists, using defaults") configuration = {} reverse = False @@ -201,7 +201,7 @@ def __init__(self, event): self.focus_stepper.deceleration = self.focus_stepper.acceleration self.focus_stepper.speed = self.focus_max_speed * self.focus_steps_per_mm * 256 - logger.info("the focus stepper initialisation is over") + loguru.logger.info("the focus stepper initialisation is over") def __message_focus(self, last_message): """ @@ -210,23 +210,23 @@ def __message_focus(self, last_message): Args: last_message (dict): The last received message. """ - logger.debug("We have received a focusing request") + loguru.logger.debug("We have received a focusing request") # If a new received command is "focus" but args contains "stop" we stop! if last_message["action"] == "stop": - logger.debug("We have received a stop focus command") + loguru.logger.debug("We have received a stop focus command") self.focus_stepper.shutdown() # Print status - logger.info("The focus has been interrupted") + loguru.logger.info("The focus has been interrupted") # Publish the status "Interrupted" to via MQTT to Node-RED self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') elif last_message["action"] == "move": - logger.debug("We have received a move focus command") + loguru.logger.debug("We have received a move focus command") if "direction" not in last_message or "distance" not in last_message: - logger.error(f"The received message has the wrong argument {last_message}") + loguru.logger.error(f"The received message has the wrong argument {last_message}") self.actuator_client.client.publish("status/focus", '{"status":"Error"}') # Get direction from the different received arguments direction = last_message["direction"] @@ -236,30 +236,30 @@ def __message_focus(self, last_message): speed = float(last_message["speed"]) if "speed" in last_message else 0 # Print status - logger.info("The focus movement is started.") + loguru.logger.info("The focus movement is started.") if speed: self.focus(direction, distance, speed) else: self.focus(direction, distance) else: - logger.warning(f"The received message was not understood {last_message}") + loguru.logger.warning(f"The received message was not understood {last_message}") def treat_command(self): """ Process a received command. """ command = "" - logger.info("We received a new message") + loguru.logger.info("We received a new message") last_message = self.actuator_client.msg["payload"] # type: ignore - logger.debug(last_message) + loguru.logger.debug(last_message) command = self.actuator_client.msg["topic"].split("/", 1)[1] # type: ignore - logger.debug(command) + loguru.logger.debug(command) self.actuator_client.read_message() if command == "focus": self.__message_focus(last_message) elif command != "": - logger.warning(f"We did not understand the received request {command} - {last_message}") + loguru.logger.warning(f"We did not understand the received request {command} - {last_message}") def focus(self, direction, distance, speed=focus_max_speed): """Moves the focus stepper @@ -274,28 +274,28 @@ def focus(self, direction, distance, speed=focus_max_speed): speed (int, optional): max speed of the stage, in mm/sec. Defaults to focus_max_speed. """ - logger.info(f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec") + loguru.logger.info(f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec") # Validation of inputs if direction not in ["UP", "DOWN"]: - logger.error("The direction command is not recognised") - logger.error("It should be either UP or DOWN") + loguru.logger.error("The direction command is not recognised") + loguru.logger.error("It should be either UP or DOWN") return if distance > 45: - logger.error("You are trying to move more than the stage physical size") + loguru.logger.error("You are trying to move more than the stage physical size") return # We are going to use 256 microsteps, so we need to multiply by 256 the steps number nb_steps = round(self.focus_steps_per_mm * distance * 256, 0) - logger.debug(f"The number of microsteps that will be applied is {nb_steps}") + loguru.logger.debug(f"The number of microsteps that will be applied is {nb_steps}") if speed > self.focus_max_speed: speed = self.focus_max_speed - logger.warning( + loguru.logger.warning( f"Focus stage speed has been clamped to a maximum safe speed of {speed} mm/sec" ) steps_per_second = speed * self.focus_steps_per_mm * 256 - logger.debug(f"There will be a speed of {steps_per_second} steps per second") + loguru.logger.debug(f"There will be a speed of {steps_per_second} steps per second") self.focus_stepper.speed = int(steps_per_second) # Publish the status "Started" to via MQTT to Node-RED @@ -321,10 +321,10 @@ def focus(self, direction, distance, speed=focus_max_speed): # Stepper is 200 steps/round, or 393steps/ml # https://www.wolframalpha.com/input/?i=pi+*+%280.8mm%29%C2%B2+*+54mm+*+3 - @logger.catch + @loguru.logger.catch def run(self): """This is the function that needs to be started to create a thread""" - logger.info(f"The stepper control process has been started in process {os.getpid()}") + loguru.logger.info(f"The stepper control process has been started in process {os.getpid()}") # Creates the MQTT Client # We have to create it here, otherwise when the process running run is started @@ -335,12 +335,12 @@ def run(self): # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') - logger.success("Stepper is READY!") + loguru.logger.success("Stepper is READY!") while not self.stop_event.is_set(): if self.actuator_client.new_message_received(): self.treat_command() if self.focus_started and self.focus_stepper.at_goal(): - logger.success("The focus movement is over!") + loguru.logger.success("The focus movement is over!") self.actuator_client.client.publish( "status/focus", '{"status":"Done"}', @@ -348,11 +348,11 @@ def run(self): self.focus_started = False self.focus_stepper.release() time.sleep(0.01) - logger.info("Shutting down the stepper process") + loguru.logger.info("Shutting down the stepper process") self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') self.focus_stepper.shutdown() self.actuator_client.shutdown() - logger.success("Stepper process shut down! See you!") + loguru.logger.success("Stepper process shut down! See you!") # This is called if this script is launched directly diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 632bce81..78401a29 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -252,7 +252,7 @@ def _handle_move_action(self, last_message): volume = float(last_message["volume"]) flowrate = float(last_message["flowrate"]) - if (flowrate := float(last_message["flowrate"])) == 0 : + if (flowrate := float(last_message["flowrate"])) == 0: loguru.logger.error("The flowrate should not be == 0") if self.actuator_client: self.actuator_client.client.publish( From 193dd8b4b349d52b0f196121a5bbbc0b577dddac Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 21:29:41 +0100 Subject: [PATCH 28/35] changes made to focus.py --- control/planktoscopehat/planktoscope/focus.py | 15 +++++++-------- control/planktoscopehat/planktoscope/pump.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index c6406937..60d9b646 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -323,16 +323,10 @@ def focus(self, direction, distance, speed=focus_max_speed): @loguru.logger.catch def run(self): - """This is the function that needs to be started to create a thread""" loguru.logger.info(f"The stepper control process has been started in process {os.getpid()}") # Creates the MQTT Client - # We have to create it here, otherwise when the process running run is started - # it doesn't see changes and calls made by self.actuator_client because this one - # only exist in the master process. See - # https://stackoverflow.com/questions/17172878/using-pythons-multiprocessing-process-class self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") - # Publish the status "Ready" to via MQTT to Node-RED self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') loguru.logger.success("Stepper is READY!") @@ -348,13 +342,18 @@ def run(self): self.focus_started = False self.focus_stepper.release() time.sleep(0.01) + loguru.logger.info("Shutting down the stepper process") - self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Dead"}') self.focus_stepper.shutdown() - self.actuator_client.shutdown() + + if self.actuator_client: + self.actuator_client.shutdown() loguru.logger.success("Stepper process shut down! See you!") + # This is called if this script is launched directly if __name__ == "__main__": # TODO This should be a test suite for this library diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 78401a29..632bce81 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -252,7 +252,7 @@ def _handle_move_action(self, last_message): volume = float(last_message["volume"]) flowrate = float(last_message["flowrate"]) - if (flowrate := float(last_message["flowrate"])) == 0: + if (flowrate := float(last_message["flowrate"])) == 0 : loguru.logger.error("The flowrate should not be == 0") if self.actuator_client: self.actuator_client.client.publish( From 3dbb994aa05d5082b16d086440bf755fe6133800 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Mon, 10 Jun 2024 21:32:33 +0100 Subject: [PATCH 29/35] changes made to pump.py --- control/planktoscopehat/planktoscope/pump.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/planktoscopehat/planktoscope/pump.py b/control/planktoscopehat/planktoscope/pump.py index 632bce81..78401a29 100644 --- a/control/planktoscopehat/planktoscope/pump.py +++ b/control/planktoscopehat/planktoscope/pump.py @@ -252,7 +252,7 @@ def _handle_move_action(self, last_message): volume = float(last_message["volume"]) flowrate = float(last_message["flowrate"]) - if (flowrate := float(last_message["flowrate"])) == 0 : + if (flowrate := float(last_message["flowrate"])) == 0: loguru.logger.error("The flowrate should not be == 0") if self.actuator_client: self.actuator_client.client.publish( From cab87d24375491f6e0add199e83101bd415ef7b5 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 20:52:38 +0100 Subject: [PATCH 30/35] exception handling to resolve the none value error --- control/planktoscopehat/planktoscope/focus.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 60d9b646..8b659dff 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -168,7 +168,7 @@ def __init__(self, event): self.stop_event = event self.focus_started = False - self.actuator_client = None # Initialize actuator_client to None + self.actuator_client: typing.Optional[mqtt.MQTT_Client] = None # Initialize actuator_client to None if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json @@ -220,14 +220,16 @@ def __message_focus(self, last_message): loguru.logger.info("The focus has been interrupted") # Publish the status "Interrupted" to via MQTT to Node-RED - self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') elif last_message["action"] == "move": loguru.logger.debug("We have received a move focus command") if "direction" not in last_message or "distance" not in last_message: loguru.logger.error(f"The received message has the wrong argument {last_message}") - self.actuator_client.client.publish("status/focus", '{"status":"Error"}') + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Error"}') # Get direction from the different received arguments direction = last_message["direction"] # Get number of steps from the different received arguments @@ -249,6 +251,10 @@ def treat_command(self): Process a received command. """ command = "" + if not self.actuator_client: + loguru.logger.error("Actuator client is not initialized") + return + loguru.logger.info("We received a new message") last_message = self.actuator_client.msg["payload"] # type: ignore loguru.logger.debug(last_message) @@ -259,7 +265,9 @@ def treat_command(self): if command == "focus": self.__message_focus(last_message) elif command != "": - loguru.logger.warning(f"We did not understand the received request {command} - {last_message}") + loguru.logger.warning( + f"We did not understand the received request {command} - {last_message}" + ) def focus(self, direction, distance, speed=focus_max_speed): """Moves the focus stepper @@ -274,7 +282,9 @@ def focus(self, direction, distance, speed=focus_max_speed): speed (int, optional): max speed of the stage, in mm/sec. Defaults to focus_max_speed. """ - loguru.logger.info(f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec") + loguru.logger.info( + f"The focus stage will move {direction} for {distance}mm at {speed}mm/sec" + ) # Validation of inputs if direction not in ["UP", "DOWN"]: @@ -327,7 +337,8 @@ def run(self): # Creates the MQTT Client self.actuator_client = mqtt.MQTT_Client(topic="actuator/#", name="actuator_client") - self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Ready"}') loguru.logger.success("Stepper is READY!") while not self.stop_event.is_set(): @@ -353,7 +364,6 @@ def run(self): loguru.logger.success("Stepper process shut down! See you!") - # This is called if this script is launched directly if __name__ == "__main__": # TODO This should be a test suite for this library From 76fff2d54ae37970d856fa8230843f9b9ac035a4 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 20:55:06 +0100 Subject: [PATCH 31/35] exception handling to resolve the none value error --- control/planktoscopehat/planktoscope/focus.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 8b659dff..4f04a280 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -168,7 +168,9 @@ def __init__(self, event): self.stop_event = event self.focus_started = False - self.actuator_client: typing.Optional[mqtt.MQTT_Client] = None # Initialize actuator_client to None + self.actuator_client: typing.Optional[mqtt.MQTT_Client] = ( + None # Initialize actuator_client to None + ) if os.path.exists("/home/pi/PlanktoScope/hardware.json"): # load hardware.json @@ -309,10 +311,11 @@ def focus(self, direction, distance, speed=focus_max_speed): self.focus_stepper.speed = int(steps_per_second) # Publish the status "Started" to via MQTT to Node-RED - self.actuator_client.client.publish( - "status/focus", - f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', - ) + if self.actuator_client: + self.actuator_client.client.publish( + "status/focus", + f'{{"status":"Started", "duration":{nb_steps / steps_per_second}}}', + ) # Depending on direction, select the right direction for the focus if direction == "UP": From dd69f7d7f64feb82153e94453caeaf6044e9e5b5 Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 21:02:10 +0100 Subject: [PATCH 32/35] exception handling to resolve the none value error --- control/planktoscopehat/planktoscope/focus.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 4f04a280..93b984b1 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -28,7 +28,7 @@ class Stepper: This class controls the stepper motor used for adjusting the focus. """ - def __init__(self, stepper, size): + def __init__(self, stepper, size): # pylint: disable=unused-argument """Initialize the stepper class Args: @@ -227,26 +227,28 @@ def __message_focus(self, last_message): elif last_message["action"] == "move": loguru.logger.debug("We have received a move focus command") + self.__handle_move_action(last_message) - if "direction" not in last_message or "distance" not in last_message: - loguru.logger.error(f"The received message has the wrong argument {last_message}") - if self.actuator_client: - self.actuator_client.client.publish("status/focus", '{"status":"Error"}') - # Get direction from the different received arguments - direction = last_message["direction"] - # Get number of steps from the different received arguments - distance = float(last_message["distance"]) + else: + loguru.logger.warning(f"The received message was not understood {last_message}") + + def __handle_move_action(self, last_message): + if "direction" not in last_message or "distance" not in last_message: + loguru.logger.error(f"The received message has the wrong argument {last_message}") + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Error"}') + return - speed = float(last_message["speed"]) if "speed" in last_message else 0 + direction = last_message["direction"] + distance = float(last_message["distance"]) + speed = float(last_message["speed"]) if "speed" in last_message else 0 - # Print status - loguru.logger.info("The focus movement is started.") - if speed: - self.focus(direction, distance, speed) - else: - self.focus(direction, distance) + loguru.logger.info("The focus movement is started.") + if speed: + self.focus(direction, distance, speed) else: - loguru.logger.warning(f"The received message was not understood {last_message}") + self.focus(direction, distance) + def treat_command(self): """ From d86c74fc8eaa8acc19ac3f25308d9c5643d9249e Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 21:07:06 +0100 Subject: [PATCH 33/35] correcting the structure of the elif --- control/planktoscopehat/planktoscope/focus.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 93b984b1..c275bf53 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -28,8 +28,9 @@ class Stepper: This class controls the stepper motor used for adjusting the focus. """ - def __init__(self, stepper, size): # pylint: disable=unused-argument - """Initialize the stepper class + def __init__(self, stepper, size): # pylint: disable=unused-argument + """ + Initialize the stepper class Args: stepper (either STEPPER1 or STEPPER2): reference to the object that controls the stepper @@ -249,7 +250,6 @@ def __handle_move_action(self, last_message): else: self.focus(direction, distance) - def treat_command(self): """ Process a received command. From f1605ca8b32f8c322088b58510a43193df65fc8d Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 21:12:11 +0100 Subject: [PATCH 34/35] correcting the structure of the elif --- control/planktoscopehat/planktoscope/focus.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index c275bf53..9102d9a5 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -214,26 +214,26 @@ def __message_focus(self, last_message): last_message (dict): The last received message. """ loguru.logger.debug("We have received a focusing request") - # If a new received command is "focus" but args contains "stop" we stop! - if last_message["action"] == "stop": - loguru.logger.debug("We have received a stop focus command") - self.focus_stepper.shutdown() - - # Print status - loguru.logger.info("The focus has been interrupted") - - # Publish the status "Interrupted" to via MQTT to Node-RED - if self.actuator_client: - self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') - - elif last_message["action"] == "move": - loguru.logger.debug("We have received a move focus command") + + action = last_message.get("action") + if action == "stop": + self.__handle_stop_action() + elif action == "move": self.__handle_move_action(last_message) - else: loguru.logger.warning(f"The received message was not understood {last_message}") + def __handle_stop_action(self): + loguru.logger.debug("We have received a stop focus command") + self.focus_stepper.shutdown() + + loguru.logger.info("The focus has been interrupted") + if self.actuator_client: + self.actuator_client.client.publish("status/focus", '{"status":"Interrupted"}') + def __handle_move_action(self, last_message): + loguru.logger.debug("We have received a move focus command") + if "direction" not in last_message or "distance" not in last_message: loguru.logger.error(f"The received message has the wrong argument {last_message}") if self.actuator_client: From 29c4d8d6e5f498fb7bb85c41e455617b7753cabd Mon Sep 17 00:00:00 2001 From: oumayma-hy Date: Wed, 12 Jun 2024 21:14:52 +0100 Subject: [PATCH 35/35] removing blank lines --- control/planktoscopehat/planktoscope/focus.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/control/planktoscopehat/planktoscope/focus.py b/control/planktoscopehat/planktoscope/focus.py index 9102d9a5..d3871030 100644 --- a/control/planktoscopehat/planktoscope/focus.py +++ b/control/planktoscopehat/planktoscope/focus.py @@ -214,7 +214,6 @@ def __message_focus(self, last_message): last_message (dict): The last received message. """ loguru.logger.debug("We have received a focusing request") - action = last_message.get("action") if action == "stop": self.__handle_stop_action() @@ -233,13 +232,11 @@ def __handle_stop_action(self): def __handle_move_action(self, last_message): loguru.logger.debug("We have received a move focus command") - if "direction" not in last_message or "distance" not in last_message: loguru.logger.error(f"The received message has the wrong argument {last_message}") if self.actuator_client: self.actuator_client.client.publish("status/focus", '{"status":"Error"}') return - direction = last_message["direction"] distance = float(last_message["distance"]) speed = float(last_message["speed"]) if "speed" in last_message else 0