예제 #1
0
 def setUp(self):
     self.deferred = Deferred()
     self.doneOrder = []
     self.failOrder = []
     self.cmdQueue = CommandQueue(
         killFunc=self.killFunc,
         priorityDict = cmdPriorityDict
     )
     self.commandsLeft = False
예제 #2
0
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a ScaleDevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of scaling ring controller
        @param[in] port  port of scaling ring controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        # the mitutoyos will be zeroed when the scaling ring is moved to 20
        # so this zero point keep track fo the offset.
        # if I could preset the mitutoyo's this would be unnecessary
        # the preset command "CP**" doesn't seem to work.
        self.zeroPoint = 20.0 # mm.  Position where scale = 1
        self.encPos = [None]*6

        self.devCmdQueue = CommandQueue({})

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )
예제 #3
0
 def setupCmdQueue(self):
     cmdQueue = CommandQueue(
         priorityDict = {
             "init" : CommandQueue.Immediate,
             # all other commands have an equal (default) priority
         }
     )
     return cmdQueue
예제 #4
0
    def __init__(self, name, host, port, measScaleDev=None, nomSpeed=NOM_SPEED, callFunc=None):
        """!Construct a ScaleDevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of scaling ring controller
        @param[in] port  port of scaling ring controller
        @param[in] measScaleDev  instance of a MeasScaleDev (access to the mitutoyos for servoing)
        @param[in] nom_speed nominal speed at which to move (this can be modified via the speed command)
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.targetPos = None
        # holds a userCommand for "move"
        # set done only when move has reached maxIter
        # or is within tolerance
        self.iter = 0
        self.moveUserCmd = expandUserCmd(None)
        self.moveUserCmd
        self.nomSpeed = nomSpeed
        self.measScaleDev = measScaleDev
        self.status = Status()

        # all commands of equal priority
        # except stop kills a running (or pending move) move
        # priorityDict = {"stop": CommandQueue.Immediate}
        priorityDict = {
            "stop":1,
            "status":1,
            "move":1,
            "speed":1,
        }
        self.devCmdQueue = CommandQueue(
            priorityDict,
            killFunc = self.killFunc,
            )
        # stop will kill a running move
        # else everything queues with equal prioirty
        self.devCmdQueue.addRule(CommandQueue.KillRunning, ["stop"], ["move"])

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )
예제 #5
0
파일: tcsDevice.py 프로젝트: csayres/lcoTCC
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a LCODevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of tcs controller
        @param[in] port  port of tcs controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.status = Status()
        self._statusTimer = Timer()

        self.waitRotCmd = UserCmd()
        self.waitRotCmd.setState(self.waitRotCmd.Done)
        self.waitRotTimer = Timer()

        # self.waitFocusCmd = UserCmd()
        # self.waitFocusCmd.setState(self.waitFocusCmd.Done)

        self.waitOffsetCmd = UserCmd()
        self.waitOffsetCmd.setState(self.waitOffsetCmd.Done)
        self.waitOffsetTimer = Timer()
        self.rotDelay = False

        self.devCmdQueue = CommandQueue({}) # all commands of equal priority

        self.lastGuideRotApplied = None

        self.doGuideRot = True

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )
예제 #6
0
파일: m2Device.py 프로젝트: csayres/lcoTCC
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a LCODevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of m2 controller
        @param[in] port  port of m2 controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.status = Status()
        self._statusTimer = Timer()
        self.waitMoveCmd = UserCmd()
        self.waitMoveCmd.setState(self.waitMoveCmd.Done)
        # self.waitGalilCmd = UserCmd()
        # self.waitGalilCmd.setState(self.waitGalilCmd.Done)
        # give status commands equal priority so they don't kill eachother
        priorityDict = {
            "status": 1,
            "speed": 1,
            "stop": 1,
            "move": 1,
            "galil": 1,
            "offset": 1,
        }

        self.devCmdQueue = CommandQueue(priorityDict) # all commands of equal priority

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )
예제 #7
0
class CmdQueueTest(unittest.TestCase):
    """Unit test for CommandQueue
    """
    def setUp(self):
        self.deferred = Deferred()
        self.doneOrder = []
        self.failOrder = []
        self.cmdQueue = CommandQueue(
            killFunc=self.killFunc,
            priorityDict = cmdPriorityDict
        )
        self.commandsLeft = False

    def tearDown(self):
        self.deferred = None
        self.doneOrder = []
        self.failOrder = []
        self.cmdQueue = None

    def addCmdsToQueue(self, cmdStrList, timeStep=0.02):
        # add commands to the queue every timeStep seconds
        cmdStrListCopy = cmdStrList[:]
        self.commandsLeft = True
        def addInOrder():
            if not cmdStrListCopy:
                # all commands have been added to the queue
                self.commandsLeft = False
                return
            else:
                cmdStr = cmdStrListCopy.pop(0)
                self.cmdQueue.addCmd(self.makeCmd(cmdStr), nullCallFunc)
                # print 'adding', cmdStr
                # print 'command queue!', [x.cmdVerb for x in self.cmdQueue.cmdQueue]
                Timer(timeStep, addInOrder)
        addInOrder()

    def addToQueue(self, cmdStr):
        self.cmdQueue.addCmd(self.makeCmd(cmdStr), nullCallFunc)
        # Timer(0., self.cmdQueue.addCmd, self.makeCmd(cmdStr), nullCallFunc)

    def cmdCallback(self, cmd):
        # allow commands to run for 0.15 seconds before killing
       # print cmd.cmdVerb, cmd.state
       # print 'q: ', [x.cmd.cmdVerb for x in self.cmdQueue.cmdQueue]
        if cmd.isDone:
            # print 'cmd %s finished with state %s and txtMsg=%s'%(cmd.cmdVerb, cmd.state, cmd.textMsg)
            if cmd.didFail:
                self.failOrder.append(cmd.cmdVerb)
            else:
                self.doneOrder.append(cmd.cmdVerb)
        elif cmd.isActive:
            Timer(0.15, self.setDone, cmd)
        if (not self.cmdQueue.cmdQueue) and (self.cmdQueue.currExeCmd.cmd.isDone) and not self.commandsLeft: # must be last command and its done
            # the queue is empty and last command is done, end the test
            self.deferred.callback('go')

    def killFunc(self, killMe, killedBy):
        killMe.setState(killMe.Cancelled)

    def setDone(self, cmd):
        if not cmd.isDone:
            cmd.setState(cmd.Done)

    def makeCmd(self, cmd):
        newCmd = UserCmd(userID = 0, cmdStr = cmd)
        newCmd.cmdVerb = cmd
        newCmd.addCallback(self.cmdCallback)
        return newCmd

    def testNoRules(self):
        cmdsIn = ['hia', 'hib', 'meda', 'medb', 'lowa', 'lowb']
        def checkResults(cb):
            self.assertEqual(cmdsIn, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testPrioritySort(self):
        cmdsIn = ['hia', 'meda', 'lowa', 'lowb', 'medb', 'hib']
        cmdsOut = ['hia', 'hib', 'meda', 'medb', 'lowa', 'lowb']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testLowPriorityFirstIn(self):
        cmdsIn = ['lowb', 'meda', 'lowa']
        def checkResults(cb):
            self.assertEqual(cmdsIn, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testImmediate(self):
        cmdsIn = ['hia', 'meda', 'lowa', 'lowb', 'medb', 'hib', 'killa']
        cmdsOut = ['killa']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testDoubleImmediate(self):
        cmdsIn = ['hia', 'meda', 'lowa', 'lowb', 'medb', 'hib', 'killa', 'killb']
        cmdsOut = ['killb']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testKillRule(self):
        cmdsIn = ['meda', 'medb']
        cmdsOut = ['medb']
        self.cmdQueue.addRule(
            action = CommandQueue.KillRunning,
            newCmds = ['medb'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testSupersede(self):
        cmdsIn = ['hia', 'hib', 'meda', 'medb', 'meda']
        cmdsOut = ['hia', 'hib', 'medb', 'meda']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelQueued,
            newCmds = ['meda'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testQueueSame(self):
        cmdsIn = ['hia', 'hia']
        cmdsOut = ['hia', 'hia']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelNewRule(self):
        cmdsIn = ['meda', 'hib', 'meda', 'medb']
        cmdsOut = ['meda', 'hib', 'meda']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = ['medb'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelQueuedRule(self):
        cmdsIn = ['meda', 'hib', 'meda', 'medb']
        cmdsOut = ['meda', 'hib', 'medb']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelQueued,
            newCmds = ['medb'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testBadRule(self):
        try:
            self.cmdQueue.addRule(
                action = 'badRule',
                newCmds = ['medb'],
                queuedCmds = ['meda'],
            )
        except RuntimeError:
            self.assertTrue(True)
        else:
            self.assertTrue(False)

    def testRuleUnrecognizedCmd(self):
        try:
            self.cmdQueue.addRule(
                action = CommandQueue.CancelNew,
                newCmds = ['randomCmd'],
                queuedCmds = ['meda'],
            )
        except RuntimeError:
            self.assertTrue(True)
        else:
            self.assertTrue(False)

    def testStartUnrecognizedCmd(self):
        self.addToQueue('randomCmd')
        def checkResults(cb):
            self.assertEqual(['randomCmd'], self.doneOrder)
        self.deferred.addCallback(checkResults)
        return self.deferred

    def testUnrecognizedCmdSort(self):
        cmdsIn = ['hia', 'randomCmd', 'meda', 'lowa', 'lowb', 'medb', 'hib']
        cmdsOut = ['hia', 'hib', 'meda', 'medb', 'lowa', 'lowb', 'randomCmd']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelAllQueuedRule(self):
        cmdsIn = ['meda', 'hib', 'meda', 'medb']
        cmdsOut = ['meda', 'medb']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelQueued,
            newCmds = ['medb'],
            queuedCmds = 'all',
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelNewRuleRunning(self):
        cmdsIn = ['meda', 'medb']
        cmdsOut = ['meda']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = ['medb'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelAllNewRule(self):
        cmdsIn = ['meda', 'medb', 'randomCmd', 'hib', 'meda', 'medb']
        cmdsOut = ['meda']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = 'all',
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelAllQueuedRule(self):
        cmdsIn = ['meda', 'medb', 'randomCmd', 'hib', 'meda', 'medb']
        cmdsOut = ['meda', 'medb']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelQueued,
            newCmds = ['medb'],
            queuedCmds = 'all',
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testCancelAllQueuedAndCancelNewRule(self):
        cmdsIn = ['hia', 'medb', 'randomCmd', 'hib', 'meda', 'medb']
        cmdsOut = ['hia', 'hib', 'medb', 'meda', 'randomCmd']
        self.cmdQueue.addRule(
            action = CommandQueue.CancelQueued,
            newCmds = ['medb'],
            queuedCmds = 'all',
        )
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = ['medb'],
            queuedCmds = ['meda'],
        )
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        # for cmd in cmdsIn:
        #     self.addToQueue(cmd)
        return self.deferred

    def testOverDefinition(self):
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = ['medb'],
            queuedCmds = 'all',
        )
        try:
            self.cmdQueue.addRule(
                action = CommandQueue.KillRunning,
                newCmds = 'all',
                queuedCmds = ['medb'],
            )
        except RuntimeError as e:
            print e
            self.assertTrue(True)
        else:
            self.assertTrue(False)

    def testAllowedDoubleAllDefinition(self):
        self.cmdQueue.addRule(
            action = CommandQueue.CancelNew,
            newCmds = ['medb'],
            queuedCmds = 'all',
        )
        try:
            self.cmdQueue.addRule(
                action = CommandQueue.CancelNew,
                newCmds = 'all',
                queuedCmds = ['medb'],
            )
        except RuntimeError as e:
            print e
            self.assertTrue(False)
        else:
            self.assertTrue(True)

    def testCancelAllNewRule(self):
        cmdsIn = ['meda', 'randx', 'randx']
        cmdsOut = ['meda', 'randx']
        def checkResults(cb):
            self.assertEqual(cmdsOut, self.doneOrder)
        self.deferred.addCallback(checkResults)
        self.addCmdsToQueue(cmdsIn)
        return self.deferred
예제 #8
0
파일: tcsDevice.py 프로젝트: csayres/lcoTCC
class TCSDevice(TCPDevice):
    """!A Device for communicating with the LCO TCS."""
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a LCODevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of tcs controller
        @param[in] port  port of tcs controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.status = Status()
        self._statusTimer = Timer()

        self.waitRotCmd = UserCmd()
        self.waitRotCmd.setState(self.waitRotCmd.Done)
        self.waitRotTimer = Timer()

        # self.waitFocusCmd = UserCmd()
        # self.waitFocusCmd.setState(self.waitFocusCmd.Done)

        self.waitOffsetCmd = UserCmd()
        self.waitOffsetCmd.setState(self.waitOffsetCmd.Done)
        self.waitOffsetTimer = Timer()
        self.rotDelay = False

        self.devCmdQueue = CommandQueue({}) # all commands of equal priority

        self.lastGuideRotApplied = None

        self.doGuideRot = True

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )

    @property
    def currExeDevCmd(self):
        return self.devCmdQueue.currExeCmd.cmd

    @property
    def currDevCmdStr(self):
        return self.currExeDevCmd.cmdStr

    # @property
    # def atFocus(self):
    #     if self.status.statusFieldDict["focus"].value and abs(self.targFocus - self.status.statusFieldDict["focus"].value) < FocusPosTol:
    #         return True
    #     else:
    #         return False

    @property
    def isTracking(self):
        if not self.waitOffsetCmd.isDone:
            # offsets do not trigger tcs tracking state, so we fake it here
            return False
        return self.status.statusFieldDict["state"].value == Tracking

    @property
    def isSlewing(self):
        if not self.waitOffsetCmd.isDone or not self.status.isClamped:
            # if clamp is not on, then we are moving the rotator
            return True
        else:
            return False

    def init(self, userCmd=None, timeLim=None, getStatus=True):
        """Called automatically on startup after the connection is established.
        Only thing to do is query for status or connect if not connected
        """
        log.info("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        # print("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        userCmd = expandUserCmd(userCmd)
        # if not self.isConnected:
        #     # time lim handled by lco.deviceCmd
        #     return self.connect(userCmd=userCmd)
        if getStatus:
            return self.getStatus(userCmd=userCmd)
        else:
            userCmd.setState(userCmd.Done)
            return userCmd

    def getStatus(self, userCmd=None):
        """Return current telescope status. Continuously poll.
        """
        log.info("%s.getStatus(userCmd=%s)" % (self, userCmd)) # logging this will flood the log
        userCmd = expandUserCmd(userCmd)
        if not self.conn.isConnected:
            userCmd.setState(userCmd.Failed, "Not Connected to TCS")
            return userCmd
        self._statusTimer.cancel() # incase a status is pending
        userCmd = expandUserCmd(userCmd)
        userCmd.addCallback(self._statusCallback)
        # record the present RA, DEC (for determining when offsets are done)
        # self.status.previousRA = self.status.statusFieldDict["ra"].value
        # self.status.previousDec = self.status.statusFieldDict["dec"].value
        if self.status.statusFieldDict["mpos"].value is None:
            self.status.previousRA, self.status.previousDec = None, None
        else:
            self.status.previousRA = self.status.statusFieldDict["mpos"].value[0]
            self.status.previousDec = self.status.statusFieldDict["mpos"].value[1]
        # gather list of status elements to get
        devCmdList = [DevCmd(cmdStr=cmdVerb) for cmdVerb in self.status.statusFieldDict.keys()]
        LinkCommands(userCmd, devCmdList)
        for devCmd in devCmdList:
            self.queueDevCmd(devCmd)
        if not self.status.isClamped or self.rotDelay:
            # rotator is moving, get status frequently
            pollTime = PollTimeRot
        elif self.isSlewing:
            # slewing, get status kinda frequently
            pollTime = PollTimeSlew
        elif self.isTracking:
            # tracking, get status less frequently
            pollTime = PollTimeTrack
        else:
            # idle, get status infrequently (as things shouldn't be changing fast)
            pollTime = PollTimeIdle
        self._statusTimer.start(pollTime, self.getStatus)
        return userCmd

    def _statusCallback(self, cmd):
        """! When status command is complete, send info to users, and check if any
        wait commands need to be set done
        """
        # print("tcs status callback", cmd)
        if cmd.isDone and not cmd.didFail:
            # do we want status output so frequently? probabaly not.
            # perhaps only write status if it has changed...
            statusStr = self.status.getStatusStr()
            if statusStr:
                self.writeToUsers("i", statusStr, cmd)

            if self.waitOffsetCmd.isActive and not True in self.status.axesSlewing():
                self.waitOffsetCmd.setState(self.waitOffsetCmd.Done)

            if self.waitRotCmd.isActive and not self.rotDelay and self.status.isClamped: #not self.status.rotMoving: #and self.status.rotOnTarget :
                print("set rot command done", self.rotDelay, self.status.isClamped, self.status.rotMoving)
                self.waitRotCmd.setState(self.waitRotCmd.Done)

    def target(self, ra, dec, doHA, userCmd=None):
        """Set coordinates for a slew.

        @param[in] ra: right ascension decimal degrees
        @param[in] dec: declination decimal degrees
        @param[in] doHA: if True, use degrees in hour angle rather than ra.
        @param[in] userCmd: a twistedActor BaseCommand.
        """
        log.info("%s.slew(userCmd=%s, ra=%.2f, dec=%.2f)" % (self, userCmd, ra, dec))
        userCmd = expandUserCmd(userCmd)
        if not self.conn.isConnected:
            userCmd.setState(userCmd.Failed, "Not Connected to TCS")
            return userCmd
        if doHA:
            enterRa = "HAD %.8f"%ra
        else:
            enterRa = "RAD %.8f"%ra
        enterDec = "DECD %.8f"%dec
        enterEpoch = "MP %.2f"%2000 # LCO: HACK should coords always be 2000?
        devCmdList = [DevCmd(cmdStr=cmdStr) for cmdStr in [enterRa, enterDec, enterEpoch]]#, cmdSlew]]
        # set userCmd done only when each device command finishes
        # AND the pending slew is also done.
        # when the last dev cmd is done (the slew), set axis cmd statue to slewing

        LinkCommands(userCmd, devCmdList) #LCO: HACK don't wait for a slew to finish + [self.waitSlewCmd])
        for devCmd in devCmdList:
            self.queueDevCmd(devCmd)

        statusStr = self.status.getStatusStr()
        if statusStr:
            self.writeToUsers("i", statusStr, userCmd)
        return userCmd

    def slewOffset(self, ra, dec, userCmd=None):
        """Offset telescope in right ascension and declination.

        @param[in] ra: right ascension in decimal degrees
        @param[in] dec: declination in decimal degrees
        @param[in] userCmd a twistedActor BaseCommand

        @todo, consolidate similar code with self.target?
        """
        log.info("%s.slewOffset(userCmd=%s, ra=%.6f, dec=%.6f)" % (self, userCmd, ra, dec))
        userCmd = expandUserCmd(userCmd)
        # zero the delta computation so the offset isn't marked done immediately
        self.status.previousDec = ForceSlew
        self.status.previousRA = ForceSlew
        if not self.conn.isConnected:
            userCmd.setState(userCmd.Failed, "Not Connected to TCS")
            return userCmd
        if not self.waitOffsetCmd.isDone:
            self.waitOffsetCmd.setState(self.waitOffsetCmd.Cancelled, "Superseded by new offset")
        waitOffsetCmd = UserCmd()
        self.waitOffsetCmd = waitOffsetCmd
        enterRa = "OFRA %.8f"%(ra*ArcSecPerDeg)
        enterDec = "OFDC %.8f"%(dec*ArcSecPerDeg) #lcohack
        devCmdList = [DevCmd(cmdStr=cmdStr) for cmdStr in [enterRa, enterDec, CMDOFF]]
        # set userCmd done only when each device command finishes
        # AND the pending slew is also done.
        # set an offset done after 6 seconds no matter what
        def setWaitOffsetCmdDone(aWaitingOffsetCmd):
            print("wait offset command state", aWaitingOffsetCmd.state)
            if not aWaitingOffsetCmd.isDone:
                print("Wait offset timed out!!!!")
                self.writeToUsers("w", "Text=OFFSET SET DONE ON TIMER.")
                aWaitingOffsetCmd.setState(aWaitingOffsetCmd.Done, "offset set done on a timer")
        self.waitOffsetTimer.start(8, setWaitOffsetCmdDone, waitOffsetCmd)
        # self.waitOffsetCmd.setTimeLimit(6)
        LinkCommands(userCmd, devCmdList + [self.waitOffsetCmd])
        for devCmd in devCmdList:
            self.queueDevCmd(devCmd)
        statusStr = self.status.getStatusStr()
        if statusStr:
            self.writeToUsers("i", statusStr, userCmd)
        return userCmd

    def rotOffset(self, rot, userCmd=None, force=False):
        """Offset telescope rotator.  USE APGCIR cmd
        which holds current

        @param[in] rot: in decimal degrees
        @param[in] userCmd a twistedActor BaseCommand
        """
        # LCOHACK: allow offsets only if over 5 arcseconds!

        # if True:
        #     #! lupton!
        #     userCmd = expandUserCmd(userCmd)
        #     self.writeToUsers("w", "Rotator offset %.6f bypassed"%rot)
        #     userCmd.setState(userCmd.Done)
        #     return userCmd

        if not self.conn.isConnected:
            userCmd.setState(userCmd.Failed, "Not Connected to TCS")
            return userCmd
        if not self.waitRotCmd.isDone:
            # rotator is unclamped, a move is in progress
            userCmd.setState(userCmd.Failed, "Rotator is unclamped (already moving?)")
            return userCmd
        # if abs(rot) < MinRotOffset and not force:
        if not self.doGuideRot:
            # set command done, rotator offset is miniscule
            self.writeToUsers("w", "Guide rot not enabled, not applying", userCmd)
            userCmd.setState(userCmd.Done)
            return userCmd
        if abs(rot) > MaxRotOffset:
            # set command failed, rotator offset is too big
            self.writeToUsers("w", "Rot offset greater than max threshold", userCmd)
            userCmd.setState(userCmd.Failed, "Rot offset %.4f > %.4f"%(rot, MaxRotOffset))
            return userCmd
        ### print time since last rot applied from guider command
        if not force:
            if self.lastGuideRotApplied is None:
                self.lastGuideRotApplied = time.time()
            else:
                tnow = time.time()
                infoStr = "time since last guide rot update: %.2f"%(tnow-self.lastGuideRotApplied)
                print(infoStr)
                log.info(infoStr)
                self.lastGuideRotApplied = tnow

        # apgcir requires absolute position, calculate it
        # first get status
        newPos = self.status.rotPos - rot
        # rotStart = time.time()
        # def printRotSlewTime(aCmd):
        #     if aCmd.isDone:
        #         rotTime = time.time() - rotStart
        #         print("rot: off, time, speed: %.5f %.5f %5f"%(newPos, rotTime, newPos/rotTime))
        waitRotCmd = UserCmd()
        self.waitRotCmd = waitRotCmd
        # calculate time limit for rot move:
        rotTimeLimBuffer = 2 # check for clamp after 4 seconds
        self.rotDelay = True
        print("setting rot delay true")
        def setRotBufferOff():
            print("setting rot delay false")
            print("rot buffer Off (clamped?)", self.status.isClamped)
            self.rotDelay = False
        self.waitRotTimer.start(rotTimeLimBuffer, setRotBufferOff)
        self.waitOffsetCmd.setTimeLimit(rotTimeLimBuffer + 20)
        self.status.setRotOffsetTarg(rot)
        enterAPGCIR = DevCmd(cmdStr="APGCIR %.8f"%(newPos))
        LinkCommands(userCmd, [enterAPGCIR, self.waitRotCmd])
        # begin the dominos game
        self.queueDevCmd(enterAPGCIR)
        statusStr = self.status.getStatusStr()
        if statusStr:
            self.writeToUsers("i", statusStr, userCmd)
        return userCmd


    def handleReply(self, replyStr):
        """Handle a line of output from the device. Called whenever the device outputs a new line of data.

        @param[in] replyStr   the reply, minus any terminating \n

        Tasks include:
        - Parse the reply
        - Manage the pending commands
        - Output data to users
        - Parse status to update the model parameters
        - If a command has finished, call the appropriate command callback
        """
        log.info("%s read %r, currCmdStr: %s" % (self, replyStr, self.currDevCmdStr))
        replyStr = replyStr.strip()
        if replyStr == "-1":
            # error
            errorStr = "handleReply failed for %s with -1"%self.currDevCmdStr
            if self.waitOffsetCmd.isActive:
                self.waitOffsetCmd.setState(self.waitOffsetCmd.Failed, errorStr)
            if self.waitRotCmd.isActive:
                # note the clamp should still execute!!!!
                self.waitRotCmd.setState(self.waitRotCmd.Failed, errorStr)
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, errorStr)
            return
        statusField = self.status.statusFieldDict.get(self.currDevCmdStr, None)
        if statusField:
            # a piece of status was requested, parse it
            # how to check for error condition? parsing -1 as a float will still work.
            statusField.setValue(replyStr)
            self.currExeDevCmd.setState(self.currExeDevCmd.Done)
        elif replyStr == "0":
            # this was a command, a "0" is expected
            self.currExeDevCmd.setState(self.currExeDevCmd.Done)
        else:
            log.info("unexpected reply" % replyStr)
            #self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Unexpected reply %s for %s"%(replyStr, self.currDevCmdStr))


    def queueDevCmd(self, devCmd):
        """Add a device command to the device command queue

        @param[in] devCmd: a twistedActor DevCmd.
        """
        log.info("%s.queueDevCmd(devCmd=%r, devCmdStr=%r, cmdQueue: %r"%(self, devCmd, devCmd.cmdStr, self.devCmdQueue))
        # append a cmdVerb for the command queue (other wise all get the same cmdVerb and cancel eachother)
        # could change the default behavior in CommandQueue?
        devCmd.cmdVerb = devCmd.cmdStr
        def queueFunc(devCmd):
            # all tcs commands return immediately so set a short timeout
            devCmd.setTimeLimit(SEC_TIMEOUT)
            devCmd.setState(devCmd.Running)
            self.startDevCmd(devCmd.cmdStr)
        self.devCmdQueue.addCmd(devCmd, queueFunc)


    def startDevCmd(self, devCmdStr):
        """
        @param[in] devCmdStr a line of text to send to the device
        """
        devCmdStr = devCmdStr.upper() # lco uses all upper case
        log.info("%s.startDevCmd(%r)" % (self, devCmdStr))
        try:
            if self.conn.isConnected:
                log.info("%s writing %r" % (self, devCmdStr))
                if CMDOFF.upper() == devCmdStr:
                    self.waitOffsetCmd.setState(self.waitOffsetCmd.Running)
                elif "CIR" in devCmdStr:
                    self.waitRotCmd.setState(self.waitRotCmd.Running)
                self.conn.writeLine(devCmdStr)
            else:
                self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Not connected to TCS")
        except Exception as e:
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, textMsg=strFromException(e))
예제 #9
0
class ScaleDevice(TCPDevice):
    """!A Device for communicating with the LCO Scaling ring."""
    validCmdVerbs = ["move", "stop", "status", "speed"]
    def __init__(self, name, host, port, measScaleDev=None, nomSpeed=NOM_SPEED, callFunc=None):
        """!Construct a ScaleDevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of scaling ring controller
        @param[in] port  port of scaling ring controller
        @param[in] measScaleDev  instance of a MeasScaleDev (access to the mitutoyos for servoing)
        @param[in] nom_speed nominal speed at which to move (this can be modified via the speed command)
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.targetPos = None
        # holds a userCommand for "move"
        # set done only when move has reached maxIter
        # or is within tolerance
        self.iter = 0
        self.moveUserCmd = expandUserCmd(None)
        self.moveUserCmd
        self.nomSpeed = nomSpeed
        self.measScaleDev = measScaleDev
        self.status = Status()

        # all commands of equal priority
        # except stop kills a running (or pending move) move
        # priorityDict = {"stop": CommandQueue.Immediate}
        priorityDict = {
            "stop":1,
            "status":1,
            "move":1,
            "speed":1,
        }
        self.devCmdQueue = CommandQueue(
            priorityDict,
            killFunc = self.killFunc,
            )
        # stop will kill a running move
        # else everything queues with equal prioirty
        self.devCmdQueue.addRule(CommandQueue.KillRunning, ["stop"], ["move"])

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )

    def _addMeasScaleDev(self, measScaleDev):
        """Add a way to add a measScaleDev exposfacto,
        really this is only for use with the device wrappers.
        Any real use should specify measScaleDev in __init__
        """
        self.measScaleDev = measScaleDev

    def killFunc(self, doomedCmd, killerCmd):
        doomedCmd.setState(doomedCmd.Failed, "Killed by %s"%(str(killerCmd)))

    @property
    def motorPos(self):
        """Position reported by the motor (Keithly)
        """
        return self.status.position

    @property
    def encPos(self):
        """Average position of the 3 mitutoyo encoders
        """
        return self.measScaleDev.position

    @property
    def scaleZeroPos(self):
        return self.measScaleDev.zeroPoint

    @property
    def currExeDevCmd(self):
        return self.devCmdQueue.currExeCmd.cmd

    @property
    def currDevCmdStr(self):
        return self.currExeDevCmd.cmdStr

    @property
    def isMoving(self):
        return self.moveUserCmd.isActive

    def init(self, userCmd=None, timeLim=None, getStatus=False):
        """Called automatically on startup after the connection is established.
        Only thing to do is query for status or connect if not connected
        """
        log.info("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        userCmd = expandUserCmd(userCmd)
        # stop, set speed, then status?
        stopCmd = self.queueDevCmd("stop", userCmd)
        speedCmd = self.queueDevCmd("speed %.4f"%self.nomSpeed, userCmd)
        statusCmd = self.queueDevCmd("status", userCmd)
        LinkCommands(userCmd, [stopCmd, speedCmd, statusCmd])
        return userCmd
        # if getStatus:
        #     return self.getStatus(userCmd=userCmd)
        # else:
        #     userCmd.setState(userCmd.Done)
        #     return userCmd

    def getStatus(self, userCmd=None, timeLim=None, linkState=True):
        """!Get status of the device.  If the device is
        busy (eg moving), send the cached status
        note that during moves the thread_ring_axis actual_position gets
        periodically output and thus updated in the status
        """

        # note measScaleDevice could be read even if
        # scaling ring is moving.  do this at somepoint?
        userCmd = expandUserCmd(userCmd)
        if timeLim is None:
            timeLim = 2
        if self.isMoving:
            self.writeToUsers("i", "text=showing cached status", userCmd)
            self.writeStatusToUsers(userCmd)
            userCmd.setState(userCmd.Done)
        else:
            # get a completely fresh status from the device
            statusDevCmd = self.queueDevCmd("status", userCmd)
            # get encoder values too
            encStatusDevCmd = self.measScaleDev.getStatus()
            statusDevCmd.addCallback(self._statusCallback)
            statusDevCmd.setTimeLimit(timeLim)
            if linkState:
                LinkCommands(userCmd, [statusDevCmd, encStatusDevCmd])
                return userCmd
            else:
                # return the device command to be linked outside
                return statusDevCmd

    def _statusCallback(self, statusCmd):
        # if statusCmd.isActive:
        #     # not sure this is necessary
        #     # but ensures we get a 100% fresh status
        #     self.status.flushStatus()
        if statusCmd.isDone and not statusCmd.didFail:
            # write the status we have to users
            # if this was a status, write output to users
            # and set the current axis back to the thread ring
            self.status.setThreadAxisCurrent()
            # full status is checked in handleReply, and
            # another status is commanded if the current status
            # is munged
            # statusError = self.status.checkFullStatus()
            # if statusError:
            #     print("status Error")
            #     self.writeToUsers("w", statusError, statusCmd.userCmd)
            self.writeStatusToUsers(statusCmd.userCmd)

    def getStateKW(self):
        # determine time remaining in this state
        timeElapsed = time.time() - self.status._timeStamp
        # cannot have negative time remaining
        timeRemaining = max(0, self.status._totalTime - timeElapsed)
        return "ThreadringState=%s, %i, %i, %.2f, %.2f"%(
            self.status._state, self.status.currIter, self.status.maxIter, timeRemaining, self.status._totalTime
            )

    def getFaultStr(self):
        faultList = []
        for axis, val in self.status.dict.iteritems():
            if hasattr(val, "iteritems"):
                for key, value in val.iteritems():
                    if "_fault" in key and bool(value):
                        # fault value is non zero or not None
                        faultList.append("%s %s %i"%(axis, key, val))
        if not faultList:
            # no faults
            return None
        else:
            faultStr = ",".join(faultList)
            return "ScaleRingFaults=%s"%faultStr

    def statusStr(self):
        kwList = []
        threadRingPos = "%.4f"%self.encPos if self.encPos else "nan"
        desThreadRingPos = "%.4f"%self.targetPos if self.targetPos else "nan"
        kwList.append("ThreadRingMotorPos=%.4f"%self.motorPos)
        kwList.append("ThreadRingEncPos=%s"%threadRingPos)
        kwList.append("ThreadRingSpeed=%.4f"%self.status.speed)
        kwList.append("ThreadRingMaxSpeed=%.4f"%self.status.maxSpeed)
        kwList.append("DesThreadRingPos=%s"%desThreadRingPos)
        kwList.append("ScaleZeroPos=%.4f"%self.scaleZeroPos)
        kwList.append("instrumentNum=%i"%21)#21)#self.status.cartID)
        kwList.append("CartLocked=%s"%"T" if self.status.locked else "F")
        kwList.append("CartLoaded=%s"%"T" if self.status.loaded else "F")
        return "; ".join(kwList)

    def writeStatusToUsers(self, userCmd=None):
        """Write the current status to all users
        """

        faultStr = self.getFaultStr()
        if faultStr is not None:
            self.writeToUsers("w", faultStr, userCmd)
        statusError = self.status.checkFullStatus()
        if statusError:
            self.writeToUsers("w", statusError)
        statusKWs = self.statusStr()
        self.writeToUsers("i", statusKWs, userCmd)
        self.writeState(userCmd)

    def writeState(self, userCmd=None):
        stateKW = self.getStateKW()
        self.writeToUsers("i", stateKW, userCmd)


    def speed(self, speedValue, userCmd=None):
        """Set the desired move speed for the thread ring
        @param[in] speedValue: a float, scale value to be converted to steps and sent to motor
        @param[in] userCmd: a twistedActor BaseCommand
        """
        speedValue = float(speedValue)
        userCmd = expandUserCmd(userCmd)
        if self.isMoving:
            userCmd.setState(userCmd.Failed, "Cannot set speed, device is busy moving")
            return userCmd
        elif float(speedValue) > self.status.maxSpeed:
            userCmd.setState(userCmd.Failed, "Max Speed Exceeded: %.4f > %.4f"%(speedValue, self.status.maxSpeed))
            return userCmd
        else:
            speedDevCmd = self.queueDevCmd("speed %.6f"%speedValue, userCmd)
            statusDevCmd = self.queueDevCmd("status", userCmd)
            statusDevCmd.addCallback(self._statusCallback)
            LinkCommands(userCmd, [speedDevCmd, statusDevCmd])
        return userCmd

    def getMoveCmdStr(self):
        """Determine difference between current and
        desired position and send move command as an
        offset.

        This is hacky.  I'm only allowed to command absolute positions
        on the scaling ring so i need to determine the absolute position wanted
        based on the offset I measure...gah.
        """
        offset = self.targetPos - self.encPos
        absMovePos = self.status.position + offset
        return "move %.6f"%(absMovePos)

    def move(self, position, userCmd=None):
        """!Move to a position

        @param[in] postion: a float, position to move to (mm)
        @param[in] userCmd: a twistedActor BaseCommand
        """
        log.info("%s.move(postion=%.6f, userCmd=%s)" % (self, position, userCmd))
        userCmd=expandUserCmd(userCmd)
        if self.isMoving:
            userCmd.setState(userCmd.Failed, "Cannot move, device is busy moving")
            return userCmd
        # verify position is in range
        minPos, maxPos = self.status.moveRange
        if not minPos<=position<=maxPos:
            userCmd.setState(userCmd.Failed, "Move %.6f not in range [%.4f, %.4f]"%(position, minPos, maxPos))
            return userCmd
        self.iter = 1
        self.targetPos = position
        self.moveUserCmd = userCmd
        print("moving threadring to: ", self.targetPos)
        if not self.moveUserCmd.isActive:
            self.moveUserCmd.setState(self.moveUserCmd.Running)
        self.writeToUsers("i", "DesThreadRingPos=%.4f"%self.targetPos)
        moveDevCmd = self.queueDevCmd(self.getMoveCmdStr(), self.moveUserCmd)
        moveDevCmd.addCallback(self._moveCallback)
        return userCmd

    def home(self, userCmd=None):
        log.info("%s.home(userCmd=%s)" % (self, userCmd))
        setCountCmd = self.measScaleDev.setCountState()

        def finishHome(_statusCmd):
            if _statusCmd.didFail:
                userCmd.setState(userCmd.Failed, "status failed.")
            elif _statusCmd.isDone:
                # assert that the encoders are really reading
                # zeros
                if not numpy.all(numpy.abs(self.measScaleDev.encPos) < 0.001):
                    userCmd.setState(userCmd.Failed, "zeros failed to set: %s"%str(self.measScaleDev.encPos))
                else:
                    userCmd.setState(userCmd.Done)

        def getStatus(_zeroEncCmd):
            if _zeroEncCmd.didFail:
                userCmd.setState(userCmd.Failed, "Encoder zero failed.")
            elif _zeroEncCmd.isDone:
                statusCmd = self.getStatus()
                statusCmd.addCallback(finishHome)

        def zeroEncoders(_moveCmd):
            if _moveCmd.didFail:
                userCmd.setState(userCmd.Failed, "Failed to move scaling ring to home position")
            elif _moveCmd.isDone:
                # zero the encoders in 1 second (give the ring a chance to stop)
                def zeroEm():
                    zeroEncCmd = self.measScaleDev.setZero()
                    zeroEncCmd.addCallback(getStatus)
                reactor.callLater(1., zeroEm)

        def moveThreadRing(_setCountCmd):
            if _setCountCmd.didFail:
                userCmd.setState(userCmd.Failed, "Failed to set Mitutoyo EV counter into counting state")
            elif _setCountCmd.isDone:
                moveCmd = self.queueDevCmd("move %.6f"%self.measScaleDev.zeroPoint, userCmd)
                moveCmd.addCallback(zeroEncoders)
        setCountCmd.addCallback(moveThreadRing)

    def _moveCallback(self, moveCmd):
        if moveCmd.isActive:
            self.status.setThreadAxisCurrent() # should already be there but whatever
            # set state to moving, compute time, etc
            time4move = abs(self.targetPos-self.status.position)/float(self.status.speed)
            # update command timeout
            moveCmd.setTimeLimit(time4move+2)
            self.status.setState(self.status.Moving, self.iter, time4move)
            self.writeState(self.moveUserCmd)
        if moveCmd.didFail:
            self.moveUserCmd.setState(self.moveUserCmd.Failed, "Failed to move scaling ring.")
        elif moveCmd.isDone:
            # get the status of the scaling ring
            moveStatusCmd = self.queueDevCmd("status", self.moveUserCmd)
            moveStatusCmd.addCallback(self._statusCallback)
            moveStatusCmd.addCallback(self._readEncs)

    def _readEncs(self, moveStatusCmd):
        if moveStatusCmd.didFail:
            self.moveUserCmd.setState(self.moveUserCmd.Failed, "Failed to get scaling ring status after move.")
        elif moveStatusCmd.isDone:
            # move's done, read the encoders
            # and get scaling ring status
            readEncCmd = self.measScaleDev.getStatus()
            readEncCmd.addCallback(self._moveIter)

    def _moveIter(self, readEncCmd):
        """Encoders have been read, decide to move again or finish the user
        commanded move
        """
        if readEncCmd.didFail:
            self.moveUserCmd.setState(self.moveUserCmd.Failed, "Failed to read encoders after scaling ring move.")
        elif readEncCmd.isDone:
            atMaxIter = self.iter > self.status.maxIter
            withinTol = numpy.abs(self.targetPos - self.encPos) < self.status.moveTol
            if atMaxIter:
                self.writeToUsers("i", "text='Max iter reached for scaling ring move.'")
            if atMaxIter or withinTol:
                # the move is done
                self.iter = 0
                self.status.setState(self.status.Done, self.iter)
                self.writeState(self.moveUserCmd)
                self.moveUserCmd.setState(self.moveUserCmd.Done)
            else:
                # command another move iteration
                self.iter += 1
                self.writeToUsers("i", "text='Begining scaling ring move iteration %i'"%self.iter)
                moveDevCmd = self.queueDevCmd(self.getMoveCmdStr(), self.moveUserCmd)
                moveDevCmd.addCallback(self._moveCallback)


    def stop(self, userCmd=None):
        """Stop any scaling movement, cancelling any currently executing
        command and any commands waiting on queue

        @param[in] userCmd: a twistedActor BaseCommand
        """
        userCmd=expandUserCmd(userCmd)
        # kill any commands pending on the queue
        # nicely kill move command if it's running
        # if not self.currExeDevCmd.userCmd.isDone:
        #     self.currExeDevCmd.userCmd.setState(self.currExeDevCmd.userCmd.Failed, "Killed by stop")
        if self.moveUserCmd.isActive:
            self.moveUserCmd.setState(self.moveUserCmd.Cancelled, "Scaling ring move cancelled by stop command.")
        stopDevCmd = self.queueDevCmd("stop", userCmd)
        statusDevCmd = self.queueDevCmd("status", userCmd)
        statusDevCmd.addCallback(self._statusCallback)
        LinkCommands(userCmd, [stopDevCmd, statusDevCmd])
        return userCmd

    def handleReply(self, replyStr):
        """Handle a line of output from the device.

        @param[in] replyStr   the reply, minus any terminating \n
        """
        log.info("%s.handleReply(replyStr=%s)" % (self, replyStr))
        replyStr = replyStr.strip().lower()
        # print(replyStr, self.currExeDevCmd.cmdStr)
        if not replyStr:
            return
        if self.currExeDevCmd.isDone:
            # ignore unsolicited output?
            log.info("%s usolicited reply: %s for done command %s" % (self, replyStr, str(self.currExeDevCmd)))
            self.writeToUsers("i", "%s usolicited reply: %s for done command %s" % (self, replyStr, str(self.currExeDevCmd)))
            return
        if replyStr == "ok":
            # print("got ok", self.currExeDevCmd.cmdStr)
            if self.currExeDevCmd.cmdStr == "status":
                # if this is a status, verify it was not mangled before setting done
                # if it is mangled try again
                try:
                    statusError = self.status.checkFullStatus()
                except MungedStatusError as statusError:
                    # status was munged, try again
                    print("statusError", statusError)
                    self.writeToUsers("w", statusError, self.currExeDevCmd.userCmd)
                    self.status.nIter += 1
                    if self.status.nIter > self.status.maxIter:
                        self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Maximum status iter reached. Status output generally mangled?")
                    else:
                        self.writeToUsers("w", "scale status mangle trying again iter %i"%self.status.nIter, self.currExeDevCmd.userCmd)
                        print("status munged, rewriting status")
                        log.info("%s writing %r iter %i" % (self, "status", self.status.nIter))
                        self.conn.writeLine("status")
                    return
                print("status done and good")
            self.currExeDevCmd.setState(self.currExeDevCmd.Done)
        elif replyStr == self.currExeDevCmd.cmdStr:
            # command echo
            pass
        elif "error" in replyStr:
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, replyStr)
        elif self.currExeDevCmd.cmdStr == "status":
            # only parse lines if we asked for status
            try:
                parsed = self.status.parseStatusLine(replyStr)
            except Exception as e:
                errMsg = "Scale Device failed to parse: %s"%str(replyStr)
                print(traceback.print_exc(file=sys.stdout))
                log.error(errMsg)
                self.writeToUsers("w", errMsg)
            # if not parsed:
            #     print("%s.handleReply unparsed line: %s" % (self, replyStr))
            #     log.info("%s.handleReply unparsed line: %s" % (self, replyStr))


    # def sendCmd(self, devCmdStr, userCmd):
    #     """!Execute the command
    #     @param[in] devCmdStr  a string to send to the scale controller
    #     @param[in] userCmd  a user command
    #     """
    #     if not self.conn.isConnected:
    #         log.error("%s cannot write %r: not connected" % (self, userCmd.cmdStr))
    #         userCmd.setState(userCmd.Failed, "not connected")
    #         return
    #     if not self.currCmd.isDone:
    #         log.error("%s cannot write %r: existing command %r not finished" % (self, userCmd.cmdStr, self.currCmd.cmdStr))
    #         userCmd.setState(userCmd.Failed, "device is busy")
    #         return
    #     self.currCmd = userCmd
    #     self.currDevCmdStr = devCmdStr
    #     log.info("%s writing %s" % (self, devCmdStr))
    #     self.conn.writeLine(devCmdStr)

    def queueDevCmd(self, devCmdStr, userCmd):
        """Add a device command to the device command queue

        @param[in] devCmdStr: a command string to send to the device.
        @param[in] userCmd: a UserCmd associated with this device (probably but
                                not necessarily linked.  Used here for writeToUsers
                                reference.
        """
        log.info("%s.queueDevCmd(devCmdStr=%r, cmdQueue: %r"%(self, devCmdStr, self.devCmdQueue))
        # append a cmdVerb for the command queue (otherwise all get the same cmdVerb and cancel eachother)
        # could change the default behavior in CommandQueue?
        cmdVerb = devCmdStr.split()[0]
        assert cmdVerb in self.validCmdVerbs
        devCmd = DevCmd(cmdStr=devCmdStr)
        devCmd.cmdVerb = cmdVerb
        devCmd.userCmd = userCmd
        def queueFunc(devCmd):
            # when the command is ready run this
            # everything besides a move should return quickly
            devCmd.setTimeLimit(SEC_TIMEOUT)
            devCmd.setState(devCmd.Running)
            if cmdVerb == "status":
                # wipe status, to ensure we've
                # gotten a full status when done.
                self.status.flushStatus()
            self.startDevCmd(devCmd.cmdStr)
        self.devCmdQueue.addCmd(devCmd, queueFunc)
        return devCmd


    def startDevCmd(self, devCmdStr):
        """
        @param[in] devCmdStr a line of text to send to the device
        """
        devCmdStr = devCmdStr.lower()
        log.info("%s.startDevCmd(%r)" % (self, devCmdStr))
        try:
            if self.conn.isConnected:
                log.info("%s writing %r" % (self, devCmdStr))
                self.conn.writeLine(devCmdStr)
            else:
                self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Not connected")
        except Exception as e:
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, textMsg=strFromException(e))
예제 #10
0
class MeasScaleDevice(TCPDevice):
    """!A Device for communicating with the LCO Scaling ring."""
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a ScaleDevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of scaling ring controller
        @param[in] port  port of scaling ring controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        # the mitutoyos will be zeroed when the scaling ring is moved to 20
        # so this zero point keep track fo the offset.
        # if I could preset the mitutoyo's this would be unnecessary
        # the preset command "CP**" doesn't seem to work.
        self.zeroPoint = 20.0 # mm.  Position where scale = 1
        self.encPos = [None]*6

        self.devCmdQueue = CommandQueue({})

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )

    @property
    def position(self):
        # return the average value of all encoder positions
        # with respect to the zeroPoint
        # this is used for servoing the scaling ring
        if not self.isHomed:
            return None
        else:
            return numpy.mean(self.encPos) + self.zeroPoint

    @property
    def isHomed(self):
        if None in self.encPos:
            return False
        else:
            return True

    @property
    def currExeDevCmd(self):
        return self.devCmdQueue.currExeCmd.cmd

    @property
    def currDevCmdStr(self):
        return self.currExeDevCmd.cmdStr


    def init(self, userCmd=None, timeLim=3, getStatus=True):
        """Called automatically on startup after the connection is established.
        Only thing to do is query for status or connect if not connected

        getStatus ignored?
        """
        log.info("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        userCmd = expandUserCmd(userCmd)
        self.getStatus(userCmd) # status links the userCmd
        return userCmd

    def getStatus(self, userCmd=None, timeLim=1, linkState=True):
        """!Read all enc positions 1-6 channels, 3 physical gauges.
        """
        # first flush the current status to ensure we don't
        # have stale values
        print("reading migs!")
        userCmd = expandUserCmd(userCmd)
        self.encPos = [None]*6
        statusDevCmd = self.queueDevCmd(READ_ENC, userCmd)
        statusDevCmd.addCallback(self._statusCallback)
        statusDevCmd.setTimeLimit(timeLim)
        if linkState:
            LinkCommands(userCmd, [statusDevCmd])
            return userCmd
        else:
            # return the device command to be linked outside
            return statusDevCmd

    def setCountState(self, userCmd=None, timeLim=1):
        """!Set the Mitutoyo EV counter into the counting state,
        this is required after a power cycle
        """
        userCmd = expandUserCmd(userCmd)
        countDevCmd = self.queueDevCmd(COUNTING_STATE, userCmd)
        currValDevCmd = self.queueDevCmd(DISPLAY_CURR, userCmd)
        currValDevCmd.addCallback(self._statusCallback)
        countDevCmd.setTimeLimit(timeLim)
        currValDevCmd.setTimeLimit(timeLim)
        LinkCommands(userCmd, [countDevCmd, currValDevCmd])
        return userCmd

    def setZero(self, userCmd=None, timeLim=1):
        """!Set the Mitutoyo EV counter into the counting state,
        this is required after a power cycle
        """
        userCmd = expandUserCmd(userCmd)
        zeroDevCmd = self.queueDevCmd(ZERO_SET, userCmd)
        zeroDevCmd.setTimeLimit(timeLim)
        LinkCommands(userCmd, [zeroDevCmd])
        return userCmd

    def _statusCallback(self, statusCmd):
        # if statusCmd.isActive:
        #     # not sure this is necessary
        #     # but ensures we get a 100% fresh status
        #     self.status.flushStatus()
        if statusCmd.isDone and not statusCmd.didFail:
            self.writeStatusToUsers(statusCmd.userCmd)
            print("mig values,", self.encPos)
            print("done reading migs")

    def writeStatusToUsers(self, userCmd=None):
        self.writeToUsers("i", "ScaleZeroPos=%.4f"%self.zeroPoint)
        self.writeToUsers("i", self.encPosKWStr, userCmd)
        severity = "i"
        if not self.isHomed:
            severity = "w"
        self.writeToUsers(severity, self.encHomedKWStr, userCmd)


    @property
    def encPosKWStr(self):
        encPosStr = []
        for encPos in self.encPos[:3]:
            if encPos is None:
                encPosStr.append("?")
            else:
                encPos += self.zeroPoint
                encPosStr.append("%.3f"%encPos)
        return "ScaleEncPos=" + ", ".join(encPosStr[:3])

    @property
    def encHomedKWStr(self):
        homedInt = 1 if self.isHomed else 0
        return "ScaleEncHomed=%i"%homedInt

    def setEncValue(self, serialStr):
        """Figure out which gauge this line corresponds to and set the value
        """
        gaugeStr, gaugeVal = serialStr.split(",")
        if "error" in gaugeVal.lower():
            gaugeVal = None
        else:
            gaugeVal = float(gaugeVal)
        gaugeInd = int(gaugeStr.strip("GN0")) - 1
        self.encPos[gaugeInd] = gaugeVal

    def handleReply(self, replyStr):
        """Handle a line of output from the device.

        @param[in] replyStr   the reply, minus any terminating \n
        """
        log.info("%s.handleReply(replyStr=%s)" % (self, replyStr))
        replyStr = replyStr.strip()
        # print(replyStr, self.currExeDevCmd.cmdStr)
        if not replyStr:
            return
        if self.currExeDevCmd.isDone:
            # ignore unsolicited output?
            log.info("%s usolicited reply: %s for done command %s" % (self, replyStr, str(self.currExeDevCmd)))
            self.writeToUsers("i", "%s usolicited reply: %s for done command %s" % (self, replyStr, str(self.currExeDevCmd)))
            return

        if "error 15" in replyStr.lower():
            self.writeToUsers("w", "Mitutoyo Error 15, not in counting state (was it power cycled?). Homing necessary.")
            self.encPos = [None]*6

        elif "error" in replyStr.lower():
            # some other error?
            self.writeToUsers("w", "Mitutoyo EV counter Error output: " + replyStr)

        if self.currExeDevCmd.cmdStr == READ_ENC:
            # all encoders values have been read
            # set command done
            self.setEncValue(replyStr)
            # was this the 6th value read? if so we are done
            if replyStr.startswith(ENCVAL_PREFIX+"%i"%6):
                self.currExeDevCmd.setState(self.currExeDevCmd.Done)
        if self.currExeDevCmd.cmdStr in [COUNTING_STATE, ZERO_SET, DISPLAY_CURR]:
            if replyStr == SUCESS:
                # successful set into counting state
                self.currExeDevCmd.setState(self.currExeDevCmd.Done)


    def queueDevCmd(self, devCmdStr, userCmd):
        """Add a device command to the device command queue

        @param[in] devCmdStr: a command string to send to the device.
        @param[in] userCmd: a UserCmd associated with this device (probably but
                                not necessarily linked.  Used here for writeToUsers
                                reference.
        """
        log.info("%s.queueDevCmd(devCmdStr=%r, cmdQueue: %r"%(self, devCmdStr, self.devCmdQueue))
        #print("%s.queueDevCmd(devCmdStr=%r, cmdQueue: %r"%(self, devCmdStr, self.devCmdQueue))
        # append a cmdVerb for the command queue (otherwise all get the same cmdVerb and cancel eachother)
        # could change the default behavior in CommandQueue?
        devCmd = DevCmd(cmdStr=devCmdStr)
        devCmd.userCmd = userCmd
        devCmd.cmdVerb = devCmdStr
        self.devCmdQueue.addCmd(devCmd, self.startDevCmd)
        return devCmd


    def startDevCmd(self, devCmd):
        """
        @param[in] devCmd a dev command
        """
        log.info("%s.startDevCmd(%r)" % (self, devCmd.cmdStr))
        #print("%s.startDevCmd(%r)" % (self, devCmd.cmdStr))
        try:
            if self.conn.isConnected:
                log.info("%s writing %r" % (self, devCmd.cmdStr))
                devCmd.setState(devCmd.Running)
                self.conn.writeLine(devCmd.cmdStr)
            else:
                self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Not connected")
        except Exception as e:
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, textMsg=strFromException(e))
예제 #11
0
파일: m2Device.py 프로젝트: csayres/lcoTCC
class M2Device(TCPDevice):
    """!A Device for communicating with the M2 process."""
    def __init__(self, name, host, port, callFunc=None):
        """!Construct a LCODevice

        Inputs:
        @param[in] name  name of device
        @param[in] host  host address of m2 controller
        @param[in] port  port of m2 controller
        @param[in] callFunc  function to call when state of device changes;
                note that it is NOT called when the connection state changes;
                register a callback with "conn" for that task.
        """
        self.status = Status()
        self._statusTimer = Timer()
        self.waitMoveCmd = UserCmd()
        self.waitMoveCmd.setState(self.waitMoveCmd.Done)
        # self.waitGalilCmd = UserCmd()
        # self.waitGalilCmd.setState(self.waitGalilCmd.Done)
        # give status commands equal priority so they don't kill eachother
        priorityDict = {
            "status": 1,
            "speed": 1,
            "stop": 1,
            "move": 1,
            "galil": 1,
            "offset": 1,
        }

        self.devCmdQueue = CommandQueue(priorityDict) # all commands of equal priority

        TCPDevice.__init__(self,
            name = name,
            host = host,
            port = port,
            callFunc = callFunc,
            cmdInfo = (),
        )

    @property
    def isBusy(self):
        return self.status.state == Moving

    @property
    def isOff(self):
        return self.status.galil == Off

    @property
    def isDone(self):
        # only done when state=done and galil=off
        return not self.isBusy and self.isOff

    @property
    def currExeDevCmd(self):
        return self.devCmdQueue.currExeCmd.cmd

    @property
    def currDevCmdStr(self):
        return self.currExeDevCmd.cmdStr

    # @property
    # def atFocus(self):
    #     if self.status.statusFieldDict["focus"].value and abs(self.targFocus - self.status.statusFieldDict["focus"].value) < FocusPosTol:
    #         return True
    #     else:
    #         return False

    def init(self, userCmd=None, timeLim=None, getStatus=True):
        """Called automatically on startup after the connection is established.
        Only thing to do is query for status or connect if not connected
        """
        log.info("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        # print("%s.init(userCmd=%s, timeLim=%s, getStatus=%s)" % (self, userCmd, timeLim, getStatus))
        userCmd = expandUserCmd(userCmd)
        # if not self.isConnected:
        #     return self.connect(userCmd=userCmd)
        # get the speed on startup
        # ignore getStatus flag, just do it always
        self.queueDevCmd("speed")
        return self.getStatus(userCmd=userCmd)
        # userCmd.setState(userCmd.Done)
        # return userCmd

    def getStatus(self, userCmd=None):
        """Return current telescope status. Continuously poll.
        """
        log.info("%s.getStatus(userCmd=%s)" % (self, userCmd)) # logging this will flood the log
        # print("%s.getStatus(userCmd=%s)" % (self, userCmd))
        userCmd = expandUserCmd(userCmd)
        if not self.conn.isConnected:
            userCmd.setState(userCmd.Failed, "Not Connected to M2")
            return userCmd
        self._statusTimer.cancel() # incase a status is pending
        userCmd = expandUserCmd(userCmd)
        # userCmd.addCallback(self._statusCallback)
        # gather list of status elements to get
        statusCmd = self.queueDevCmd("status", userCmd)
        LinkCommands(userCmd, [statusCmd])
        return userCmd

    def processStatus(self, replyStr):
        # print("procesStatus", replyStr)

        self.status.parseStatus(replyStr)
        # do we want status output so frequently? probabaly not.
        # perhaps only write status if it has changed...
        # but so far status is a small amount of values
        # so its probably ok
        statusStr = self.status.getStatusStr()
        if statusStr:
            userCmd = self.currExeDevCmd.userCmd
            if self.waitMoveCmd.isActive:
                userCmd = self.waitMoveCmd
                # write as debug during moves
                self.writeToUsers("d", statusStr, userCmd)
            else:
                self.writeToUsers("i", statusStr, userCmd)

        if self.waitMoveCmd.isActive:
            if not self.isBusy:
                # move is done
                if not self.isOff:
                    # move just finished but galil is not off, turn it off
                    self.queueDevCmd("galil off")
                else:
                    # move is done and galil is off, set wait move command as done
                    self.waitMoveCmd.setState(self.waitMoveCmd.Done)
        if not self.isDone:
            # keep polling until done
            self._statusTimer.start(PollTime, self.getStatus)

    def stop(self, userCmd=None):
        userCmd = expandUserCmd(userCmd)
        if not self.waitMoveCmd.isDone:
            self.waitMoveCmd.setState(self.waitMoveCmd.Cancelled, "Stop commanded")
        #print("sec stop commanded")
        stopCmd = self.queueDevCmd("stop", userCmd)
        galilOffCmd = self.queueDevCmd("galil off", userCmd)
        status = self.queueDevCmd("status", userCmd)
        status2 = self.queueDevCmd("status", userCmd)
        # first status gets the error state
        # second status clears it
        LinkCommands(userCmd, [stopCmd, status, galilOffCmd, status2])
        return userCmd

    def focus(self, focusValue, offset=False, userCmd=None):
        """Command an offset or absolute focus move

        @param[in] focusValue: float, focus value in microns
        @param[in] offset, if true this is offset, else absolute
        @param[in] userCmd: a twistedActor BaseCommand

        WARNING!!!
        At APO increasing focus corresponds to decreasing M1-M2 dist.
        The mirror controller at LCO convention is the opposite.
        """
        log.info("%s.focus(userCmd=%s, focusValue=%.2f, offset=%s)" % (self, userCmd, focusValue, str(bool(offset))))
        # if this focus value is < 50 microns
        userCmd = expandUserCmd(userCmd)
        if offset:
            deltaFocus = focusValue
        else:
            deltaFocus = self.status.secFocus - focusValue


        if abs(deltaFocus) < MIN_FOCUS_MOVE:
            # should focus be cancelled or just set to done?
            self.writeToUsers("w", "Focus offset below threshold of < %.2f, moving anyways."%MIN_FOCUS_MOVE, userCmd)
            # userCmd.setState(userCmd.Done)
            # return userCmd

        # focusDir = 1 # use M2's natural coordinates
        # focusDir = -1 # use convention at APO
        return self.move(valueList=[focusValue], offset=offset, userCmd=userCmd)

    def move(self, valueList, offset=False, userCmd=None):
        """Command an offset or absolute orientation move

        @param[in] valueList: list of 1 to 5 values specifying pistion(um), tiltx("), tilty("), transx(um), transy(um)
        @param[in] offset, if true this is offset, else absolute
        @param[in] userCmd: a twistedActor BaseCommand

        Note: increasing distance eg pistion means increasing spacing between primary and
        secondary mirrors.
        """
        log.info("%s.move(userCmd=%s, valueList=%s, offset=%s)" % (self, userCmd, str(valueList), str(bool(offset))))
        userCmd = expandUserCmd(userCmd)
        if not self.waitMoveCmd.isDone:
            userCmd.setState(userCmd.Failed, "Mirror currently moving")
            return userCmd
        if not 1<=len(valueList)<=5:
            userCmd.setState(userCmd.Failed, "Must specify 1 to 5 numbers for a move")
            return userCmd
        self.waitMoveCmd = UserCmd()
        self.waitMoveCmd.userCmd = userCmd # for write to users
        self.status.desOrientation = self.status.orientation[:]
        if offset:
            # if offset is specified, offset from current value
            for ii, value in enumerate(valueList):
                self.status.desOrientation[ii] += value
        else:
            # if absolute is wanted, overwrite all those
            # specified
            for ii, value in enumerate(valueList):
                self.status.desOrientation[ii] = value
        cmdType = "offset" if offset else "move"
        strValList = " ".join(["%.2f"%val for val in valueList])
        cmdStr = "%s %s"%(cmdType, strValList)
        moveCmd = self.queueDevCmd(cmdStr, userCmd)
        statusCmd = self.queueDevCmd("status", userCmd)
        # status immediately to see moving state
        # determine total time for move
        # just use focus distance as proxy (ignore)
        galilOverHead = 2 # galil take roughly 2 secs to boot up.
        extraOverHead = 2 #
        self.status._moveTimeTotal = self.getTimeForMove()
        timeout = self.status._moveTimeTotal+galilOverHead+extraOverHead
        userCmd.setTimeLimit(timeout)
        LinkCommands(userCmd, [moveCmd, statusCmd, self.waitMoveCmd])
        return userCmd

    def getTimeForMove(self):
        dist2Move = numpy.max(numpy.abs(numpy.subtract(self.status.desOrientation, self.status.orientation)))
        time4Move = dist2Move / self.status.speed
        return time4Move

    def handleReply(self, replyStr):
        """Handle a line of output from the device. Called whenever the device outputs a new line of data.

        @param[in] replyStr   the reply, minus any terminating \n

        Tasks include:
        - Parse the reply
        - Manage the pending commands
        - Parse status to update the model parameters
        """
        log.info("%s read %r, currCmdStr: %s" % (self, replyStr, self.currDevCmdStr))
        # print("%s read %r, currCmdStr: %s" % (self, replyStr, self.currDevCmdStr))
        replyStr = replyStr.strip()
        if not replyStr:
            return
        if self.currExeDevCmd.isDone:
            # ignore unsolicited ouput
            return
        if "error" in replyStr.lower():
            # error
            self.writeToUsers("w", "Error in M2 reply: %s, current cmd: %s"%(replyStr, self.currExeDevCmd.cmdStr))
        # if this was a speed command, set it
        if self.currDevCmdStr.lower() == "speed":
            self.status.speed = float(replyStr)
        elif self.currDevCmdStr.lower() == "status":
            self.processStatus(replyStr)
        # only one line is ever returned after a request
        # so if we got one, then the request is done
        self.currExeDevCmd.setState(self.currExeDevCmd.Done)


    def queueDevCmd(self, cmdStr, userCmd=None):
        """Add a device command to the device command queue

        @param[in] cmdStr, string to send to the device.
        """
        log.info("%s.queueDevCmd(cmdStr=%r, cmdQueue: %r"%(self, cmdStr, self.devCmdQueue))
        # print("%s.queueDevCmd(devCmd=%r, devCmdStr=%r, cmdQueue: %r"%(self, devCmd, devCmd.cmdStr, self.devCmdQueue))
        # append a cmdVerb for the command queue (other wise all get the same cmdVerb and cancel eachother)
        # could change the default behavior in CommandQueue?
        userCmd = expandUserCmd(userCmd)
        devCmd = DevCmd(cmdStr)
        devCmd.cmdVerb = cmdStr.split()[0]
        devCmd.userCmd = userCmd
        def queueFunc(devCmd):
            self.startDevCmd(devCmd)
        self.devCmdQueue.addCmd(devCmd, queueFunc)
        return devCmd


    def startDevCmd(self, devCmd):
        """
        @param[in] devCmdStr a line of text to send to the device
        """
        devCmdStr = devCmd.cmdStr.lower() # m2 uses all lower case
        log.info("%s.startDevCmd(%r)" % (self, devCmdStr))
        try:
            if self.conn.isConnected:
                log.info("%s writing %r" % (self, devCmdStr))
                # set move command to running now. Bug if set earlier race condition
                # with status
                if "move" in devCmdStr.lower() or "offset" in devCmdStr.lower():
                    self.waitMoveCmd.setState(self.waitMoveCmd.Running)
                    self.status.state = Moving
                    self.writeToUsers("i", self.status.secStateStr(), devCmd.userCmd)
                # if "galil" in devCmdStr.lower():
                #     self.waitGalilCmd.setState(self.waitGalilCmd.Running)
                self.conn.writeLine(devCmdStr)
            else:
                self.currExeDevCmd.setState(self.currExeDevCmd.Failed, "Not connected to M2")
        except Exception as e:
            self.currExeDevCmd.setState(self.currExeDevCmd.Failed, textMsg=strFromException(e))