NOTES:

  • Waiting vs blocking
    --> blocking holds up everything (could be selective?)
    --> waiting for specific resources to reach inactive state (flags?)

  • Platemap vs positionmap

  • Axes orientation

TODO:

  • tip touch
  • get motor current position
  • tip touch
  • calibration
  • initialization reference
  • GUI
  • pyVISA

In [1]:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from IPython.display import display
import ipywidgets as widgets
from __future__ import division

In [2]:
%matplotlib notebook

Motor

Lin Engineering
http://www.linengineering.com/wp-content/uploads/downloads/Silverpak_17C/documentation/Lin_Command_Manual.pdf

  1. Determine appropriate velocity_max = microsteps/sec
  2. Determine motor limits
  3. Determine conv = microsteps/mm
  4. Determine orientation (P+; D-)

In [ ]:
import serial as s
import time
import yaml

# TODO: get current position for relative move
class Motor:
    def __init__(self, config_file, init=True):
        self.serial = s.Serial() # placeholder

        f = open(config_file, 'r')
        self.config = yaml.load(f)
        f.close()
        
        if init:
            self.initialize()

    def initialize(self):
        self.serial = s.Serial(**self.config['serial']) # open serial connection
        
        # TODO set moving current
        # TODO set holding current
        self.set_velocity(self.config['velocity_limit'])  # set velocity
        self.home() # move motor to home
        
    def cmd(self, cmd_string, block=True):
        full_string = self.config['prefix'] + cmd_string + self.config['terminator']
        self.serial.write(full_string)
        
        time.sleep(0.15) # TODO: monitor for response?
        response = self.serial.read(self.serial.inWaiting()).decode('utf8', 'ignore')
        
        while block and self.is_busy():
            pass
           
        return response
    
    def is_busy(self):
        cmd_string = 'Q'
        time.sleep(0.05)
        response = self.cmd(cmd_string, False)
        return response.rfind('`') == -1
    
    
    # velocity: (usteps/sec) 
    def set_velocity(self, velocity):
        if velocity > self.config['velocity_limit']:
            velocity = self.config['velocity_limit']
            print 'ERR: Desired velocity exceeds velocity_limit; velocity now set to velocity_limit'
            
        cmd_string = 'V{}R'.format(velocity)
        return self.cmd(cmd_string)
       
    def halt(self):
        cmd_string = 'T'
        self.cmd(cmd_string)
    
    def home(self):
        cmd_string = 'Z{}R'.format(self.config['ustep_max'])
        return self.cmd(cmd_string)
    
    def move(self, mm, block=True):
        ustep = int(self.config['conv']*mm)
        if ustep > self.config['ustep_max']:
            ustep = self.config['ustep_max']
            print 'ERR: Desired move to {} mm exceeds max of {} mm; moving to max instead'.format(mm, self.config['ustep_max']/self.config['conv'])
        if ustep < self.config['ustep_min']:
            ustep = self.config['ustep_min']
            print 'ERR: Desired move to {} mm exceeds min of {} mm; moving to min instead'.format(mm, self.config['ustep_min']/self.config['conv'])
            
        cmd_string = 'A{}R'.format(ustep)
        
        return self.cmd(cmd_string, block)
        
    def move_relative(self, mm):
        ustep = int(self.config['conv']*mm)
        ustep_current = int(self.config['ustep_max']/2)  # TODO: limit movement (+ and -)
        
        if mm >= 0:
            if (ustep_current + ustep) > self.config['ustep_max']:
                ustep = self.config['ustep_max'] - ustep_current
                print 'ERR: Desired move of +{} mm exceeds max of {} mm; moving to max instead'.format(mm, self.config['ustep_max']/self.config['conv'])
            cmd_string = 'P{}R'.format(ustep)
        
        else:
            if (ustep_current + ustep) < self.config['ustep_min']:
                ustep = self.config['ustep_min'] - ustep_current
                print 'ERR: Desired move of {} mm exceeds min of {} mm; moving to min instead'.format(mm, self.config['ustep_min']/self.config['conv'])
            ustep = -1*ustep
            cmd_string = 'D{}R'.format(ustep)
        
        return self.cmd(cmd_string)
    
    def where(self):
        cmd_string = '?0'
        ustep = self.cmd(cmd_string)
        retrun float(ustep)/self.config['conv']
    
    def exit(self):
        self.serial.close()

In [ ]:
m = Motor('config/le_motor.yaml')

In [ ]:
m.serial.write('/1Q\r')
time.sleep(0.5)
m.serial.read(m.serial.inWaiting())

In [ ]:
m.cmd('Z1000R')

In [ ]:
print m.move(32)
time.sleep(1)
print m.move(20)

In [ ]:
print m.cmd('P100000D100000P100000D100000P100000D100000P100000D100000R')
print m.cmd('/1?0')

In [ ]:
m.exit()

ASI Controller

Applied Scientific Instrumentation
http://www.asiimaging.com/downloads/manuals/Operations_and_Programming_Manual.pdf

  1. Set hall effect sensors to appropriate limits
  2. Determine orientation (X+-, Y+-)

In [ ]:
import serial as s
import time
import yaml

# TODO: Fix serial.read encoding
class ASI_Controller:
    def __init__(self, config_file, init=True):
        self.serial = s.Serial() # placeholder
        
        f = open(config_file, 'r')
        self.config = yaml.load(f)
        f.close()
        
        if init:
            self.initialize()

    def initialize(self):
        self.serial = s.Serial(**self.config['serial']) # open serial connection

        self.cmd_xy('mc x+ y+')  # enable motor control for xy
        self.cmd_z('mc z+') # enable motor control for z

        print "Initializing stage..."
        self.move_xy(2000, -2000)  # move to switch limits (bottom right)
        self.r_xy(-0.5, 0.5)  # move from switch limits 0.5 mm


    def cmd(self, cmd_string):
        full_string = self.config['prefix'] + cmd_string + self.config['terminator']
        self.serial.write(full_string)
        time.sleep(0.05)
        response = self.serial.read(self.serial.inWaiting())
        return response
    
    def halt(self):
        self.halt_xy()
        self.halt_z()
    
    
    # XY ----------------------------------------------
    def cmd_xy(self, cmd_string, block=True):
        full_string = '2h ' + cmd_string
        response = self.cmd(full_string)
        
        while block and self.is_busy_xy():
            time.sleep(0.05)
            pass
         
        return response
    
    def is_busy_xy(self):
        status = self.cmd('2h STATUS')[0]
        return status == 'B'

    def halt_xy(self):
        self.cmd_xy('HALT', False)
    
    def where_xy(self):
        response = self.cmd_xy('WHERE X Y')
        if response.find('A'):
            pos_xy = response.split()[1:3]
            pos_x = float(pos_xy[0])
            pos_y = float(pos_xy[1])
            return pos_x, pos_y
        else:
            return None, None 

    def move_xy(self, x_mm, y_mm):
        conv = self.config['conv']
        xStr = 'x=' + str(float(x_mm) * conv)
        yStr = 'y=' + str(float(y_mm) * conv)
        return self.cmd_xy(' '.join(['m', xStr, yStr]))
    
    def r_xy(self, x_mm, y_mm):
        conv = self.config['conv']
        xStr = 'x=' + str(float(x_mm) * conv)
        yStr = 'y=' + str(float(y_mm) * conv)
        return self.cmd_xy(' '.join(['r', xStr, yStr]))

    # Z -----------------------------------------------
    def cmd_z(self, cmd_string, block=True):
        while block and self.is_busy_z():
            time.sleep(0.3)
        full_string = '1h ' + cmd_string
        return self.cmd(full_string)
    
    def is_busy_z(self):
        status = self.cmd('1h STATUS')
        return status[0] == 'B'

    def halt_z(self):
        self.cmd_z('HALT', False)
        
    def where_z(self):
        response = self.cmd_z('WHERE Z')
        if response.find('A'):
            pos_z = float(response.split()[1:2])
            return pos_z
        else:
            return None    
    
    def move_z(self, z_mm):
        conv = self.config['conv']
        zStr = 'z=' + str(float(z_mm) * conv)
        return self.cmd_z(' '.join(['m', zStr]))   
    
    def r_z(self, z_mm):
        conv = self.config['conv']
        zStr = 'z=' + str(float(z_mm) * conv)
        return self.cmd_z(' '.join(['r', zStr]))
    
    def exit(self):
        self.serial.close()

In [ ]:
a = ASI_Controller('config/asi_controller.yaml')

In [ ]:
a.exit()

Autosipper


In [ ]:
from utils import lookup, read_delim_pd
import numpy as np

class Autosipper:
    def __init__(self, z, xy):
        self.Z = z    # must be initialized first!
        self.XY = xy
        
        while True:
            fp = raw_input('Type in plate map file:')
            try:
                self.load_platemap(fp)  # load platemap
                break
            except IOError:
                print 'No file', fp

        raw_input('Place dropper above reference (press enter when done)')
        self.XY.cmd_xy('here x y')  # establish current position as 0,0
    
    def load_platemap(self, filepath):
        self.platemap = read_delim_pd(filepath)

    def go_to(self, columns, values):
        x1,y1,z1 = np.array(lookup(self.platemap, columns, values)[['x','y','z']])[0]

        self.Z.home()          # move needle to travel height (blocking)
        self.XY.move_xy(x1,y1) # move stage (blocking)
        self.Z.move(z1)        # move needle to bottom of well (blocking)
        
    def where(self):
        pos_x, pos_y = XY.where_xy()
        pos_z = Z.where()
        return pos_x, pos_y, pos_z
        
    
    def exit(self):
        self.XY.exit()
        self.Z.exit()

In [ ]:
d = Autosipper(Motor('config/le_motor.yaml'), ASI_Controller('config/asi_controller.yaml'))

In [ ]:
d.platemap

In [ ]:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(d.platemap['x'], d.platemap['y'], d.platemap['z'], s=5)
plt.show()

In [ ]:
d.Z.home

In [ ]:
d.XY.r_xy(0,5)

In [ ]:
d.go_to(['name'],'A12')

In [ ]:
d.exit()

Communication: PyVISA


In [ ]:
import visa

In [ ]:
rm = visa.ResourceManager()

In [ ]:
rm.list_resources()

In [ ]:
rm.list_resources_info()

Documentation: Sphinx