Custom Instruments


This document describes how to extend the Genie LabOS to manage a new instrument type. Note that a Premium account is required to add custom instrument types. Instrument operations are written in Python, therefore working knowledge of the Python programming language is also required.

The document will walk through the high level steps below, using the Opentrons MagDeck as an example instrument.

  • Setting up the instrument
  • Connecting the instrument to the LabOS platform
  • Publishing an operation and associating it with the instrument
  • Controlling the instrument through LabOS

Instrument Setup

Instrument Setup aims to achieve two goals:

  1. Verify that the instrument is in good working condition
  2. Ensure the instrument APIs are accessible to an internet connected Windows PC

Instrument APIs differ but typically fall into one of the categories listed below. Regardless of the exposed API, make sure you can programmatically access the API from your Windows PC.

  • Network based API, such as SiLA or REST. Network accessible APIs make it easy to control an instrument from centralized controllers. Cytena CWash’s SiLA API is a good example of a network API; another is the PF400 robotic arm’s raw TCP based API. In case of a network based API, it’s often enough to ensure the Windows PC and the instrument are connected to the same network.
  • Serial Port based API. A number of instruments expose an API over the serial port, typically using serial over USB. QInstruments BioShake and Opentrons MagDeck are good examples of instruments which expose their API over the serial port. In case of a serial port based API, connect the instrument’s serial or USB cable to the Windows PC.
  • C# DLL based API. Some instrument vendors publish their API as a C# DLL. Examples include INHECO’s TEC controlled instruments and PAA’s KX2 arm. In case of a DLL based API, install the DLL(s) and all of the required packages onto the Windows PC.
  • USB HID based API. This is less common but is, for example, one of the APIs exposed by the KingFisher Presto. Just like with a serial port based API, connect the instrument’s USB cable to the Windows PC.

Instrument Onboarding

The goal of Instrument Onboarding is to establish connectivity between the target instrument and the LabOS platform. LabOS communicates with on-premises instruments through the Connector software package. LabOS sends instrument commands to the Connector, which forwards them to the instruments and returns the results. Login to the Genie LabOS platform to download the Connector. Once installed on the aforementioned Windows PC, the Connector will show as being Online in the Connectors table.

Navigate to the Virtual Lab page and add a new instrument with the following selections:
  • Select the previously installed Connector. Instrument commands will be sent to this Connector to then be forwarded to the instrument.
  • Provide a user friendly name for the instrument, such as MagDeck.
  • Select Custom as the instrument type and use a descriptive instrument type, such as OTMagDeck.
At this point the instrument’s status will show as Idle but it won’t have any Actions or Configurations.

Publishing a Custom Operation

The goal of this step is to publish a Python script that will execute a specific user action on your instrument. The script is published as a LabOS Operation, which can prompt users for script parameters. We will create an operation to move the Opentrons MagDeck to a specific position.

Navigate to Library > Operations to create a new Operation. Set the operation name to Move, instrument family to Custom and instrument type to OTMagDeck. Add a new Number parameter called Height with a minimum value of 0 and maximum value of 45. Set the default value to 30 and check the Allow Override checkbox. LabOS will prompt the user to enter a value between 0 and 45 with 30 being automatically populated into the input field. The goal is to create an operation that moves the magnet to the user provided offset (in millimeters). Select Insert Snippet > Load Parameters to generate Python code that accesses the user supplied parameter value.

Paste in the Python code shown below to fully implement the Move operation.
					""" Opentrons MagDeck Driver """
from enum import Enum
from typing import List, Optional
from serial import Serial  # type: ignore
from import comports  # type: ignore
from labsdk.state.parameters import load_parameters_env
class Commands(str, Enum):
    Home = 'G28.2'
    Probe = 'G38.2'
    GetPlateHeight = 'M836'
    GetInfo = 'M115'
    GetPosition = 'M114.2'
    Move = 'G0'
ACK = 'ok\r\nok\r\n'
MAGDECK_PORT_NAME = 'Opentrons MagDeck'
class OpentronsMagnetDriver():
    """ Driver for Opentrons Magnet """
    def __init__(self, port_name: Optional[str] = None):
        self.port_name = port_name
        self._port = None
    def connect(self) -> None:
        if self.port_name is None:
            ports = list(map(lambda port: port.device, filter(lambda port: port.product == MAGDECK_PORT_NAME, comports())))
            if len(ports) == 0:
                raise ValueError('Could not find an Opentrons MagDeck')
            self.port_name = ports[0]
        if self._port is None:
                self._port = Serial(self.port_name, baudrate=115200, timeout=10)
                self.get_device_info()  # throws if no response is received
    def disconnect(self) -> None:
        if self._port is not None:
            self._port = None
    def port(self) -> Serial:
        if self._port is None:
            raise ValueError('Not connected to a Opentrons MagDeck')
        return self._port
    def _send_command(self, elements: List[str]) -> str:
        if elements[-1] is not COMMAND_TERMINATOR:
        self.port.write(' '.join(elements).encode('utf8'))
        # Read until we reach the ack, or we hit the timeout
        response_bytes = self.port.read_until(ACK.encode('utf8'))
        if len(response_bytes) == 0:
            raise ValueError('MagDeck did not return valid device info')
        # verify that an ACK was in the response and then return the response 'body'
        response = response_bytes.decode('utf8')
        return response.split(ACK)[0].strip() if ACK in response else ''
    def move(self, mag_height: float = DEFAULT_PLATE_HEIGHT) -> None:
        """ Move to the specified height """
        if mag_height > MAX_TRAVEL_DISTANCE or mag_height < 0:
            raise ValueError(f'Height of {mag_height} is not supported')
        self._send_command([Commands.Move, f'Z{mag_height}'])
    def home(self) -> None:
        """ move the magnet to the home position """
    def get_device_info(self) -> str:
        """ return the device info """
        response = self._send_command([Commands.GetInfo])
        if response == '':
            raise ValueError('Did not receive device info details from Opentrons MagDeck')
        return response
def main() -> None:
    params = load_parameters_env()
    height = params['height'].data
    driver = OpentronsMagnetDriver()
if __name__ == "__main__":

Save and Publish the operation. Publishing an operation creates an immutable version of the operation.

Navigate to the Virtual Lab and open the previously onboarded MagDeck instrument. On the Actions screen, add the newly published operation to the instrument. Be sure to Save the instrument.

Using the Custom Operation

From the Instruments page, execute the Move action. Observe the magnet moving.

Alternatively, you can invoke this custom operation through the Instrument REST API.

Book a Demo

Genie is now in limited Beta. Complete the form below and one of our sales representitives will reach out to you within 24 hours to setup an in-person demo of our platform.