software to provide detailed control of Opentrons OT2 robot
The code is designed to be run from two computers. The controlling computer uses a ProtocolExecutor object to run a protocol from googlesheets. The raspberry pi on the robot runs a recieving code to take commands from the executor and runs them on the robot.
This guide is intended for a nontechnical audience.
python controller.py
. (Note, for advanced
users, controller.py has a cli to skip later input stages.
run python controller.py -h
for more information) Note: The above steps assume that the ot2_robot code is running, which is almost always the
case. This code is designed to be very robust, and should only need to be rerun when updated or
when the robot is powered off. To run the code, ssh into the robot and move into the directory,
OT2_Control
, and run the command python ot2_robot.py
. (There's also usually a tmux session
named run that is already in the directory)
Documentation for the code can be found here https://hendricks-laboratory.github.io/OT2Control/
The code follows an object oriented model, and global functions should be used only for the driver code in controller.py and ot2_robot.py, or in df_utils for simple helper functions that are clearly not a part of a larger class. Global variables should not be used. Constants are ok if documented.
All docstrings are formatted in markdown, (so you must add two spaces to the end of the line to
terminate it)
All Classes, Modules, and methods/functions should have a docstring. The docstring format of a
module is freeform. The formats for the other types are shown below.
specified below.
'''
Description of class
ATTRIBUTES:
CONSTANTS:
METHODS:
INHERITED METHODS: (optional)
INHERITED ATTRIBUTES: (optional)
'''
'''
Description
params:
returns:
'''
The code implements a number of useful abstractions that should be preserved as new features are added.
The code is designed to be run with the ot2_robot server on another machine. Therefore, all input/output should take place on the controller.
The only systems that the ot2_robot code interacts with are the os it runs on (to dump local files), and the controller. i.e. All google sheets i/o is handled by the controller.
The exact locations of chemicals on the robot are not visible to the controller. --This abstraction is very leaky (because the platereader needs access to exact locations) , and may need to be replaced later.
The controller does not have access to any opentrons API information, or details about labware, e.g. volumes, what locations they have.
The recipt of ready commands is an Armchair level process, and is not visible to client code.
This section guides you through the process of implementing a new feature to transfer a fraction of the contents of a chemical into another well. e.g. move half of the liquid in 'R1' to 'P2'. The steps are presented sequentially in an intuitive order that more or less follows the control flow of the program in execution, but, of course, it make no difference in what order these are implemented. Most features will adhere to a similar pattern.
Define a new operation in the excel sheet, lets call it 'move_reagent'.
Define Arguments: This command will need three arguments, the src wellname, the dst wellname it to, and the ratio of the volume to move. It is intuitive to use the existing structure of the excel sheet for the src and destination. Lets specify the destination by putting a 1 in the appropriate column, and we'll specify the src in the reagent column. We also need to specify the amount to transfer, so let's make a new column to hold that parameter left of the reagent column. --Note we could also use a float instead of 1 in order to specify what percentage to transfer, but for demonstration purposes, we'll add the argument.
Update excel_specs: As soon as you change the rxn sheet, you should update the excel_specification with the changes you made.
Update parsing: Since we added a new argument, we'll need to change a little about how it's parsed. In our case this is as simple as changing _load_rxn_df(input_data) in the controller to rename your new column to something that's less wordy and doesn't have spaces (you don't need to do this. It's convenience so you can have different names in code and on sheets)
Think about the information you need: We now have a new type of row, but we need to think about what information needs to be extracted to send to the robot. In this case it's very simple. The robot will need to know, the src, the dst, and the ratio of volume to transfer.
Make a new armchair packet type:
Create a helper function with args (row, i). This function will need to parse a 'move_reagent' row of the reaction dataframe and extract the three parameters mentioned above. These parameters should then be sent over the Armchair portal using send_pack.
Update execute_protocol_df in the controller to check if the operation is 'move_reagent' if it is, you should call your helper.
Create a helper function for the robot. This is where we'll implement the helper we called under execute, self._exec_move_reagent(*arguments). You should unpack arguments into the parameters you need for this function, and then write the code to make the robot do what you need. There are likely already helper functions defined in ot2_robot that you should use to help, as well as other class attributes. In our specific case it will look something like the following:
Register your helper: In order for the robot to invoke your helper function when it recieves
an armchair command, you must register it with the decorator, exec_func(str name, int exit_code, bool send_ready, dict_exec_funcs)
. Going through these arguments:
That's it! To test you can run everything locally, and just enter 'n' after the simulation, or you can run it on the robot and platereader without physically moving anything by runing the script with the -s flag, and entering 'y' after the simulation.