Module pipettin-piper.piper.plugins.plugin

Functions

def load_plugin(controller: "'Controller'", **kwargs)
Expand source code
def load_plugin(controller: "Controller", **kwargs):
    """
    Plugins are expected to have a function named 'load_plugin' which will instantiate
    the plugin's class and returning it to the main Commander class.
    If they fail to load, they must raise a PluginError exception.
    """
    logging.debug(f"load_plugin: loading {plugin_name} plugin.")
    try:
        class_instance = Plugin(controller=controller, **kwargs)
    except Exception as e:
        msg = f"Failed to load with error: {e}"
        logging.error(msg)
        raise PluginError(msg) from e

    return class_instance

Plugins are expected to have a function named 'load_plugin' which will instantiate the plugin's class and returning it to the main Commander class. If they fail to load, they must raise a PluginError exception.

Classes

class Plugin (controller: Controller)
Expand source code
class Plugin:
    """Base class for plugins."""

    def __init__(self, controller: Controller):

        # Log messages.
        logging.info("Loading plugin %s", plugin_name)

        # Stop here if a plugin has not implemented its own __init__.
        raise PluginError("Plugins must override the init method of the base class")

        # Save the Controller and GCODE builder objects.
        self.controller = controller
        self.gcode_builder: GcodeBuilder = controller.builder

        # Get the gcode generator.
        self.gcode: GcodePrimitives = controller.builder.gcode

        # Add your handlers for new protocol actions.
        # The builder handler is used by 'builder.parseAction' to generate GCODE.
        self.controller.builder.add_action_handler(name=self.action_name,
                                                   function=self.action_handler)
        # The controller handler is used by 'controller.run_actions_protocol',
        # to execute the action.
        self.controller.add_action_runner(name=self.action_name,
                                          function=self.action_runner)

        # Add socket.io handlers.
        self.register_event_handlers()

        # Example: add a coroutine for the controller to launch at start.
        self.controller.coroutine_methods.extend([
            self.example_coroutine()
        ])

        # Set status.
        self._status = True

    @staticmethod
    def make_cmd_id(data: str = ""):
        """Generate an MD5 hash from the provided string.
        The current time is appended to the string, to make hashes more unique.
        """
        # Should be unique by using the current time
        uid = str(data) + str(time.time())
        uid_md5 = md5(uid.encode())
        cmd_id = uid_md5.hexdigest()
        return cmd_id

    action_name = "MAGIC"
    def action_handler(self,  action: dict, i: int):
        """This method must generate GCODE for the provided action.

        Args:
            action (dict): Raw action data.
            i (int): Action index.
        """
        # GCODE must be saved in a list.
        commands = []

        # Append a comment to the GCODE command list.
        commands.append(
            self.gcode.comment("Use the GcodePrimitives class (or whatever) to generate GCODE for this action.")
        )

        # Commit the command to the GcodeBuilder class.
        self.gcode_builder.extend_commands(commands, action)

        # The return values are not used. You're free.

    def action_runner(self,  action: dict, i: int, wait:bool, check:bool, timeout:float):
        """This method must execute provided action.

        Args:
            action (dict): Processed action data.
            i (int): Action index.
            wait (bool): Check that the action "finishes" before returning.
            check (bool):  Check that the action completed "successfully" before returning.
            timeout (float): Timeout for the "wait" and "check" checks.
        """
        # Do your thing here. The return values are not used.


    async def example_coroutine(self):
        """Minimal coroutine object, started by the controller."""
        try:
            while True:
                asyncio.sleep(1)
        except asyncio.CancelledError:
            pass

    def register_event_handlers(self):
        """ Add event handlers to the socket.io object from the controller class.
        The events will typically originate from the Pipettin Writer GUI's backend.
        """
        if self.controller.comms.sio:
            # Register an event handler. This uses the socket.io object from the controller class.
            @self.controller.comms.sio.on('example_event')
            async def example_event_handler(data: dict):
                # Log the event.
                logging.info("Received 'example_event' command: %s", pformat(data))

                # Do something with the data...
                cmd_id = data.get("id", None)  # Get the ID property, returning "-1" if not found.

                # Make a unique ID for the command
                if cmd_id is None:
                    cmd_id = self.make_cmd_id('example_event_handler')

                # Respond to the event.
                return {"id": cmd_id, "data": "example response data"}

    _status = None
    @property
    def status(self):
        """This method reports the status of the plugin.
        This helps the rest of the program know what's up over here.
        Must be an atrribute or a property."""
        return self._status

    @status.setter
    def status(self, value):
        self._status = True if value else False

Base class for plugins.

Subclasses

Class variables

var action_name

Static methods

def make_cmd_id(data: str = '')
Expand source code
@staticmethod
def make_cmd_id(data: str = ""):
    """Generate an MD5 hash from the provided string.
    The current time is appended to the string, to make hashes more unique.
    """
    # Should be unique by using the current time
    uid = str(data) + str(time.time())
    uid_md5 = md5(uid.encode())
    cmd_id = uid_md5.hexdigest()
    return cmd_id

Generate an MD5 hash from the provided string. The current time is appended to the string, to make hashes more unique.

Instance variables

prop status
Expand source code
@property
def status(self):
    """This method reports the status of the plugin.
    This helps the rest of the program know what's up over here.
    Must be an atrribute or a property."""
    return self._status

This method reports the status of the plugin. This helps the rest of the program know what's up over here. Must be an atrribute or a property.

Methods

def action_handler(self, action: dict, i: int)
Expand source code
def action_handler(self,  action: dict, i: int):
    """This method must generate GCODE for the provided action.

    Args:
        action (dict): Raw action data.
        i (int): Action index.
    """
    # GCODE must be saved in a list.
    commands = []

    # Append a comment to the GCODE command list.
    commands.append(
        self.gcode.comment("Use the GcodePrimitives class (or whatever) to generate GCODE for this action.")
    )

    # Commit the command to the GcodeBuilder class.
    self.gcode_builder.extend_commands(commands, action)

    # The return values are not used. You're free.

This method must generate GCODE for the provided action.

Args

action : dict
Raw action data.
i : int
Action index.
def action_runner(self, action: dict, i: int, wait: bool, check: bool, timeout: float)
Expand source code
def action_runner(self,  action: dict, i: int, wait:bool, check:bool, timeout:float):
    """This method must execute provided action.

    Args:
        action (dict): Processed action data.
        i (int): Action index.
        wait (bool): Check that the action "finishes" before returning.
        check (bool):  Check that the action completed "successfully" before returning.
        timeout (float): Timeout for the "wait" and "check" checks.
    """
    # Do your thing here. The return values are not used.

This method must execute provided action.

Args

action : dict
Processed action data.
i : int
Action index.
wait : bool
Check that the action "finishes" before returning.
check : bool
Check that the action completed "successfully" before returning.
timeout : float
Timeout for the "wait" and "check" checks.
async def example_coroutine(self)
Expand source code
async def example_coroutine(self):
    """Minimal coroutine object, started by the controller."""
    try:
        while True:
            asyncio.sleep(1)
    except asyncio.CancelledError:
        pass

Minimal coroutine object, started by the controller.

def register_event_handlers(self)
Expand source code
def register_event_handlers(self):
    """ Add event handlers to the socket.io object from the controller class.
    The events will typically originate from the Pipettin Writer GUI's backend.
    """
    if self.controller.comms.sio:
        # Register an event handler. This uses the socket.io object from the controller class.
        @self.controller.comms.sio.on('example_event')
        async def example_event_handler(data: dict):
            # Log the event.
            logging.info("Received 'example_event' command: %s", pformat(data))

            # Do something with the data...
            cmd_id = data.get("id", None)  # Get the ID property, returning "-1" if not found.

            # Make a unique ID for the command
            if cmd_id is None:
                cmd_id = self.make_cmd_id('example_event_handler')

            # Respond to the event.
            return {"id": cmd_id, "data": "example response data"}

Add event handlers to the socket.io object from the controller class. The events will typically originate from the Pipettin Writer GUI's backend.