opentrons 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/opentrons/commands.rb +127 -0
- data/lib/opentrons/instruments.rb +243 -0
- data/lib/opentrons/labware.rb +156 -0
- data/lib/opentrons/otprotocol.rb +81 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95cdf5d0184ba33c091182c256d97d90cdd15d59
|
|
4
|
+
data.tar.gz: b0b638480c593846786765098f61942f05b9fbeb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f1cd9e362d4288610f59f7cc19fed1d49b36c0c646876f2386e66332d3f459e3be336eff467014d38fb56ecf0a2f0f7bf2707216a6b43f20c87a18f629dba7a
|
|
7
|
+
data.tar.gz: 39b157b04679225ba97a5658dd995ea762c2ab13a62eb5221bfdb8c724b712e2f7bac2a052cfe7dedf7896dc91d7d798e2a2edd0a7d9de28ac23c542047fa490
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module OpenTrons
|
|
2
|
+
class Commands
|
|
3
|
+
attr_accessor :protocol, :command_list
|
|
4
|
+
|
|
5
|
+
def initialize(protocol)
|
|
6
|
+
@protocol = protocol
|
|
7
|
+
@command_list = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# return a pure list of hashes
|
|
11
|
+
def to_list
|
|
12
|
+
return command_list.map {|command| command.to_hash}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
"<OpenTron::Commands:0x#{self.__id__.to_s(16)}>"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def inspect
|
|
20
|
+
to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Command
|
|
25
|
+
attr_accessor :command, :params
|
|
26
|
+
|
|
27
|
+
#parent class for all the comands
|
|
28
|
+
def initialize(command, params)
|
|
29
|
+
@command = command
|
|
30
|
+
@params = params
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_hash
|
|
34
|
+
as_hash = {}
|
|
35
|
+
as_hash["command"] = command
|
|
36
|
+
as_hash["params"] = params
|
|
37
|
+
return as_hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_s
|
|
41
|
+
"<OpenTron::Command:0x#{self.__id__.to_s(16)}>"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inspect
|
|
45
|
+
to_s
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class PipetteCommand < Command
|
|
50
|
+
attr_accessor :pipette, :location
|
|
51
|
+
|
|
52
|
+
def initialize(command, pipette, location)
|
|
53
|
+
super(command, {})
|
|
54
|
+
|
|
55
|
+
pipette_id = pipette.instruments.instrument_hash.key pipette
|
|
56
|
+
params["pipette"] = pipette_id
|
|
57
|
+
|
|
58
|
+
if location.is_a? Array
|
|
59
|
+
labware_item = location[0].labware_item
|
|
60
|
+
params["labware"] = labware_item.labware.labware_hash.key labware_item
|
|
61
|
+
params["well"] = location[0].location
|
|
62
|
+
params["position"] = location[1]
|
|
63
|
+
else
|
|
64
|
+
labware_item = location.labware_item
|
|
65
|
+
params["labware"] = labware_item.labware.labware_hash.key labware_item
|
|
66
|
+
params["well"] = location.location
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class VolumeCommand < PipetteCommand
|
|
72
|
+
attr_accessor :volume
|
|
73
|
+
|
|
74
|
+
def initialize(command, pipette, volume, location)
|
|
75
|
+
super(command, pipette, location)
|
|
76
|
+
params["volume"] = volume
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Delay < Command
|
|
81
|
+
def initialize(wait, message)
|
|
82
|
+
super("delay", {"wait" => wait, "message" => message})
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class Aspirate < VolumeCommand
|
|
87
|
+
def initialize(pipette, volume, location)
|
|
88
|
+
super("aspirate", pipette, volume, location)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class Dispense < VolumeCommand
|
|
93
|
+
def initialize(pipette, volume, location)
|
|
94
|
+
super("dispense", pipette, volume, location)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class AirGap < VolumeCommand
|
|
99
|
+
def initialize(pipette, volume, location)
|
|
100
|
+
super("air-gap", pipette, volume, location)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class PickUpTip < PipetteCommand
|
|
105
|
+
def initialize(pipette, location)
|
|
106
|
+
super("pick-up-tip", pipette, location)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class DropTip < PipetteCommand
|
|
111
|
+
def initialize(pipette, location)
|
|
112
|
+
super("drop-tip", pipette, location)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class TouchTip < PipetteCommand
|
|
117
|
+
def initialize(pipette, location)
|
|
118
|
+
super("touch-tip", pipette, location)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class BlowOut < PipetteCommand
|
|
123
|
+
def initialize(pipette, location)
|
|
124
|
+
super("blowout", pipette, location)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
module OpenTrons
|
|
2
|
+
PIPETTE_NAMES = {
|
|
3
|
+
P10_Single: "p10_single_v1",
|
|
4
|
+
P10_Multi: "p10_multi_v1",
|
|
5
|
+
P50_Single: "p50_single_v1",
|
|
6
|
+
P50_Multi: "p50_multi_v1",
|
|
7
|
+
P300_Single: "p300_single_v1",
|
|
8
|
+
P300_Multi: "p300_multi_v1",
|
|
9
|
+
P1000_Single: "p1000_single_v1",
|
|
10
|
+
P1000_Multi: "p1000_multi_v1"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class Instruments
|
|
14
|
+
attr_accessor :protocol, :instrument_hash
|
|
15
|
+
|
|
16
|
+
def initialize(protocol)
|
|
17
|
+
@protocol = protocol
|
|
18
|
+
@instrument_hash = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Individual methods to maintain similarities w/ Python API.
|
|
22
|
+
PIPETTE_NAMES.each do |method_name, pipette_name|
|
|
23
|
+
define_method(method_name) do |**kwargs|
|
|
24
|
+
|
|
25
|
+
mount = kwargs.fetch(:mount, "left")
|
|
26
|
+
tip_racks = kwargs.fetch(:tip_racks, [])
|
|
27
|
+
tip_model = kwargs.fetch(:tip_model, nil)
|
|
28
|
+
|
|
29
|
+
return add_pipette(pipette_name, mount: mount, tip_racks: tip_racks, tip_model: tip_model)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_pipette(model, mount: "left", tip_racks: [], tip_model: nil)
|
|
34
|
+
instrument_hash.each do |key, item|
|
|
35
|
+
if item.mount == mount
|
|
36
|
+
raise ArgumentError.new "Cannot place #{model} on mount #{mount} (already occupied)."
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
generated_id = ""
|
|
41
|
+
loop do
|
|
42
|
+
generated_id = model + "-" + rand(100000...999999).to_s
|
|
43
|
+
break if !(instrument_hash.key? generated_id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if model.include? "multi"
|
|
47
|
+
pipette = MultiPipette.new(protocol, self, model, mount: mount, tip_racks: tip_racks, tip_model: tip_model)
|
|
48
|
+
else
|
|
49
|
+
pipette = Pipette.new(protocol, self, model, mount: mount, tip_racks: tip_racks, tip_model: tip_model)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
instrument_hash[generated_id] = pipette
|
|
53
|
+
|
|
54
|
+
return pipette
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_hash
|
|
58
|
+
as_hash = {}
|
|
59
|
+
instrument_hash.each do |key, item|
|
|
60
|
+
as_hash[key] = item.to_hash
|
|
61
|
+
end
|
|
62
|
+
return as_hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_s
|
|
66
|
+
"<OpenTron::Instruments:0x#{self.__id__.to_s(16)}>"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def inspect
|
|
70
|
+
to_s
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Pipette
|
|
75
|
+
attr_accessor :protocol, :instruments, :mount, :model, :tip_racks, :tip_model
|
|
76
|
+
|
|
77
|
+
def initialize(protocol, instruments, model, mount: "left", tip_racks: [], tip_model: nil)
|
|
78
|
+
@protocol = protocol
|
|
79
|
+
@instruments = instruments
|
|
80
|
+
@model = model
|
|
81
|
+
|
|
82
|
+
@mount = mount
|
|
83
|
+
@tip_racks = tip_racks
|
|
84
|
+
@tip_model = tip_model
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def to_hash
|
|
88
|
+
as_hash = {}
|
|
89
|
+
as_hash["mount"] = mount
|
|
90
|
+
as_hash["model"] = model
|
|
91
|
+
return as_hash
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_s
|
|
95
|
+
"<OpenTron::Pipette:0x#{self.__id__.to_s(16)}>"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def inspect
|
|
99
|
+
to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def aspirate(volume, location)
|
|
103
|
+
command = Aspirate.new(self, volume, location)
|
|
104
|
+
protocol.commands.command_list << command
|
|
105
|
+
return command
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def dispense(volume, location)
|
|
109
|
+
command = Dispense.new(self, volume, location)
|
|
110
|
+
protocol.commands.command_list << command
|
|
111
|
+
return command
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# For whatever reason, air gap has no location in Python API but has a location in the JSON schema.
|
|
115
|
+
# Not implemented for now.
|
|
116
|
+
# def air_gap(volume, location)
|
|
117
|
+
# command = Aspirate.new(self, volume, location)
|
|
118
|
+
# self.protocol.commands << command
|
|
119
|
+
# return command
|
|
120
|
+
# end
|
|
121
|
+
|
|
122
|
+
def get_next_tip(multi: false)
|
|
123
|
+
location = nil
|
|
124
|
+
catch :tip_found do
|
|
125
|
+
tip_racks.each do |tip_rack|
|
|
126
|
+
tip_rack.well_list.each do |column|
|
|
127
|
+
if multi
|
|
128
|
+
if column.all? {|x| x.tip}
|
|
129
|
+
location = column[0]
|
|
130
|
+
throw :tip_found
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
column.each do |x|
|
|
134
|
+
if x.tip
|
|
135
|
+
location = x
|
|
136
|
+
throw :tip_found
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
return false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
return location
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def pick_up_tip(location=false)
|
|
149
|
+
if !location
|
|
150
|
+
tip_location = self.get_next_tip()
|
|
151
|
+
# If no tip found and tip model is provided, create a tip rack.
|
|
152
|
+
if !tip_location
|
|
153
|
+
if tip_model
|
|
154
|
+
tip_racks << protocol.labware.load(tip_model, protocol.labware.free_slots[-1], 'Auto-generated-tip-rack')
|
|
155
|
+
else
|
|
156
|
+
raise ArgumentError.new "pick_up_tip called without location and pipette is out of tips."
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
tip_location = self.get_next_tip()
|
|
160
|
+
location = tip_location
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if location.is_a? Array
|
|
164
|
+
well = location[0]
|
|
165
|
+
else
|
|
166
|
+
well = location
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if !(well.tip)
|
|
170
|
+
puts "Warning: Already picked up tip at #{location}."
|
|
171
|
+
else
|
|
172
|
+
well.tip = false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
command = PickUpTip.new(self, location)
|
|
176
|
+
protocol.commands.command_list << command
|
|
177
|
+
return command
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def drop_tip(location=false)
|
|
181
|
+
location = protocol.trash.wells(0) if !location
|
|
182
|
+
command = DropTip.new(self, location)
|
|
183
|
+
protocol.commands.command_list << command
|
|
184
|
+
return command
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def touch_tip(location)
|
|
188
|
+
command = TouchTip.new(self, location)
|
|
189
|
+
protocol.commands.command_list << command
|
|
190
|
+
return command
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def blowout(location)
|
|
194
|
+
command = Blowout.new(self, location)
|
|
195
|
+
protocol.commands.command_list << command
|
|
196
|
+
return command
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def delay(wait, message: "")
|
|
200
|
+
command = Delay.new(wait, message)
|
|
201
|
+
protocol.commands.command_list << command
|
|
202
|
+
return command
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
class MultiPipette < Pipette
|
|
207
|
+
def initialize(protocol, instruments, model, mount: "left", tip_racks: [], tip_model: nil)
|
|
208
|
+
super(protocol, instruments, model, mount: mount, tip_racks: tip_racks, tip_model: tip_model)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def pick_up_tip(location: false)
|
|
212
|
+
if !location
|
|
213
|
+
tip_location = self.get_next_tip(multi: true)
|
|
214
|
+
# If no tip found and tip model is provided, create a tip rack.
|
|
215
|
+
if !tip_location
|
|
216
|
+
if tip_model
|
|
217
|
+
tip_racks += protocol.labware.load(tip_model, protocol.labware.free_slots[-1], 'Auto-generated-tip-rack')
|
|
218
|
+
else
|
|
219
|
+
raise ArgumentError.new "pick_up_tip called without location and pipette is out of tips."
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
tip_location = self.get_next_tip(multi: true)
|
|
223
|
+
location = tip_location
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
if location.is_a? Array
|
|
227
|
+
well = location[0]
|
|
228
|
+
else
|
|
229
|
+
well = location
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
column = well.labware_item.well_list.find do |column|
|
|
233
|
+
column.include? well
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
column.each {|x| x.tip = false}
|
|
237
|
+
|
|
238
|
+
command = PickUpTip.new(self, location)
|
|
239
|
+
protocol.commands.command_list << command
|
|
240
|
+
return command
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
module OpenTrons
|
|
2
|
+
class Labware
|
|
3
|
+
attr_accessor :protocol, :labware_hash, :labware_definitions
|
|
4
|
+
|
|
5
|
+
def initialize(protocol)
|
|
6
|
+
@protocol = protocol
|
|
7
|
+
@labware_hash = {}
|
|
8
|
+
|
|
9
|
+
#TODO: Better system for dealing with labware defs, including user-specified.
|
|
10
|
+
@labware_definitions = []
|
|
11
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
|
12
|
+
directory = File.join(directory, "..", "..", "definitions")
|
|
13
|
+
Dir[directory + "/*.json"].each do |filename|
|
|
14
|
+
labware_definitions << JSON.parse(File.read(filename))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load(model, slot, display_name="")
|
|
19
|
+
generated_id = ""
|
|
20
|
+
loop do
|
|
21
|
+
generated_id = display_name + "-" + rand(100000...999999).to_s
|
|
22
|
+
break if !(labware_hash.key?(generated_id))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
labware_item = LabwareItem.new(self, model, slot, display_name)
|
|
26
|
+
|
|
27
|
+
labware_hash[generated_id] = labware_item
|
|
28
|
+
|
|
29
|
+
return labware_item
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def free_slots
|
|
33
|
+
slots = (1..12).to_a.map{|x| x.to_s}
|
|
34
|
+
taken_slots = labware_hash.map {|key, item| item.slot}
|
|
35
|
+
return slots.select{|x| !(taken_slots.include? x)}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns a pure hash of hashes.
|
|
39
|
+
def to_hash
|
|
40
|
+
as_hash = {}
|
|
41
|
+
labware_hash.each do |key, item|
|
|
42
|
+
as_hash[key] = item.to_hash
|
|
43
|
+
end
|
|
44
|
+
return as_hash
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"<OpenTron::Labware:#{object_id}>"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
to_s
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class LabwareItem
|
|
57
|
+
attr_accessor :labware, :well_list, :model, :slot, :display_name, :definition
|
|
58
|
+
|
|
59
|
+
def initialize(labware, model, slot, display_name)
|
|
60
|
+
if labware.labware_hash.map {|key, item| item.slot}.include? slot
|
|
61
|
+
raise ArgumentError.new "Cannot place #{display_name} in slot #{slot} (already occupied)."
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@labware = labware
|
|
65
|
+
@model = model
|
|
66
|
+
@slot = slot
|
|
67
|
+
@display_name = display_name
|
|
68
|
+
@definition = labware.labware_definitions.find{|x| x["metadata"]["name"] == model}
|
|
69
|
+
@well_list = []
|
|
70
|
+
definition["ordering"].each do |column|
|
|
71
|
+
well_list << column.map {|x| Well.new(self, x)}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def wells(location=nil)
|
|
77
|
+
if location.is_a? String
|
|
78
|
+
well_list.each do |column|
|
|
79
|
+
column.each do |x|
|
|
80
|
+
if x.location == location
|
|
81
|
+
return x
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
return ArgumentError.new "Well #{location} is out of range."
|
|
86
|
+
elsif location.is_a? Integer
|
|
87
|
+
i = location
|
|
88
|
+
well_list.each do |column|
|
|
89
|
+
column.each do |x|
|
|
90
|
+
if i == 0
|
|
91
|
+
return x
|
|
92
|
+
end
|
|
93
|
+
i -= 1
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
return ArgumentError.new "Well #{location} is out of range."
|
|
97
|
+
else
|
|
98
|
+
return well_list
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_hash
|
|
103
|
+
as_hash = {}
|
|
104
|
+
as_hash["model"] = model
|
|
105
|
+
as_hash["slot"] = slot
|
|
106
|
+
as_hash["display-name"] = display_name
|
|
107
|
+
return as_hash
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_s
|
|
111
|
+
"<OpenTron::LabwareItem:0x#{self.__id__.to_s(16)}>"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def inspect
|
|
115
|
+
to_s
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class Well
|
|
120
|
+
attr_accessor :labware_item, :location, :tip
|
|
121
|
+
|
|
122
|
+
def initialize(labware_item, location)
|
|
123
|
+
@labware_item = labware_item
|
|
124
|
+
@location = location
|
|
125
|
+
@tip = true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def top(z)
|
|
129
|
+
position = {
|
|
130
|
+
"anchor" => "top",
|
|
131
|
+
"offset" => {
|
|
132
|
+
"z" => z
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return [self, position]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def bottom(z)
|
|
139
|
+
position = {
|
|
140
|
+
"anchor" => "bottom",
|
|
141
|
+
"offset" => {
|
|
142
|
+
"z" => z
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return [self, position]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def to_s
|
|
149
|
+
"<OpenTron::Well:0x#{self.__id__.to_s(16)}>"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def inspect
|
|
153
|
+
to_s
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module OpenTrons
|
|
2
|
+
# This gem is not a product of OpenTrons.
|
|
3
|
+
#
|
|
4
|
+
# This version of the gem provides some basic functionality for creating and editing OpenTrons JSON-format
|
|
5
|
+
# protocols in Ruby. The organization of the methods should be familiar to anyone who has used
|
|
6
|
+
# the official OpenTrons Python API.
|
|
7
|
+
#
|
|
8
|
+
# Examples:
|
|
9
|
+
# Import the gem: require 'opentrons'
|
|
10
|
+
# Create a protocol: p = OpenTrons::OTProtocol.new
|
|
11
|
+
# Create a labware item: block = p.labware.load('96-deep-well', '2', 'culture_block')
|
|
12
|
+
# Create a tiprack: tip_rack_1 = p.labware.load('tiprack-10ul', '3', 'tiprack-10ul')
|
|
13
|
+
# Create a pipette: p10 = p.instruments.P10_Single(mount: 'left', tip_racks: [tip_rack_1])
|
|
14
|
+
# Pick up a tip: p10.pick_up_tip()
|
|
15
|
+
# Add an aspirate command: p10.aspirate(10, block.wells(0))
|
|
16
|
+
# Add a dispense command: p10.dispense(10, block.wells(1).top(2))
|
|
17
|
+
# Discard tip: p10.drop_tip()
|
|
18
|
+
#
|
|
19
|
+
# Examples for saving protocols:
|
|
20
|
+
# Generate a hash of the protocol: p.to_hash
|
|
21
|
+
# Generate a JSON string of the protocol: p.to_json
|
|
22
|
+
# Save a protocol as a JSON file: File.open("protocol.json", 'w') {|f| f.write(p.to_json)}
|
|
23
|
+
#
|
|
24
|
+
# Limitations of current version:
|
|
25
|
+
# -Built for OT protocol JSON schema 1.0 (which is not the final version).
|
|
26
|
+
# https://github.com/Opentrons/opentrons/blob/391dcebe52411c432bb6f680d8aa5952a11fe90f/shared-data/protocol-json-schema/protocol-schema.json
|
|
27
|
+
# -Custom containers yet supported
|
|
28
|
+
# -Modules (heat and magnetic) not yet supported
|
|
29
|
+
# -Complex liquid handling shortcuts not yet supported
|
|
30
|
+
# -Loading and editing existing protocols from JSON not yet supported.
|
|
31
|
+
# -Error checking not very robust
|
|
32
|
+
# -And more...
|
|
33
|
+
class OTProtocol
|
|
34
|
+
attr_accessor :protocol_schema, :robot, :designer_application, :metadata, :labware, :instruments, :commands, :trash
|
|
35
|
+
|
|
36
|
+
def initialize(params: {})
|
|
37
|
+
@protocol_schema = params.fetch(:protocol_schema, "1.0.0")
|
|
38
|
+
@robot = params.fetch(:robot, {"model" => "OT-2 Standard"})
|
|
39
|
+
@designer_application = params.fetch(:designer_application, {})
|
|
40
|
+
@metadata = params.fetch(:metadata, {})
|
|
41
|
+
|
|
42
|
+
@labware = params.fetch(:labware, Labware.new(self))
|
|
43
|
+
@trash = labware.load('fixed-trash', '12', 'Trash')
|
|
44
|
+
|
|
45
|
+
@instruments = params.fetch(:instruments, Instruments.new(self))
|
|
46
|
+
|
|
47
|
+
@commands = params.fetch(:commands, Commands.new(self))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_hash(check_validity: true)
|
|
51
|
+
# Returns entire protocol as an OT-protocol-format hash (which can then be converted to json).
|
|
52
|
+
protocol_hash = {}
|
|
53
|
+
|
|
54
|
+
protocol_hash["protocol-schema"] = protocol_schema
|
|
55
|
+
protocol_hash["robot"] = robot
|
|
56
|
+
protocol_hash["designer_application"] = designer_application
|
|
57
|
+
protocol_hash["metadata"] = metadata
|
|
58
|
+
|
|
59
|
+
protocol_hash["labware"] = labware.to_hash
|
|
60
|
+
|
|
61
|
+
protocol_hash["pipettes"] = instruments.to_hash
|
|
62
|
+
|
|
63
|
+
protocol_hash["procedure"] = [{"subprocedure" => commands.to_list}]
|
|
64
|
+
|
|
65
|
+
return protocol_hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_json(check_validity: true)
|
|
69
|
+
#converts protocol to a JSON-formatted string
|
|
70
|
+
return self.to_hash.to_json
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_s
|
|
74
|
+
"<OpenTron::OTProtocol:0x#{self.__id__.to_s(16)}>"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inspect
|
|
78
|
+
to_s
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opentrons
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Emery
|
|
@@ -18,6 +18,10 @@ extensions: []
|
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
20
|
- lib/opentrons.rb
|
|
21
|
+
- lib/opentrons/commands.rb
|
|
22
|
+
- lib/opentrons/instruments.rb
|
|
23
|
+
- lib/opentrons/labware.rb
|
|
24
|
+
- lib/opentrons/otprotocol.rb
|
|
21
25
|
homepage: https://github.com/emernic/opentrons_gem
|
|
22
26
|
licenses:
|
|
23
27
|
- LICENSE.txt
|