Документ взят из кэша поисковой машины. Адрес оригинального документа : http://www.apo.nmsu.edu/Telescopes/TCC/html/tcc_actor_8py_source.html
Дата изменения: Tue Sep 15 02:25:37 2015
Дата индексирования: Sun Apr 10 01:36:40 2016
Кодировка:

Поисковые слова: http astrokuban.info astrokuban
lsst.tcc: python/tcc/actor/tccActor.py Source File
lsst.tcc  1.2.2-3-g89ecb63
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
tccActor.py
Go to the documentation of this file.
1 from __future__ import division, absolute_import
2 
3 import os
4 import re
5 import sys
6 import traceback
7 
8 import numpy
9 
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
14 import tcc.base
15 import tcc.axis
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, \
20  showAxeLim, showTune
21 from tcc.cmd import showScaleFactor
22 
23 __all__ = ["TCCActor"]
24 
25 numpy.seterr(divide="ignore") # don't warn about divide by 0
26 
27 DefaultUserPort = 2200
28 DefaultUDPPort = 2201
29 EarthInterval = 3600 # time between earth updates (sec)
30 
31 # set of axis "error" codes that do not merit a warning
32 AxisNoWarnErrSet = set((tcc.base.AxisErr_HaltRequested, tcc.base.AxisErr_NotAvailable, tcc.base.AxisErr_OK))
33 class TCCActor(BaseActor):
34  """!Actor for controlling a telescope
35  """
36  def __init__(self,
37  axisDict,
38  mirrorDict,
39  userPort = DefaultUserPort,
40  udpPort = DefaultUDPPort,
41  name = "TCCActor",
42  posRefCatPath = "posRefCatalog.dat",
43  connectRot = False,
44  ):
45  """Construct a TCCActor
46 
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
56  """
57  self.dataDir = tcc.base.getDataDir()
58  self.udpPort = udpPort
59  # create a null tune block before calling BaseActor.__init__, and do NOT set it from a data file yet,
60  # because BaseActor sets self.maxUsers, which sets self.tune.maxUsers
61  self.tune = tcc.base.Tune()
62  BaseActor.__init__(self, userPort=userPort, maxUsers=1, name=name, version=__version__)
63 
64  log.info("%r starting up" % (self,))
65 
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
70  self.mirrorDict = mirrorDict
71  mirDevList = [mirrorDict.get(mirName, None) for mirName in tcc.mir.MirrorDeviceSet.SlotList]
72  self.mirDevSet = tcc.mir.MirrorDeviceSet(
73  actor = self,
74  devList = mirDevList,
75  )
76 
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
81 
82  # a dict of all axis devices
83  # keys are "az", "alt", "rot1", "rot2", etc. (for those rotators that exist)
84  # values are AxisDevices
85  self.axisDict = axisDict
86  axisDevList=[axisDict["az"], axisDict["alt"], None]
87  if connectRot:
88  axisDevList[-1] = axisDict["rot1"]
89  self.axisDevSet = tcc.axis.AxisDeviceSet(
90  actor=self,
91  devList=axisDevList,
92  )
93 
94  # self.dev = DeviceCollection(self.mirrorDict.values() + self.axisDict.values())
95  self.cmdParser = TCCCmdParser()
96  self.axeLim = tcc.base.AxeLim()
97  self.axeLim.loadPath(os.path.join(self.dataDir, "axelim.dat"))
98  self.earth = tcc.base.Earth()
99  self.inst = tcc.base.Inst()
100  if connectRot:
101  self.inst.rotID = 1
102  self.obj = tcc.base.Obj()
103  self.obj.site.wavelen = 4500
104  self.obj.gsSite.wavelen = 4500
105  self.savedObj = None
106  self.telMod = tcc.base.TelMod(os.path.join(self.dataDir, "telmod.dat"))
107  self.tune.loadPath(os.path.join(self.dataDir, "tune.dat"))
108  self.weath = tcc.base.Weath()
109  self.slewCmd = UserCmd()
110  self.slewCmd.setState(self.slewCmd.Done)
111 
112  self.collimateTimer = Timer(0, self.updateCollimation)
113  self.collimateSlewEndTimer = Timer()
114  self.earthTimer = Timer(0, self.updateEarth)
115  self.slewDoneTimer = Timer()
116  self.statusTimer = Timer()
117  self.trackTimer = Timer()
118 
119  fullPosRefCatPath = os.path.join(self.dataDir, posRefCatPath)
120  log.info("Loading position reference catalog %r" % (fullPosRefCatPath,))
121  self.posRefCatalog = tcc.axis.PosRefCatalog(fullPosRefCatPath)
122  log.info("Loaded %s position reference stars" % (len(self.posRefCatalog),))
123 
124  def startAxisStatus(userCmd):
125  """Queue axis status once userCmd succeeds"""
126  if userCmd.didFail:
127  log.warn("Axis connection failed; not queueing status")
128  elif userCmd.isDone:
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)
134 
135  self.mirDevSet.connect()
136  self.updateEarth()
138  obj = self.obj,
139  doWrap=False,
140  doRestart=[False]*tcc.base.NAxes,
141  reportLim=True, # ???is this argument good for anything???
142  earth = self.earth,
143  inst = self.inst,
144  telMod = self.telMod,
145  axeLim = self.axeLim,
146  tai = tcc.base.tai(),
147  )
148 
149  self.udpSender = UDPSender(self.udpPort)
150  self.udpSender.startListening()
151  self.brdTelPosTimer = Timer(0, self.brdTelPos)
152 
153  @property
154  def maxUsers(self):
155  # link self.maxUsers to self.tune.maxUsers
156  return self.tune.maxUsers
157 
158  @maxUsers.setter
159  def maxUsers(self, maxUsers):
160  # link self.maxUsers to self.tune.maxUsers
161  maxUsers = int(maxUsers)
162  if maxUsers < 1:
163  raise ValueError("maxUsers = %r; must be > 0" % (maxUsers,))
164  self.tune.maxUsers = maxUsers
165 
166  def _cancelTimers(self):
167  """Cancel all timers
168  """
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()
177 
178  def parseAndDispatchCmd(self, cmd):
179  """Dispatch the user command
180 
181  @param[in] cmd user command (a twistedActor.UserCmd)
182  """
183  if not cmd.cmdBody:
184  # echo to show alive
185  self.writeToOneUser(":", "", cmd=cmd)
186  return
187 
188  try:
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)))
192  return
193 
194  #cmd.parsedCmd.printData()
195  if cmd.parsedCmd.callFunc:
196  cmd.setState(cmd.Running)
197  try:
198  cmd.parsedCmd.callFunc(self, cmd)
199  except CommandError as e:
200  cmd.setState("failed", textMsg=strFromException(e))
201  return
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)
209  else:
210  raise RuntimeError("Command %r not yet implemented" % (cmd.parsedCmd.cmdVerb,))
211 
212  def showUserInfo(self, cmd):
213  """Show user information including your userID.
214 
215  This overridden version also outputs YourUserNum, to match the old TCC.
216  """
217  self.writeToOneUser("i", "YourUserNum=%s" % (cmd.userID,), cmd=cmd)
218  BaseActor.showUserInfo(self, cmd)
219  showAxeLim(self, cmd)
220  showTune(self, cmd)
221  showScaleFactor(self, cmd, setDone=False)
222 
223  def showVersion(self, cmd, onlyOneUser=False):
224  """!Show actor version
225 
226  This overridden version uses uppercase for the version string
227  """
228  msgStr = "Version=%s" % (quoteStr(self.version),)
229  if onlyOneUser:
230  self.writeToOneUser("i", msgStr, cmd=cmd)
231  else:
232  self.writeToUsers("i", msgStr, cmd=cmd)
233 
234  def queueStatus(self, delaySec=None):
235  """Queue a new status request
236 
237  @param[in] delaySec delay (sec); if None then auto-computed
238  """
239  if delaySec is None:
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]
244  else:
245  delaySec = self.tune.statusInterval[2]
246  log.info("%s.queueStatus; delaySec=%s" % (self, delaySec))
247  self.statusTimer.start(delaySec, self.showStatus)
248 
249  def showStatus(self, cmd=None):
250  """Show status
251 
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
254  """
255  # print "showStatus"
256  if not self.tune.doStatus:
257  msgCode, msgStr = formatDisabledProc(self.tune)
258  self.writeToUsers(msgCode, msgStr)
259  return
260 
261  def showRestOfStatus(statusCmd, cmd=cmd):
262  """Show the rest of the status after axis status
263  """
264  obj = self.obj
265  currTAI = tcc.base.tai()
266  msgList = (
267  "TCCPos=%0.6f, %0.6f, %0.6f" % tuple(pvt.getPos(currTAI) for pvt in obj.targetMount),
268  # AxePos must use that case to make the enclosure controller happy
269  "AxePos=%0.6f, %0.6f, %0.6f" % tuple(pvt.getPos(currTAI) for pvt in obj.actMount),
270  "ObjInstAng=%s" % (formatPVTList([obj.objUserInstAng]),),
271  "SpiderInstAng=%s" % (formatPVTList([obj.spiderInstAng]),),
272  )
273  self.writeToUsers(msgCode="i", msgStr="; ".join(msgList), cmd=cmd)
274 
275  msgCode, axisCmdStateStr = formatAxisCmdStateAndErr(obj, showAll=True)
276  self.writeToUsers(msgCode=msgCode, msgStr=axisCmdStateStr, cmd=cmd)
277 
278  self.axisDevSet.showConnState(userCmd=cmd)
279  self.mirDevSet.showConnState(userCmd=cmd)
280  # verify that the tune block wants status
281  if self.tune.doStatus:
282  self.queueStatus()
283 
284  if cmd:
285  if statusCmd.didFail:
286  cmd.setState(cmd.Failed, textMsg=statusCmd.textMsg, hubMsg=statusCmd.hubMsg)
287  else:
288  cmd.setState(cmd.Done)
289 
290  statusCmd = self.axisDevSet.status()
291  statusCmd.addCallback(showRestOfStatus)
292 
293  def updateCollimation(self, cmd=None):
294  """Update collimation based on info in obj, inst, weath blocks, for all mirrors present
295 
296  @param[in] cmd command (twistedActor.BaseCmd) associated with this request;
297  state will be updated upon completion; None if no command is associated
298  """
299  try:
300  # print "updateCollimation"
301  if not self.tune.doCollimate:
302  msgCode, msgStr = formatDisabledProc(self.tune)
303  self.writeToUsers(msgCode, msgStr)
304  return
305 
306  alt = self.obj.targetMount[1].getPos(tcc.base.tai())
307  if not numpy.isfinite(alt):
308  # altitude unknown; don't bother
309  if cmd:
310  self.writeToUsers("w", 'Text="Cannot set collimation: target altitude unknown"', cmd=cmd)
311  cmd.setState(cmd.Done)
312  return
313 
314  # compute mirror commands (if this fails for any mirror, give up,
315  # since mirrors should be moved together)
316  mirNameCmdStrList = [] # list of mirror name: mirror command string
317  for mirName in self.mirrorDict.keys():
318  orient = computeOrient(mirName, alt, self.inst, self.weath)
319  cmdStr = 'move ' + ','.join(['%.4f' % (o,) for o in orient])
320  mirNameCmdStrList.append((mirName, cmdStr))
321 
322  # execute mirror commands
323  subCmdList = []
324  for mirName, mirCmdStr in mirNameCmdStrList:
325  mirCmd = self.mirrorDict[mirName].startCmd(mirCmdStr, timeLim=60)
326  subCmdList.append(mirCmd)
327 
328  if cmd:
329  if subCmdList:
330  # link the user command to the mirror move commands
331  LinkCommands(mainCmd = cmd, subCmdList = subCmdList)
332  else:
333  cmd.setState(cmd.Done)
334  else:
335  # no userCmd supplied, add callbacks to sub commands
336  # to inform users if they fail
337  for cmd in subCmdList:
338  cmd.addCallback(self._writeCollimationFailureToUsers, callNow=True)
339  finally:
340  if self.tune.doCollimate:
341  self.collimateTimer.start(self.tune.collimateInterval, self.updateCollimation)
342  else:
343  self.collimateTimer.cancel()
344 
345  def _writeCollimationFailureToUsers(self, cmd):
346  if cmd.didFail:
347  self.writeToUsers("w", "Text=\"Collimation Failure: %s\""%cmd.textMsg)
348 
349  def updateEarth(self):
350  """Update earth orientation predictions
351 
352  Load data for the current TAI from the latest data file into self.earth and schedule the next update.
353  """
354  try:
355  currTAI = tcc.base.tai()
356  with file(os.path.join(self.dataDir, "earthpred.dat")) as f:
357  self.earth.loadPredictions(f, forTAI=currTAI)
358  finally:
359  self.earthTimer.start(sec=EarthInterval, callFunc=self.updateEarth)
360 
361  def updateTracking(self, cmd=None):
362  """Compute next tracking position and send to axis controllers (slew or track).
363 
364  @param[in] cmd command (twistedActor.BaseCmd) associated with this request;
365  state will be updated upon completion; None if no command is associated
366  """
367  try:
368  # print "updateTracking(cmd=%s): axisCmdState=%s" % (cmd, self.obj.axisCmdState)
369  self.trackTimer.cancel()
370  if not self.obj.isMoving():
371  return
372 
373  startTAI = tcc.base.tai()
374  advTime = self.obj.updateTime - startTAI
375 
376  startMsgStr = "updateTracking started at TAI=%0.2f: advTime=%0.2f seconds" % (startTAI, advTime)
377  if advTime < 0.1: # perhaps add a value to the Tune block?
378  log.warn(startMsgStr)
379  else:
380  log.info(startMsgStr)
381 
382  if advTime < self.tune.trackAdvTime * 1.5:
383  # issue a tracking update
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))
388 
390  obj = self.obj,
391  doWrap=False,
392  doRestart=[False]*tcc.base.NAxes,
393  reportLim=False, # ???is this argument good for anything???
394  earth = self.earth,
395  inst = self.inst,
396  telMod = self.telMod,
397  axeLim = self.axeLim,
398  tai = self.obj.updateTime + self.tune.trackInterval,
399  )
400 
401  # check position, velocity and acceleration over the path segment
402  for i in range(3):
403  if not self.obj.targetMount[i].isfinite():
404  # axis is not moving; don't bother
405  continue
406 
407  if not oldTargetMount[i].isfinite():
408  # bug: tracking must have a known start and end PVT
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
414  continue
415 
416  if self.obj.axisErrCode[i] == tcc.base.AxisErr_OK:
417  self.obj.axisErrCode[i] = tcc.mov.checkSegment(
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,
423  doPos = True,
424  doVel = True,
425  doAccel = True,
426  doJerk = False,
427  axisLim = self.axeLim[i],
428  )
429 
430  # if axis has a problem then halt it
431  if self.obj.axisErrCode[i] != tcc.base.AxisErr_OK:
432  self.obj.targetMount[i].invalidate(self.obj.updateTime)
433 
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))
437 
438  # send MOVE PVT command (or MOVE if done halting) to axis devices
439  self.axisDevSet.movePVT(self.obj.targetMount, userCmd=cmd)
440 
441  # report any state change
442  msgCode, msgStr = formatAxisCmdStateAndErr(self.obj)
443  if msgStr:
444  self.writeToUsers(msgCode=msgCode, msgStr=msgStr, cmd=cmd)
445 
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")
449  else:
450  log.info("updateTracking called early enough that no tracking updated needed yet")
451 
452  if self.obj.isMoving():
453  endTAI = tcc.base.tai()
454  nextWakeTAI = self.obj.updateTime - self.tune.trackAdvTime
455  sleepSec = nextWakeTAI - endTAI
456  self.trackTimer.start(sec=sleepSec, callFunc=self.updateTracking)
457 
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)
461  if sleepSec < 0.1:
462  log.warn(endMsgStr)
463  else:
464  log.info(endMsgStr)
465  else:
466  endMsgStr = "updateTracking ends at TAI=%0.2f; execSec=%0.2f; no wakeup scheduled" % \
467  (endTAI, execSec)
468  log.info(endMsgStr)
469  except Exception as e:
470  traceback.print_exc(file=sys.stderr)
471 
472  # halt axes abruptly
473  if not self.slewCmd.isDone:
474  self.slewCmd.setState(self.slewCmd.Failed, "Slew failed: " + strFromException(e))
475 
476  self.axisDevSet.stop()
477 
478  def brdTelPos(self):
479  """Broadcast telescope position in a UDP packet
480  """
481  try:
482  # print "brdTelPos"
483  if not self.tune.doBrdTelPos:
484  msgCode, msgStr = formatDisabledProc(self.tune)
485  self.writeToUsers(msgCode, msgStr)
486  return
487 
488  binaryData = getBinaryForUDP(inst=self.inst, obj=self.obj)
489  self.udpSender.sendPacket(binaryData)
490  finally:
491  self.brdTelPosTimer.start(1., self.brdTelPos)
def formatPVTList
Format a sequence of PVTs.
def computeOrient
Compute the orientation of one mirror.
Definition: computeOrient.py:8
Actor for controlling a telescope.
Definition: tccActor.py:33
def showVersion
Show actor version.
Definition: tccActor.py:223
def formatDisabledProc
Format the DisabledProc keyword.