Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Integrating your Devices with MADSci Nodes

This notebook aims to teach you how to automate and integrate all the devices, instruments, sensors, and robots in your self-driving lab using the MADSci Node standard.

Goals

After completing this notebook, you should understand

  1. What we mean when we talk about a MADSci Node

  2. The MADSci Node Standard

  3. How to integrate and automate a device using the MADSci Node standard

  4. How to use the RestNode python class included in madsci.node_module to integrate a MADSci Node

  5. How to leverage the built-in MADSci client management through the MadsciClientMixin

MADSci Control Flow Diagram

What is a Node?

  • In MADSci, a Node refers to a single instrument, sensor, robot, or other device, combined with the software needed to control, operate, automate, and integrate it into the Automated or Autonomous Lab as a whole.

  • Node’s accept Action Requests, and return Action Results. They also report State and Status, Info about themselves, log Events, and can optionally use and manage Resources.

Diagram illustratring how a node intermediates between a User and different devices

Anatomy of a Node

A Node typically consists of the following sub-components:

  • A physical device (robot, instrument, sensor, etc.)

  • A driver, API, library, or software application for communicating with that device, often provided by the hardware vendor

  • A device interface class that handles the neccessary initialization, communication, and cleanup required to use the given device

  • The node implementation, typically a class, which uses the interface to handle the execution of the actions and the lifecycle of the node (state, status, statup, shutdown, etc.).

  • The node configuration, which defines specific details about a given instance of a node. Configuration can be set via settings files, environment variables, or in code.

  • The node server and node client, which allow for standardized control of nodes. These are implementations of the MADSci Node standard interface, and operate using standard protocols (currently, REST-based HTTP)

Diagram of the components of a MADSci Node

Node Instances vs. Node Modules

Sometimes, we need to disambiguate between “this particular Node, tied to a specific device” and “this class of Node, tied to this type of device”. We usually refer to the former as a Node Instance, and the latter as a Node Module when we need to be precise.

For example, we might have one “Opentrons OT-2” Node Module that we’ve written, which we then use to run 2 different Node Instances in the lab: “OT-2 Alpha” and “OT-2 Beta”

Defining A Node

While you can build a MADSci compliant Node entirely from scratch, that’s a lot of work. To save you some time and effort, we’ve implemented our own RestNode python class that you can inherit from as the basis for defining a device’s Node.

In the rest of the notebook, we’ll demonstrate an example of integrating a fake “robot arm” as a MADSci Node Module using our RestNode class.

Node Configuration

Each Node uses a NodeConfig (or RestNodeConfig for REST-based nodes) to define its configuration. This includes the node’s identity (name, type, module), as well as any node-specific settings.

Configuration values can be set via:

  • Environment variables (prefixed with NODE_, e.g. NODE_NAME=my_robot)

  • Settings files (settings.yaml, .env, etc.)

  • Code, by passing a NodeConfig instance when creating the node

This makes it easy to differentiate between and configure multiple Node Instances of a given Node Module without changing any code.

Node Configuration and Device Interfaces

In addition to the Node Class, there are two other important things we may want to define in order to integrate a device.

Node Config

The NodeConfig class is a class we define for a Node that, as the name implies, defines the configuration available for a specific Node. This often includes things like device identifiers or IP addresses, initialization arguments to pass to the device’s driver, or details about the specific device being controlled. It also includes common configurations for the node itself, such as how to configure the REST server for the node.

We define our NodeConfig as a Pydantic Settings class, and have a RestNodeConfig we can use as a base class with default configuration for the REST server.

One of the advantages of defining our NodeConfig in this way is that we can set configuration values:

  • Using environment variables (with the NODE_ prefix)

  • Using settings files (settings.yaml, .env, etc.)

  • In code, by passing a NodeConfig instance when creating the node

  • Using the set_config endpoint on a running Node.

Device Interface

Many of the devices we need to automate have their own DLL’s, API’s, Libraries, or other means of controlling and communicating with them. The exact details, capabilities, and implementations vary wildly from device to device, but in most cases these are complex enough to justify their own wrapper class.

Implementing the device specific logic as a standalone class provides two key advantages:

  • We can use the device interface standalone, for testing and debugging, maintenance, or in applications where we can’t or don’t want to use a full Node.

  • We separate the concerns of managing the node from the nuts and bolts of interfacing with the device.

This isn’t always neccessary (some devices have API’s so simple it simply isn’t worth the effort), but is often worthwhile for complex devices or unwieldy API’s.

Node Info

A node’s way of introducing itself

A useful part of the MADSci Node Standard is the NodeInfo: a description of the node, it’s current configuration and capabilites, and just about everything else you might reasonably want to know about it. The RestNode python class does it’s best to automatically generate as much of the Node Info as possible, so that users, agents, and other components of the system can easily understand what the capabilities and functionality of a given node.

Node Lifecycle

Generally speaking, nodes have the following common lifecycle components:

  • start_node: this default function kickoffs the REST server, loads the node configuration, and calls the user defined startup_handler

  • startup_handler: a user-defined function that handles whatever logic needs to be done to initialize the underlying device and prepare the node for operation. The node won’t start accepting actions until the startup_handler completes.

  • shutdown_handler: a user-defined function that handles the logic of shutting the node down: disconnecting from the device, cleaning up system resources, etc.

  • state_handler: allows the node to update it’s published state, a JSON serializable dictionary

  • status_handler: allows the node to implement any custom logic around updating the NodeStatus used by the node and the rest of the system to understand the condition of the node.

The startup_handler and shutdown_handler generally run once, while the state_handler and status_handler run periodically, with a configurable interval.

Node Lifecycle

Green represents developer-defined functionality, blue is built-in to the REST Node class

Node Actions

Now that we have the lifecycle of the Node working, let’s talk about Actions. In MADSci, a Node Action is, essentially, a function that you can call on the node in a standardized way. These functions typically, but not neccessarily, involve the device the node controls taking some action (e.g. a camera taking a picture, a robot arm moving, a plate reader doing a reading, etc.).

Anatomy of an Action

Node Actions can be defined with the following:

  • Name: a unique name for the action to perform

  • Description: a human-readable description of the action

  • Arguments: the arguments the action takes. Normal arguments must be JSON-serializable types, and can be either required or optional. There are two special cases of arguments:

    • File Arguments: files the action accepts as inputs, which are uploaded by the client

    • Location Arguments: these are functionally the same as regular Arguments, but have special meaning when using a node as part of a MADSci workcell (more on that later)

  • Results: these are what an action returns after completing, typically in the form of JSON Data, Files, or Datapoint IDs

To perform an action with a node, you send an ActionRequest, and the node returns ActionResults capturing the progress and final outcomes of the result.

Client-Server Action Methods

Node Action Method/Endpoint Diagram

Lifecycle of an Action

Action Status State Diagram

Client Integration in Actions

Notice how in the action above, we use the built-in clients:

  • self.event_client.info() for logging action progress

  • self.data_client.add_datapoint() for storing measurement results

  • self.event_client.error() and self.event_client.warning() for error handling

This demonstrates how nodes can seamlessly integrate with the broader MADSci ecosystem to:

  • Log all activities for monitoring and debugging

  • Store experimental data for later analysis

  • Track resource usage (if needed)

  • Coordinate with the overall experiment management system

Admin Commands

Admin commands provide a standardized interface for common administrative tasks used to control nodes. Unlike actions, admin commands have pre-defined behavior, and the node developer chooses whether and how to implement a specific admin command for a given device in the node. The MADSci Node Standard supports the following admin commands:

  • pause: pause the current action, if any

  • resume: resume the current action, if any

  • lock: refuse to accept new actions

  • unlock: resume accepting new actions

  • cancel: cancel the current action

  • reset: clear errors, reinitialize the node, and reset the status

  • stop: a more aggressive cancellation--halt immediately and trigger any safety/e-stop of the device

  • shutdown: disconnect from the device and stop the node’s server