def restart(): """Restart the Crossbar WAMP router and Python backend. By default, does not change networking configuration. """ if debug == True: FileIO.log('script_keeper.restart called') subprocess.call([os.path.join(dir_path,'../../otone_scripts/start.sh'), 'NOCHANGE'])
def share_inet(): """Triggers ethernet interface (eth0) to try to obtain ip address via dhcp by taking it down, and then bringing it up """ if debug == True: FileIO.log('script_keeper.share_inet called') cmd = os.path.join(dir_path,'../../otone_scripts/share_inet.sh') create_share = asyncio.create_subprocess_exec(cmd,stdout=asyncio.subprocess.PIPE) criterion = True while criterion == True: proc_share = yield from create_share stdout_, stderr_ = yield from proc_share.communicate() if stdout_ is not None: stdout_str = stdout_.decode("utf-8") if debug == True and verbose == True: FileIO.log('share_inet.stdout... '+stdout_str) read_progress(stdout_str) else: if debug == True and verbose == True: FileIO.log('share_inet.stdout... None') if stderr_ is not None: if debug == True and verbose == True: FileIO.log('share_inet.stderr...'+stderr_.decode("utf-8")) else: if debug == True and verbose == True: FileIO.log('share_inet.stderr... None') if proc_share.returncode is not None: criterion = False return
def erase_job(self, data): """Call :meth:`clear`... redundant, consider removing, and why does it have unused data parameter??? """ if debug == True: FileIO.log('the_queue.erase_job called') #doesn't map to smoothieAPI #function eraseJob(){ self.clear()
def write_led(num, val): """Turn an LED on or off. Not currently implemented. This is in anticipation of having LED indicators """ if debug == True: FileIO.log('script_keeper.write_led called') subprocess.call([os.path.join(dir_path,'../../otone_scripts/write_led.sh'),str(num),str(val)])
def delay_state(self): """Sets theState object's delaying value to 0, and then calls :meth:`on_state_change`. Used by :meth:`delay` for timing end of a delay """ if debug == True: FileIO.log('smoothie_ser2net.delay_state called') self.theState['delaying'] = 0 self.on_state_change(self.theState)
def on_connect(self, theState): """Callback when connection made currently does zilch """ if debug == True: FileIO.log('smoothie_ser2net.on_connect called')
def create_deck(self, new_deck): """Create a dictionary of new container names to be stored in each pipette given a deck list Calls :meth:`head.save_pipette_values` right before returning dictionary :returns: container data for each axis :rtype: dictionary """ if debug == True: FileIO.log('head.create_deck called') if verbose == True: FileIO.log('\tnewDeck:\n\n', new_deck,'\n') #doesn't map to smoothieAPI nameArray = [] for containerName in new_deck : nameArray.append(containerName) response = {} for n in self.PIPETTES: response[n] = self.PIPETTES[n].create_deck(nameArray) self.save_pipette_values() return response
def connect(self): """Make a connection to Smoothieboard using :class:`CB_Factory` """ if debug == True: FileIO.log('smoothie_ser2net.connect called') self.my_loop = asyncio.get_event_loop() callbacker = self.CB_Factory(self) asyncio.async(self.my_loop.create_connection(lambda: callbacker, host='0.0.0.0', port=3333))
def calibrate_container(self, pipette, container): """Set the location of a container """ if debug == True: FileIO.log('head.calibrate_container called') if pipette and self.PIPETTES[pipette]: state = self.smoothieAPI.get_state() self.PIPETTES[pipette].calibrate_container(container,state)
def save_pipette_values(self): """Save pipette values to otone_data/pipette_values.json """ if debug == True: FileIO.log('head.save_pipette_values called') pipette_values = {} for axis in self.PIPETTES: pipette_values[axis] = {} for k, v in self.PIPETTES[axis].__dict__.items(): pipette_values[axis][k] = v # should include: # 'top' # 'bottom' # 'blowout' # 'droptip' # 'volume' # 'theContainers' filetext = json.dumps(pipette_values,sort_keys=True,indent=4,separators=(',',': ')) if debug == True: FileIO.log('filetext: ', filetext) filename = os.path.join(self.dir_par_par_path,'otone_data/pipette_calibrations.json') # save the pipette's values to a local file, to be loaded when the server restarts FileIO.writeFile(filename,filetext,lambda: FileIO.onError('\t\tError saving the file:\r\r'))
def get_state(self): """Get state information from Smoothieboard """ if debug == True: FileIO.log('head.get_state called') #maps to smoothieAPI.get_state() #function get_state () return self.smoothieAPI.get_state()
def reset(self): """Reset the Smoothieboard and clear theQueue object (:class:`the_queue`) """ if debug == True: FileIO.log('head.reset called') #maps to smoothieAPI.reset() with extra code self.smoothieAPI.reset() self.theQueue.clear();
def raw(self, string): """Send a raw command to the Smoothieboard """ if debug == True: FileIO.log('head.raw called') #maps to smoothieAPI.raw() #function raw(string) self.smoothieAPI.raw(string)
def __init__(self, tools, publisher): """Initialize Head object tools = dictionary of the tools on the head """ if debug == True: FileIO.log('head.__init__ called') self.smoothieAPI = openSmoothie.Smoothie(self) self.PIPETTES = {'a':Pipette('a'),'b':Pipette('b')} #need to create this dict in head setup self.tools = tools self.pubber = publisher self.smoothieAPI.set_raw_callback(self.pubber.on_raw_data) self.smoothieAPI.set_position_callback(self.pubber.on_position_data) self.smoothieAPI.set_limit_hit_callback(self.pubber.on_limit_hit) self.smoothieAPI.set_move_callback(self.pubber.on_start) self.smoothieAPI.set_delay_callback(self.pubber.show_delay) self.theQueue = TheQueue(self, publisher) #connect with the smoothie board self.smoothieAPI.connect() self.path = os.path.abspath(__file__) self.dir_path = os.path.dirname(self.path) self.dir_par_path = os.path.dirname(self.dir_path) self.dir_par_par_path = os.path.dirname(self.dir_par_path) self.load_pipette_values()
def on_state_change(self, state): """Check the given state (from Smoothieboard) and engage :obj:`theQueue` (:class:`the_queue`) accordingly If the state is 1 or the state.delaying is 1 then :obj:`theQueue` is_busy, else if the state is 0 and the state.delaying is 0, :obj:`theQueue` is not busy, clear the currentCommand for the next one, and if not paused, tell :obj:`theQueue` to step. Then update :obj:`theState`. :todo: :obj:`theState` should be updated BEFORE the actions taken from given state """ if debug == True: FileIO.log('head.on_state_change called') if state['stat'] == 1 or state['delaying'] == 1: self.theQueue.is_busy = True elif state['stat'] == 0 and state['delaying'] == 0: self.theQueue.is_busy = False self.theQueue.currentCommand = None if self.theQueue.paused==False: self.theQueue.step(False) self.theState = state if debug == True and verbose == True: FileIO.log('\n\n\tHead state:\n\n',self.theState,'\n')
def end_sequence(self): """Returns the end pipetting sequence when running pipette command - currently an empty dictionary """ if debug == True: FileIO.log('pipette.end_sequence called') oneCommand = {} return [oneCommand]
def publish_calibrations(self): """Publish calibrations data """ if debug == True: FileIO.log('head.publish_calibrations called') self.pubber.send_message('containerLocations',self.get_deck()) self.pubber.send_message('pipetteValues',self.get_pipettes())
def try_add(self, cmd): """Add a command to the smoothieQueue """ FileIO.log('smoothie_ser2net.try_add called') self.smoothieQueue.append(cmd) #if len(self.smoothieQueue) == 1: self.try_step()
def resume_job(self): """Call :meth:`resume`... redundant, consider removing """ if debug == True: FileIO.log('the_queue.resume_job called') #doesn't map to smoothieAPI #function resumeJob() self.resume()
def reset(self): """Reset robot """ if debug == True: FileIO.log('smoothie_ser2net.reset called') if self.my_transport is not None: resetString = _dict['reset'] self.send(self, resetString)
def __init__(self, axis): """Initialize Pipette toolname = the name of the tool (string) tooltype = the type of tool e.g. 1ch pipette, 8ch pipette, etc.(string) axis = position of tool on head & associated motor (A, B, C, etc) (string) offset = the offset in space from the A tool which is defined to have offset = (0,0,0) """ if debug == True: FileIO.log('pipette.__init__ called') toolname = axis + '_pipette' super().__init__(toolname, 'pipette', axis) #default parameters to start with self.resting = 0 #rest position of plunger when not engaged self.top = 0 #just touching the plunger (saved) self.bottom = 1 #calculated by pipette object (saved) self.blowout = 2 #value saved 2-5 mm before droptip (saved) self.droptip = 4 #complete max position of plunger (saved) #the calibrated plate offsets for this pipette self.volume = 200 #max volume pipette can hold calculated during calibration (saved in pipette_calibrations.json) self.bottom_distance = 2 #distance between blowout and botton self.theContainers = {} #(saved) self.tip_racks = [] self.trash_container = [] self.tip_rack_origin = ""
def erase_job(self): """Erase the ProtocolRunner job """ if debug == True: FileIO.log('instruction_queue.erase_job called') self.head.erase_job() self.isRunning = False; self.instructionArray = []
def run_program(self,progName,ctrl=None,lid=None,vesselType=None,vesselVol=None): """run a named program in the cycler's files Name must be in quotes specify control method BLOCK, PROBE, or CALC. CALC by default specify heated lid on or off. On by default specify vessel type ('"Tubes"' or '"Plate"') and volume (10-100) Returns True if successful, False if not """ # first check if program exists and exit program if not if not self.find_program(progName): print("Program does not exist") return False # cancel other programs if self.check_busy(): self.cancel() # make sure lid is closed self.close_lid() # control method is CALC by default, lid ON by default ctrl = ctrl or self.ctrl lid = lid or self.lid # set temp calc algorithm parameters self.set_calc(vesselType,vesselVol) # send run command to cycler sendStr = self.format_input(['RUN '+progName,ctrl,lid],',') if debug == True: FileIO.log('cycler.py sending {0}'.format(sendStr)) self.send(sendStr) return True
def home(self, data): """Intermediate step to start a homing sequence """ if debug == True: FileIO.log('subscriber.home called') self.runner.insQueue.infinity_data = None self.runner.insQueue.erase_job() self.head.home(data)
def start_infinity_job(self, infinity_instructions): """Start a job and save instructions to a variable (infinity_data) so they can be perpetually run with :meth:`start_job` """ if debug == True: FileIO.log('instruction_queue.start_infinity_job called') if infinity_instructions and len(infinity_instructions): self.infinity_data = json.dumps(infinity_instructions,sort_keys=True,indent=4,separators=(',',': ')) self.start_job(infinity_instructions, True)
def move_plunger(self, data): """Tell the :class:`head` to move a :class:`pipette` to given location(s) """ if debug == True: FileIO.log('subscriber.move_plunger called') if verbose == True: FileIO.log('\ndata:\n\t',data,'\n') self.head.move_plunger(data['axis'], data['locations'])
def move_pipette(self, data): """Tell the :class:`head` to move a :class:`pipette` """ if debug == True: FileIO.log('subscriber.move_pipette called') axis = data['axis'] property_ = data['property'] self.head.move_pipette(axis, property_)
def onJoin(self, details): """Callback fired when WAMP session has been established. May return a Deferred/Future. Starts instatiation of robot objects by calling :meth:`otone_client.instantiate_objects`. """ if debug == True: FileIO.log('otone_client : WampComponent.onJoin called') if not self.factory._myAppSession: self.factory._myAppSession = self crossbar_status = True instantiate_objects() def set_client_status(status): if debug == True: FileIO.log('otone_client : WampComponent.set_client_status called') global client_status client_status = status self.publish('com.opentrons.robot_ready',True) FileIO.log('about to publish com.opentrons.robot_ready TRUE') self.publish('com.opentrons.robot_ready',True) yield from self.subscribe(set_client_status, 'com.opentrons.browser_ready') yield from self.subscribe(subscriber.dispatch_message, 'com.opentrons.browser_to_robot')
def instructions(self, data): """Intermediate step to have :class:`prtocol_runner` and :class:`the_queue` start running a protocol """ if debug == True: FileIO.log('subscriber.instructions called') if verbose == True: FileIO.log('\targs: ', data,'\n') if data and len(data): self.runner.insQueue.start_job (data, True)
def set_slot(self, slot): """Set a new slot of a DeckModule on the deck slot = an integer between 1 and 15 to indicate the position of this deckModule """ if debug == True: FileIO.log('deck_module.set_slot called') self.slot = slot