1 from __future__
import division, absolute_import
10 from RO.Comm.TwistedTimer
import Timer
11 from RO.StringUtil
import strFromException, quoteStr
12 from coordConv
import PVT
13 from twistedActor
import BaseActor, UserCmd, CommandError, LinkCommands, log
16 from tcc
import __version__
17 from .tccCmdParser
import TCCCmdParser
18 from tcc.mir import computeOrient
19 from tcc.msg import formatAxisCmdStateAndErr, formatDisabledProc, formatPVTList, UDPSender, getBinaryForUDP, \
21 from tcc.cmd import showScaleFactor
23 __all__ = [
"TCCActor"]
25 numpy.seterr(divide=
"ignore")
27 DefaultUserPort = 2200
32 AxisNoWarnErrSet = set((tcc.base.AxisErr_HaltRequested, tcc.base.AxisErr_NotAvailable, tcc.base.AxisErr_OK))
34 """!Actor for controlling a telescope
39 userPort = DefaultUserPort,
40 udpPort = DefaultUDPPort,
42 posRefCatPath =
"posRefCatalog.dat",
45 """Construct a TCCActor
47 @param[in] axisDict a dict of axis name: axis device
48 @param[in] mirrorDict a dict of mirror name: mirror device
49 @param[in] userPort port on which to listen for users
50 @param[in] udpPort port on which to broadcast UDP packets.
51 @param[in] maxUsers the maximum allowed number of users; if 0 then there is no limit
52 @param[in] name actor name; used for logging
53 @param[in] posRefCatPath path to position reference catalog, relative to $TCC_DATA_DIR
54 @param[in] connectRot bool. If True, automatically connect to rot1 on startup, else leave
55 the slot empty. SDSS TCC should set to True. 3.5m set to False
61 self.
tune = tcc.base.Tune()
62 BaseActor.__init__(self, userPort=userPort, maxUsers=1, name=name, version=__version__)
64 log.
info(
"%r starting up" % (self,))
66 for dev
in mirrorDict.itervalues():
67 if dev.name
not in (
"prim",
"sec",
"tert"):
68 raise RuntimeError(
"invalid mirror name: %r" % (dev.name,))
69 dev.writeToUsers = self.writeToUsers
71 mirDevList = [mirrorDict.get(mirName,
None)
for mirName
in tcc.mir.MirrorDeviceSet.SlotList]
77 for dev
in axisDict.itervalues():
78 if not re.match(
r"az|alt|rot\d+", dev.name):
79 raise RuntimeError(
"invalid axis name: %r" % (dev.name,))
80 dev.writeToUsers = self.writeToUsers
86 axisDevList=[axisDict[
"az"], axisDict[
"alt"],
None]
88 axisDevList[-1] = axisDict[
"rot1"]
97 self.axeLim.loadPath(os.path.join(self.
dataDir,
"axelim.dat"))
99 self.
inst = tcc.base.Inst()
103 self.obj.site.wavelen = 4500
104 self.obj.gsSite.wavelen = 4500
107 self.tune.loadPath(os.path.join(self.
dataDir,
"tune.dat"))
110 self.slewCmd.setState(self.slewCmd.Done)
119 fullPosRefCatPath = os.path.join(self.
dataDir, posRefCatPath)
120 log.
info(
"Loading position reference catalog %r" % (fullPosRefCatPath,))
122 log.
info(
"Loaded %s position reference stars" % (len(self.
posRefCatalog),))
124 def startAxisStatus(userCmd):
125 """Queue axis status once userCmd succeeds"""
127 log.warn(
"Axis connection failed; not queueing status")
129 statusDelay = self.tune.statusInterval[2]
130 log.
info(
"Axes connected; queueing status for %s seconds from now" % (statusDelay,))
131 self.statusTimer.start(statusDelay, self.
showStatus)
132 axisConnCmd = self.axisDevSet.connect()
133 axisConnCmd.addCallback(startAxisStatus, callNow=
False)
135 self.mirDevSet.connect()
140 doRestart=[
False]*tcc.base.NAxes,
146 tai = tcc.base.tai(),
150 self.udpSender.startListening()
156 return self.tune.maxUsers
161 maxUsers = int(maxUsers)
163 raise ValueError(
"maxUsers = %r; must be > 0" % (maxUsers,))
164 self.tune.maxUsers = maxUsers
166 def _cancelTimers(self):
169 self.udpSender.stopListening()
170 self.collimateTimer.cancel()
171 self.collimateSlewEndTimer.cancel()
172 self.earthTimer.cancel()
173 self.slewDoneTimer.cancel()
174 self.statusTimer.cancel()
175 self.trackTimer.cancel()
176 self.brdTelPosTimer.cancel()
179 """Dispatch the user command
181 @param[in] cmd user command (a twistedActor.UserCmd)
185 self.writeToOneUser(
":",
"", cmd=cmd)
189 cmd.parsedCmd = self.cmdParser.parseLine(cmd.cmdBody)
190 except Exception
as e:
191 cmd.setState(cmd.Failed,
"Could not parse %r: %s" % (cmd.cmdBody, strFromException(e)))
195 if cmd.parsedCmd.callFunc:
196 cmd.setState(cmd.Running)
198 cmd.parsedCmd.callFunc(self, cmd)
199 except CommandError
as e:
200 cmd.setState(
"failed", textMsg=strFromException(e))
202 except Exception
as e:
203 sys.stderr.write(
"command %r failed\n" % (cmd.cmdStr,))
204 sys.stderr.write(
"function %s raised %s\n" % (cmd.parsedCmd.callFunc, strFromException(e)))
205 traceback.print_exc(file=sys.stderr)
206 textMsg = strFromException(e)
207 hubMsg =
"Exception=%s" % (e.__class__.__name__,)
208 cmd.setState(
"failed", textMsg=textMsg, hubMsg=hubMsg)
210 raise RuntimeError(
"Command %r not yet implemented" % (cmd.parsedCmd.cmdVerb,))
213 """Show user information including your userID.
215 This overridden version also outputs YourUserNum, to match the old TCC.
217 self.writeToOneUser(
"i",
"YourUserNum=%s" % (cmd.userID,), cmd=cmd)
218 BaseActor.showUserInfo(self, cmd)
224 """!Show actor version
226 This overridden version uses uppercase for the version string
228 msgStr =
"Version=%s" % (quoteStr(self.version),)
230 self.writeToOneUser(
"i", msgStr, cmd=cmd)
232 self.writeToUsers(
"i", msgStr, cmd=cmd)
235 """Queue a new status request
237 @param[in] delaySec delay (sec); if None then auto-computed
240 if self.obj.isSlewing():
241 delaySec = self.tune.statusInterval[1]
242 elif self.obj.isMoving()
or any(actMt.isfinite()
and abs(actMt.vel) > 0.001
for actMt
in self.obj.actMount):
243 delaySec = self.tune.statusInterval[0]
245 delaySec = self.tune.statusInterval[2]
246 log.
info(
"%s.queueStatus; delaySec=%s" % (self, delaySec))
247 self.statusTimer.start(delaySec, self.
showStatus)
252 @param[in,out] cmd user command; warning; if not None then set to Done;
253 if None then some status is omitted if unchanged
256 if not self.tune.doStatus:
258 self.writeToUsers(msgCode, msgStr)
261 def showRestOfStatus(statusCmd, cmd=cmd):
262 """Show the rest of the status after axis status
265 currTAI = tcc.base.tai()
267 "TCCPos=%0.6f, %0.6f, %0.6f" % tuple(pvt.getPos(currTAI)
for pvt
in obj.targetMount),
269 "AxePos=%0.6f, %0.6f, %0.6f" % tuple(pvt.getPos(currTAI)
for pvt
in obj.actMount),
273 self.writeToUsers(msgCode=
"i", msgStr=
"; ".join(msgList), cmd=cmd)
276 self.writeToUsers(msgCode=msgCode, msgStr=axisCmdStateStr, cmd=cmd)
278 self.axisDevSet.showConnState(userCmd=cmd)
279 self.mirDevSet.showConnState(userCmd=cmd)
281 if self.tune.doStatus:
285 if statusCmd.didFail:
286 cmd.setState(cmd.Failed, textMsg=statusCmd.textMsg, hubMsg=statusCmd.hubMsg)
288 cmd.setState(cmd.Done)
290 statusCmd = self.axisDevSet.status()
291 statusCmd.addCallback(showRestOfStatus)
294 """Update collimation based on info in obj, inst, weath blocks, for all mirrors present
296 @param[in] cmd command (twistedActor.BaseCmd) associated with this request;
297 state will be updated upon completion; None if no command is associated
301 if not self.tune.doCollimate:
303 self.writeToUsers(msgCode, msgStr)
306 alt = self.obj.targetMount[1].getPos(tcc.base.tai())
307 if not numpy.isfinite(alt):
310 self.writeToUsers(
"w",
'Text="Cannot set collimation: target altitude unknown"', cmd=cmd)
311 cmd.setState(cmd.Done)
316 mirNameCmdStrList = []
317 for mirName
in self.mirrorDict.keys():
319 cmdStr =
'move ' +
','.join([
'%.4f' % (o,)
for o
in orient])
320 mirNameCmdStrList.append((mirName, cmdStr))
324 for mirName, mirCmdStr
in mirNameCmdStrList:
325 mirCmd = self.
mirrorDict[mirName].startCmd(mirCmdStr, timeLim=60)
326 subCmdList.append(mirCmd)
331 LinkCommands(mainCmd = cmd, subCmdList = subCmdList)
333 cmd.setState(cmd.Done)
337 for cmd
in subCmdList:
340 if self.tune.doCollimate:
343 self.collimateTimer.cancel()
345 def _writeCollimationFailureToUsers(self, cmd):
347 self.writeToUsers(
"w",
"Text=\"Collimation Failure: %s\""%cmd.textMsg)
350 """Update earth orientation predictions
352 Load data for the current TAI from the latest data file into self.earth and schedule the next update.
355 currTAI = tcc.base.tai()
356 with file(os.path.join(self.
dataDir,
"earthpred.dat"))
as f:
357 self.earth.loadPredictions(f, forTAI=currTAI)
359 self.earthTimer.start(sec=EarthInterval, callFunc=self.
updateEarth)
362 """Compute next tracking position and send to axis controllers (slew or track).
364 @param[in] cmd command (twistedActor.BaseCmd) associated with this request;
365 state will be updated upon completion; None if no command is associated
369 self.trackTimer.cancel()
370 if not self.obj.isMoving():
373 startTAI = tcc.base.tai()
374 advTime = self.obj.updateTime - startTAI
376 startMsgStr =
"updateTracking started at TAI=%0.2f: advTime=%0.2f seconds" % (startTAI, advTime)
378 log.warn(startMsgStr)
380 log.
info(startMsgStr)
382 if advTime < self.tune.trackAdvTime * 1.5:
384 oldTargetMount = [PVT(targetMount)
for targetMount
in self.obj.targetMount]
385 oldErrCode = self.obj.axisErrCode[:]
386 oldHaltedList = [errCode != 0
for errCode
in oldErrCode]
387 oldSigBad = numpy.any(numpy.logical_and(oldHaltedList, self.obj.axisIsSignificant))
392 doRestart=[
False]*tcc.base.NAxes,
398 tai = self.obj.updateTime + self.tune.trackInterval,
403 if not self.obj.targetMount[i].isfinite():
407 if not oldTargetMount[i].isfinite():
409 self.log.error(
"updateTracking bug: new targetMount computed when old unknown: axis=%s; obj.targetMount[i]=%s; obj.axisErrCode[i]=%s" \
410 % (i, self.obj.targetMount[i], self.obj.axisErrCode[i]))
411 self.obj.targetMount[i].invalidate(self.obj.updateTime)
412 if self.obj.axisErrCode[i] == tcc.base.AxisErr_OK:
413 self.obj.axisErrCode[i] == tcc.base.AxisErr_TCCBug
416 if self.obj.axisErrCode[i] == tcc.base.AxisErr_OK:
418 pA = oldTargetMount[i].pos,
419 vA = oldTargetMount[i].vel,
420 pB = self.obj.targetMount[i].pos,
421 vB = self.obj.targetMount[i].vel,
422 dt = self.obj.targetMount[i].t - oldTargetMount[i].t,
431 if self.obj.axisErrCode[i] != tcc.base.AxisErr_OK:
432 self.obj.targetMount[i].invalidate(self.obj.updateTime)
434 newErrCode = self.obj.axisErrCode[:]
435 newHaltedList = [errCode != tcc.base.AxisErr_OK
for errCode
in newErrCode]
436 newSigBad = numpy.any(numpy.logical_and(newHaltedList, self.obj.axisIsSignificant))
439 self.axisDevSet.movePVT(self.obj.targetMount, userCmd=cmd)
444 self.writeToUsers(msgCode=msgCode, msgStr=msgStr, cmd=cmd)
446 newSigBad = numpy.any(numpy.logical_and(newHaltedList, self.obj.axisIsSignificant))
447 if newSigBad
and not oldSigBad:
448 raise RuntimeError(
"one or more significant axes halted")
450 log.
info(
"updateTracking called early enough that no tracking updated needed yet")
452 if self.obj.isMoving():
453 endTAI = tcc.base.tai()
454 nextWakeTAI = self.obj.updateTime - self.tune.trackAdvTime
455 sleepSec = nextWakeTAI - endTAI
458 execSec = endTAI - startTAI
459 endMsgStr =
"updateTracking ends at TAI=%0.2f; execSec=%0.2f; sleepSec=%0.2f; nextWakeTAI=%0.2f" % \
460 (endTAI, execSec, sleepSec, nextWakeTAI)
466 endMsgStr =
"updateTracking ends at TAI=%0.2f; execSec=%0.2f; no wakeup scheduled" % \
469 except Exception
as e:
470 traceback.print_exc(file=sys.stderr)
473 if not self.slewCmd.isDone:
474 self.slewCmd.setState(self.slewCmd.Failed,
"Slew failed: " + strFromException(e))
476 self.axisDevSet.stop()
479 """Broadcast telescope position in a UDP packet
483 if not self.tune.doBrdTelPos:
485 self.writeToUsers(msgCode, msgStr)
489 self.udpSender.sendPacket(binaryData)
491 self.brdTelPosTimer.start(1., self.
brdTelPos)
def computeOrient
Compute the orientation of one mirror.
Actor for controlling a telescope.
def _writeCollimationFailureToUsers
def showVersion
Show actor version.