Commit ec442f7e4e3211cc018769e7d3f1ba62da88de5f

Authored by Alain Klotz
1 parent 120d4bfd
Exists in master

Debug components Focuser and MountPointing.

src/guitastro/component.py
... ... @@ -319,7 +319,39 @@ class Component(ComponentException):
319 319 categories = ['MountPointing', 'DetectorFocuser']
320 320  
321 321 # =====================================================================
322   - # properties
  322 + # init
  323 + # =====================================================================
  324 +
  325 + def __init__(self, *args, **kwargs):
  326 + # ---
  327 + self._queue = Queue()
  328 + self.database = ComponentDataBase(self)
  329 + self.database.start()
  330 + time.sleep(0.2)
  331 + # ---
  332 + self.init(*args, **kwargs)
  333 +
  334 + def init(self, *args, **kwargs):
  335 + self._my_init1(*args, **kwargs)
  336 + self._my_init2(*args, **kwargs)
  337 +
  338 + def _my_init1(self, *args, **kwargs):
  339 + # before instanciation of the simulator
  340 + pass
  341 +
  342 + def _my_init2(self, *args, **kwargs):
  343 + # after instanciation of the simulator to configure it.
  344 + pass
  345 +
  346 + # =====================================================================
  347 + # del
  348 + # =====================================================================
  349 +
  350 + def __del__(self):
  351 + self.database.stop()
  352 +
  353 + # =====================================================================
  354 + # properties: real, channel, category, action, verbose, log
323 355 # =====================================================================
324 356  
325 357 def _get_real(self)->bool:
... ... @@ -415,7 +447,7 @@ class Component(ComponentException):
415 447 log = property(_get_log, _set_log, _del_log)
416 448  
417 449 # =====================================================================
418   - # public methods
  450 + # prop
419 451 # =====================================================================
420 452  
421 453 def prop(self):
... ... @@ -432,6 +464,10 @@ class Component(ComponentException):
432 464 res['DO']['NOTHING'] = "Nothing to do"
433 465 return res
434 466  
  467 + # =====================================================================
  468 + # parameters (get/set param)
  469 + # =====================================================================
  470 +
435 471 def parameters(self, action="", operation=""):
436 472 """Get the list of operations for a given action or get the description of a given operation.
437 473  
... ... @@ -455,13 +491,17 @@ class Component(ComponentException):
455 491 value = self.prop()[action][operation]
456 492 return value
457 493  
  494 + # =====================================================================
  495 + # command (execute an action)
  496 + # =====================================================================
  497 +
458 498 def command(self, action:str, *args, **kwargs):
459 499 """General method to send command.
460 500  
461 501 Args:
462 502  
463 503 action: A str amongst elements of the list returned by the method prop
464   - *args: args[0] should be an operation
  504 + *args: args[0] should be an operation (type str)
465 505 **kwargs: Dictionary of options associated to the operation
466 506  
467 507 Returns:
... ... @@ -487,9 +527,6 @@ class Component(ComponentException):
487 527 result = self._set(*args, **kwargs)
488 528 return result
489 529  
490   - def init(self, *args, **kwargs):
491   - self._my_init1(*args, **kwargs)
492   - self._my_init2(*args, **kwargs)
493 530  
494 531 # =====================================================================
495 532 # protected methods
... ... @@ -521,6 +558,10 @@ class Component(ComponentException):
521 558 val = node_or_string
522 559 return val
523 560  
  561 + # =====================================================================
  562 + # fresult to manage log
  563 + # =====================================================================
  564 +
524 565 def _fresult(self, value):
525 566 """Formated result for returns
526 567 """
... ... @@ -533,6 +574,10 @@ class Component(ComponentException):
533 574 del(self.log)
534 575 return res
535 576  
  577 + # =====================================================================
  578 + # Action set
  579 + # =====================================================================
  580 +
536 581 def _set(self, *args, **kwargs):
537 582 args, kwargs = self._my_set(*args, **kwargs)
538 583 self._verify_nargs(2, *args)
... ... @@ -549,6 +594,10 @@ class Component(ComponentException):
549 594 def _my_set(self, *args, **kwargs):
550 595 return args, kwargs
551 596  
  597 + # =====================================================================
  598 + # Action get
  599 + # =====================================================================
  600 +
552 601 def _get(self, *args, **kwargs):
553 602 if len(args)==0:
554 603 return self.database.query()
... ... @@ -562,6 +611,10 @@ class Component(ComponentException):
562 611 def _my_get(self, *args, **kwargs):
563 612 return None
564 613  
  614 + # =====================================================================
  615 + # Action do
  616 + # =====================================================================
  617 +
565 618 def _do(self, *args, **kwargs):
566 619 result = None
567 620 self._verify_nargs(1, *args)
... ... @@ -581,31 +634,6 @@ class Component(ComponentException):
581 634 def _my_do(self, *args, **kwargs):
582 635 return None
583 636  
584   - def _my_init1(self, *args, **kwargs):
585   - # before instanciation of the simulator
586   - pass
587   -
588   - def _my_init2(self, *args, **kwargs):
589   - # after instanciation of the simulator to configure it.
590   - pass
591   -
592   - # =====================================================================
593   - # =====================================================================
594   - # Special methods
595   - # =====================================================================
596   - # =====================================================================
597   -
598   - def __init__(self, *args, **kwargs):
599   - # ---
600   - self._queue = Queue()
601   - self.database = ComponentDataBase(self)
602   - self.database.start()
603   - time.sleep(0.2)
604   - # ---
605   - self.init(*args, **kwargs)
606   -
607   - def __del__(self):
608   - self.database.stop()
609 637  
610 638 # #####################################################################
611 639 # #####################################################################
... ...
src/guitastro/component_detector_focuser.py
1   -
  1 +import time
2 2  
3 3 try:
4 4 from .home import Home
... ... @@ -62,6 +62,70 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
62 62 # methods
63 63 # =====================================================================
64 64  
  65 + # ------------ init
  66 +
  67 + def _my_init_params(self, param_optionals: dict, focuser_types: dict):
  68 + # --- This method could be overriden
  69 + return param_optionals, focuser_types
  70 +
  71 + def init(self, *args, **kwargs):
  72 + # --- Dico of optional parameters for all axis_types
  73 + param_optionals = {}
  74 + param_optionals["MODEL"] = (str, "")
  75 + param_optionals["MANUFACTURER"] = (str, "")
  76 + param_optionals["SERIAL_NUMBER"] = (str, "")
  77 + param_optionals["REAL"] = (bool, False)
  78 + param_optionals["DESCRIPTION"] = (str, "No description.")
  79 + param_optionals["SITE"] = (Siteobs,"GPS 0 E 45 100")
  80 + param_optionals["LIM_INF"] = (float,0)
  81 + param_optionals["LIM_SUP"] = (float,100000)
  82 + param_optionals["LOGO_FILE"] = (str,"")
  83 + param_optionals["CLIENT_DATA"] = (any,None)
  84 + param_optionals["INC_PER_MOTOR_REV"] = (float, 1000.0)
  85 + param_optionals["MM_PER_MOTOR_REV"] = (float, 2.0)
  86 + # --- Dico of axis_types and their parameters
  87 + focuser_types = {}
  88 + focuser_types["Z"]= {"MANDATORY" : {"NAME":[str,"Unknown"]}, "OPTIONAL" : {"LABEL":[str,"Detector focuser"]} }
  89 + # --- Add params and types when inherited
  90 + param_optionals, focuser_types = self._my_init_params(param_optionals, focuser_types)
  91 + # --- Decode args and kwargs parameters
  92 + self._focuser_params = self.decode_args_kwargs(0, focuser_types, param_optionals, *args, **kwargs)
  93 + # ===
  94 + self._mount_type = self._focuser_params["SELECTED_ARG"]
  95 + self._name = self._focuser_params["NAME"]
  96 + self._description = self._focuser_params["DESCRIPTION"]
  97 + self._model = self._focuser_params["MODEL"]
  98 + self._manufacturer = self._focuser_params["MANUFACTURER"]
  99 + self._serial_number= self._focuser_params["SERIAL_NUMBER"]
  100 + # === local
  101 + if str(type(self._focuser_params["SITE"])) == "<class 'siteobs.Siteobs'>":
  102 + self.siteobs = self._focuser_params["SITE"]
  103 + else:
  104 + self.siteobs = Home(self._focuser_params["SITE"])
  105 + # --- Update the database using the queue
  106 + param = {}
  107 + param["motion_simu"] = self.MOTION_STATE_UNKNOWN
  108 + param["motion_real"] = self.MOTION_STATE_UNKNOWN
  109 + param["unit"] = "inc" # inc, rot, lin, phy
  110 + param["target"] = 0 # unit
  111 + param["speed_slew"] = 100 # unit/s
  112 + self._queue.put(param)
  113 + time.sleep(0.02) # wait to be sure the thread has record the new value
  114 + # ---
  115 + self._inc_per_motor_rev = self._focuser_params["INC_PER_MOTOR_REV"]
  116 + self._mm_per_motor_rev = self._focuser_params["MM_PER_MOTOR_REV"]
  117 + # ---
  118 + self._my_init1(*args, **kwargs)
  119 + # --- Motor simulator
  120 + self._maxis = Motoraxis("LIN", name = "Focuser", inc_per_motor_rev=self._inc_per_motor_rev, inc0=0, senseinc=1, real=False, mm_per_motor_rev=self._mm_per_motor_rev)
  121 + self._maxis.define_phy = [self.nat2phy, self.phy2nat]
  122 + self._maxis.phy_unit = "inch"
  123 + # ---
  124 + self._my_init2(*args, **kwargs)
  125 + # ---
  126 +
  127 + # ------------ prop
  128 +
65 129 def prop(self):
66 130 """Component property concrete method
67 131 """
... ... @@ -74,66 +138,15 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
74 138 res['DO']['STOP'] = "Stop the focus motion"
75 139 return res
76 140  
77   - def val2inc(self, val:float):
78   - """Conversion coder from unit to increment
79   -
80   - Args:
81   -
82   - val: Value in unit defined by the variable 'unit'.
83   -
84   - Returns:
85   -
86   - Increments.
87   -
88   - """
89   - unit = self._get("unit")
90   - inc = val
91   - if unit=="phy":
92   - phy = val
93   - xnat2phy, xphy2nat = self._maxis.define_phy
94   - rot, lin = xphy2nat(phy)
95   - elif unit=="lin":
96   - lin = val
97   - rot = self._maxis.lin2rot(lin, self._maxis.SAVE_NONE)
98   - elif unit=="rot":
99   - rot = val
100   - if unit=="rot" or unit=="lin" or unit=="phy":
101   - inc = self._maxis.rot2inc(rot, self._maxis.SAVE_NONE)
102   - return inc
103   -
104   - def inc2val(self, inc:float):
105   - """Conversion coder from increment to unit
106   -
107   - Args:
108   -
109   - inc: Increment.
110   -
111   - Returns:
112   -
113   - Value in unit defined by the variable 'unit'.
114   -
115   - """
116   - unit = self._get("unit")
117   - val = inc
118   - if unit=="rot" or unit=="lin" or unit=="phy":
119   - rot = self._maxis.inc2rot(inc, self._maxis.SAVE_AS_SIMU)
120   - val = rot
121   - if unit=="lin" or unit=="phy":
122   - lin = self._maxis.rot2lin(rot, self._maxis.SAVE_AS_SIMU)
123   - val = lin
124   - if unit=="phy":
125   - xnat2phy, xphy2nat = self._maxis.define_phy
126   - self._maxis.physimu = xnat2phy(rot, lin)
127   - val = self._maxis.physimu
128   - return val
  141 + # ------------ do
129 142  
130 143 def _do_goto(self, *args, **kwargs):
131 144 operation = args[0].upper()
132 145 target_raw = self.database.query("target")
133 146 target = float(target_raw)
134 147 inc = self.val2inc(target)
135   - lim_inf = self._mount_params["LIM_INF"]
136   - lim_sup = self._mount_params["LIM_SUP"]
  148 + lim_inf = self._focuser_params["LIM_INF"]
  149 + lim_sup = self._focuser_params["LIM_SUP"]
137 150 if inc<lim_inf or inc>lim_sup:
138 151 texte = f"Operation {operation} cannot be done because the target asked is {inc} which is outside the limits {lim_inf} and {lim_sup}"
139 152 raise ComponentDetectorFocuserException(ComponentDetectorFocuserException.ERR_TARGET_OUTSIDE_LIMITS, texte)
... ... @@ -144,11 +157,13 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
144 157 self._maxis.simu_motion_start("SLEW", position=inc, velocity=velocity, frame='inc', drift=0)
145 158 self.database.query("motion_simu", self.MOTION_STATE_SLEWING)
146 159 # --- call the real
  160 + result = None
147 161 if self.real:
148 162 result = self._my_do(*args, **kwargs)
149 163 return result
150 164  
151 165 def _do_stop(self, *args, **kwargs):
  166 + result = None
152 167 operation = args[0].upper()
153 168 # --- make the simulation
154 169 self.log = f"start simulation of {operation}"
... ... @@ -181,11 +196,11 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
181 196 raise ComponentException(ComponentException.ERR_OPERATION_NOT_FOUND, msg)
182 197 # ---
183 198 if operation == "GOTO":
184   - result = self._do_goto(self, *args, **kwargs)
  199 + result = self._do_goto(*args, **kwargs)
185 200 elif operation == "STOP":
186   - result = self._do_stop(self, *args, **kwargs)
  201 + result = self._do_stop(*args, **kwargs)
187 202 elif operation == "COORD":
188   - result = self._do_coord(self, *args, **kwargs)
  203 + result = self._do_coord(*args, **kwargs)
189 204 else:
190 205 # --- only call the real method
191 206 self.log = f"start operation {operation}"
... ... @@ -197,15 +212,40 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
197 212 value = None
198 213 return value
199 214  
  215 + # ------------ get
  216 +
  217 + def _get_real_motions(self, *args, **kwargs):
  218 + # To be overriden
  219 + return self._get_simu_motions()
  220 +
  221 + def _get_simu_motions(self, *args, **kwargs):
  222 + self._maxis.simu_update_inc()
  223 + motion_simu = self._maxis.motion_state_simu
  224 + self._queue.put(f"motion_simu = {motion_simu}")
  225 + return motion_simu
  226 +
  227 + def _my_get_final(self, *args, **kwargs):
  228 + # This method should be overriden
  229 + pass
  230 +
200 231 def _my_get(self, *args, **kwargs):
201 232 key = args[0]
202 233 if key == "motion_simu":
203   - self._maxis.simu_update_inc()
204   - motion_simu = self._maxis.motion_state_simu
205   - self._queue.put(f"motion_simu = {motion_simu}")
206   - return motion_simu
  234 + motion = self._get_simu_motions()
  235 + return motion
  236 + elif key == "motion":
  237 + if self.real == True:
  238 + motion = self._get_real_motions()
  239 + else:
  240 + motion = self._get_simu_motions()
  241 + self._queue.put(f"motion = {motion}")
  242 + return motion
  243 + else:
  244 + self._my_get_final(*args, **kwargs)
207 245 return None
208 246  
  247 + # ------------ conversions physic <-> native
  248 +
209 249 def nat2phy(self, rot, lin, **kwargs):
210 250 # For Motoraxis
211 251 # Arguments are imposed
... ... @@ -219,63 +259,75 @@ class ComponentDetectorFocuser(ComponentDetectorFocuserException, Component, Gui
219 259 rot = lin * 360 / self._mm_per_motor_rev
220 260 return rot, lin
221 261  
222   - def _my_init_params(self, param_optionals: dict, mount_types: dict):
223   - return param_optionals, mount_types
  262 + # ------------ conversions value <-> increment
224 263  
225   - def init(self, *args, **kwargs):
226   - # --- Dico of optional parameters for all axis_types
227   - param_optionals = {}
228   - param_optionals["MODEL"] = (str, "")
229   - param_optionals["MANUFACTURER"] = (str, "")
230   - param_optionals["SERIAL_NUMBER"] = (str, "")
231   - param_optionals["REAL"] = (bool, False)
232   - param_optionals["DESCRIPTION"] = (str, "No description.")
233   - param_optionals["SITE"] = (Siteobs,"GPS 0 E 45 100")
234   - param_optionals["LIM_INF"] = (float,0)
235   - param_optionals["LIM_SUP"] = (float,100000)
236   - param_optionals["LOGO_FILE"] = (str,"")
237   - param_optionals["CLIENT_DATA"] = (any,None)
238   - param_optionals["INC_PER_MOTOR_REV"] = (float, 1000.0)
239   - param_optionals["MM_PER_MOTOR_REV"] = (float, 2.0)
240   - # --- Dico of axis_types and their parameters
241   - mount_types = {}
242   - mount_types["Z"]= {"MANDATORY" : {"NAME":[str,"Unknown"]}, "OPTIONAL" : {"LABEL":[str,"Detector focuser"]} }
243   - # --- Add params and types when inherited
244   - param_optionals, mount_types = self._my_init_params(param_optionals, mount_types)
245   - # --- Decode args and kwargs parameters
246   - self._mount_params = self.decode_args_kwargs(0, mount_types, param_optionals, *args, **kwargs)
247   - # ===
248   - self._mount_type = self._mount_params["SELECTED_ARG"]
249   - self._name = self._mount_params["NAME"]
250   - self._description = self._mount_params["DESCRIPTION"]
251   - self._model = self._mount_params["MODEL"]
252   - self._manufacturer = self._mount_params["MANUFACTURER"]
253   - self._serial_number= self._mount_params["SERIAL_NUMBER"]
254   - # === local
255   - if str(type(self._mount_params["SITE"])) == "<class 'siteobs.Siteobs'>":
256   - self.siteobs = self._mount_params["SITE"]
257   - else:
258   - self.siteobs = Home(self._mount_params["SITE"])
259   - # --- Update the database using the queue
260   - param = {}
261   - param["motion_simu"] = self.MOTION_STATE_UNKNOWN
262   - param["motion_real"] = self.MOTION_STATE_UNKNOWN
263   - param["unit"] = "inc" # inc, rot, lin, phy
264   - param["target"] = 0 # unit
265   - param["speed_slew"] = 100 # unit/s
266   - self._queue.put(param)
267   - # ---
268   - self._inc_per_motor_rev = self._mount_params["INC_PER_MOTOR_REV"]
269   - self._mm_per_motor_rev = self._mount_params["MM_PER_MOTOR_REV"]
270   - # ---
271   - self._my_init1(*args, **kwargs)
272   - # --- Motor simulator
273   - self._maxis = Motoraxis("LIN", name = "Focuser", inc_per_motor_rev=self._inc_per_motor_rev, inc0=0, senseinc=1, real=False, mm_per_motor_rev=self._mm_per_motor_rev)
274   - self._maxis.define_phy = [self.nat2phy, self.phy2nat]
275   - self._maxis.phy_unit = "inch"
276   - # ---
277   - self._my_init2(*args, **kwargs)
  264 + def val2inc(self, val:float):
  265 + """Conversion coder from unit to increment
278 266  
  267 + Args:
  268 +
  269 + val: Value in unit defined by the variable 'unit'.
  270 +
  271 + Returns:
  272 +
  273 + Increments.
  274 +
  275 + """
  276 + unit = self._get("unit")
  277 + inc = val
  278 + if unit=="phy":
  279 + phy = val
  280 + xnat2phy, xphy2nat = self._maxis.define_phy
  281 + rot, lin = xphy2nat(phy)
  282 + elif unit=="lin":
  283 + lin = val
  284 + rot = self._maxis.lin2rot(lin, self._maxis.SAVE_NONE)
  285 + elif unit=="rot":
  286 + rot = val
  287 + if unit=="rot" or unit=="lin" or unit=="phy":
  288 + inc = self._maxis.rot2inc(rot, self._maxis.SAVE_NONE)
  289 + return inc
  290 +
  291 + def inc2val(self, inc:float):
  292 + """Conversion coder from increment to unit
  293 +
  294 + Args:
  295 +
  296 + inc: Increment.
  297 +
  298 + Returns:
  299 +
  300 + Value in unit defined by the variable 'unit'.
  301 +
  302 + """
  303 + unit = self._get("unit")
  304 + val = inc
  305 + if unit=="rot" or unit=="lin" or unit=="phy":
  306 + rot = self._maxis.inc2rot(inc, self._maxis.SAVE_AS_SIMU)
  307 + val = rot
  308 + if unit=="lin" or unit=="phy":
  309 + lin = self._maxis.rot2lin(rot, self._maxis.SAVE_AS_SIMU)
  310 + val = lin
  311 + if unit=="phy":
  312 + xnat2phy, xphy2nat = self._maxis.define_phy
  313 + self._maxis.physimu = xnat2phy(rot, lin)
  314 + val = self._maxis.physimu
  315 + return val
  316 +
  317 + # ------------ conversion int -> string for human output
  318 +
  319 + def motion2string(self, motion:int, verb:bool= False):
  320 + if motion == self.MOTION_STATE_NOMOTION:
  321 + msg = "stoped" if verb else "stop"
  322 + elif motion == self.MOTION_STATE_SLEWING:
  323 + msg = "slewing" if verb else "slew"
  324 + elif motion == self.MOTION_STATE_DRIFTING:
  325 + msg = "drifting" if verb else "drift"
  326 + elif motion == self.MOTION_STATE_MOVING:
  327 + msg = "moving" if verb else "move"
  328 + else:
  329 + msg = "unknown" if verb else "unk"
  330 + return msg
279 331  
280 332 # #####################################################################
281 333 # #####################################################################
... ...
src/guitastro/component_mount_pointing.py
... ... @@ -104,12 +104,13 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
104 104 SAVE_ALL = 3
105 105  
106 106 # =====================================================================
  107 + # methods
107 108 # =====================================================================
108   - # General methods overriden
109   - # =====================================================================
110   - # =====================================================================
  109 +
  110 + # ------------ init
111 111  
112 112 def _my_init_params(self, param_optionals: dict, mount_types: dict):
  113 + # --- This method could be overriden
113 114 return param_optionals, mount_types
114 115  
115 116 def init(self, *args, **kwargs):
... ... @@ -252,6 +253,8 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
252 253 param['pierside'] = Mountaxis.PIERSIDE_AUTO
253 254 self._queue.put(param)
254 255  
  256 + # ------------ prop
  257 +
255 258 def prop(self):
256 259 """Component property concrete method
257 260 """
... ... @@ -270,67 +273,7 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
270 273 res['DO']['STOP'] = "Stop the mount motions"
271 274 return res
272 275  
273   - def travel_compute(self, inc_start:dict, inc_target:dict)->tuple:
274   - """Compute the duration of a slewing from the current position to a target.
275   -
276   - Args:
277   -
278   - inc_start: Dictionary of increments at start position
279   - inc_target: Dictionary of increments at end position
280   -
281   - Returns:
282   -
283   - delay_slew_maxi: A float value of the computed time travel
284   - delay_slew: A dictionary of the computed time travel for each axis. The key is the axis symbol.
285   - """
286   - # --- delta incs for the travel (signed incs)
287   - delay_slew = {}
288   - delay_slew_maxi = 0
289   - for axis in self.axis:
290   - symbol = axis.symbol
291   - if axis.real:
292   - key = "real"
293   - else:
294   - key = "simu"
295   - dinc = inc_target[key][symbol] - inc_start[key][symbol]
296   - inc_per_sec = axis.slew_deg_per_sec * axis.inc_per_deg
297   - delay_slew[symbol] = abs(dinc / inc_per_sec)
298   - if delay_slew[symbol] > delay_slew_maxi:
299   - delay_slew_maxi = delay_slew[symbol]
300   - return delay_slew_maxi, delay_slew
301   -
302   - def _modpoi_apply(self, cel:dict , rot:dict, sense=1)->tuple:
303   - """Corrige celestial coordinates of the pointing model.
304   -
305   - Args:
306   -
307   - cel: Input celestial coordinates
308   - rot: Input rotation coordinates for pierside
309   - sense: =1 for apparent to tel. -1 for tel to apparent.
310   -
311   - Returns:
312   -
313   - cel: Output celestial coordinates
314   -
315   - Mountaxis.PIERSIDE_POS1 for modpoi regular.
316   - Mountaxis.PIERSIDE_POS2 for modpoi flipped.
317   - """
318   - # ---
319   - if self._apply_model:
320   - simureal = 'real' if self.real else 'simu'
321   - b, p = cel[simureal]['b'], cel[simureal]['p']
322   - pierside = rot[simureal]['pierside']
323   - db, dp = (0, 0)
324   - if p<88 and p>-88:
325   - if pierside == Mountaxis.PIERSIDE_POS1:
326   - db, dp = self.modpoi_regular.coefs2ddata(b, p)
327   - elif pierside == Mountaxis.PIERSIDE_POS2:
328   - db, dp = self.modpoi_flipped.coefs2ddata(b, p)
329   - if sense == 1 or sense == -1:
330   - b = b - sense * db / 60.0
331   - p = p - sense * dp / 60.0
332   - cel['b'], cel['p'] = b, p
333   - return cel
  276 + # ------------ do
334 277  
335 278 def _do_goto(self, *args, **kwargs):
336 279 """Start a slewing motion to a target for GOTO and TRAJECTORY
... ... @@ -588,6 +531,8 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
588 531 value = None
589 532 return value
590 533  
  534 + # ------------ set
  535 +
591 536 def _my_set(self, *args, **kwargs):
592 537 key = args[0]
593 538 if key == "speed_slew":
... ... @@ -616,41 +561,96 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
616 561 k += 1
617 562 return args, kwargs
618 563  
  564 + # ------------ get
  565 +
  566 + def _get_real_motions(self, *args, **kwargs):
  567 + # To be overriden
  568 + return self._get_simu_motions()
  569 +
  570 + def _get_simu_motions(self, *args, **kwargs):
  571 + slewing = 0
  572 + drifting = 0
  573 + moving = 0
  574 + param = {} # for motion_simu
  575 + param_dict = {} # for motions_simu
  576 + for axis in self.axis:
  577 + axis.simu_update_inc()
  578 + motion = axis.motion_state_simu
  579 + # ---
  580 + param_dict["motion_"+axis.axis_type+"_simu"] = motion
  581 + # ---
  582 + slewing += 1 if motion == self.MOTION_STATE_SLEWING else 0
  583 + drifting += 1 if motion == self.MOTION_STATE_DRIFTING else 0
  584 + moving += 1 if motion == self.MOTION_STATE_MOVING else 0
  585 + # --- Global motion or dict
  586 + motion = self.MOTION_STATE_NOMOTION
  587 + if slewing>0:
  588 + motion = self.MOTION_STATE_SLEWING
  589 + elif moving>0:
  590 + motion = self.MOTION_STATE_MOVING
  591 + elif drifting>0:
  592 + motion = self.MOTION_STATE_DRIFTING
  593 + param["motion_simu"] = motion
  594 + return param_dict, motion
  595 +
  596 + def _my_get_final(self, *args, **kwargs):
  597 + # This method should be overriden
  598 + pass
  599 +
619 600 def _my_get(self, *args, **kwargs):
620 601 key = args[0]
  602 + param = {}
621 603 if key == "motion_simu" or key == "motions_simu":
622   - # --- Motion of each axis
623   - slewing = 0
624   - drifting = 0
625   - moving = 0
626   - param = {} # for motion_simu
627   - param_dict = {} # for motions_simu
628   - for axis in self.axis:
629   - axis.simu_update_inc()
630   - motion = axis.motion_state_simu
631   - if key == "motions_simu":
632   - param_dict["motion_"+axis.axis_type+"_simu"] = motion
633   - else:
634   - param["motion_"+axis.symbol+"_simu"] = motion
635   - slewing += 1 if motion == self.MOTION_STATE_SLEWING else 0
636   - drifting += 1 if motion == self.MOTION_STATE_DRIFTING else 0
637   - moving += 1 if motion == self.MOTION_STATE_MOVING else 0
638   - # --- Global motion or dict
  604 + param_dict, motion = self._get_simu_motions()
639 605 if key == "motions_simu":
640 606 motion = param_dict
641 607 param["motions_simu"] = param_dict
642 608 else:
643   - motion = self.MOTION_STATE_NOMOTION
644   - if slewing>0:
645   - motion = self.MOTION_STATE_SLEWING
646   - elif moving>0:
647   - motion = self.MOTION_STATE_MOVING
648   - elif drifting>0:
649   - motion = self.MOTION_STATE_DRIFTING
650 609 param["motion_simu"] = motion
651 610 # --- Update database
652 611 self._queue.put(param)
653 612 return motion
  613 + elif key == "motion" or key == "motions":
  614 + slewing = 0
  615 + drifting = 0
  616 + moving = 0
  617 + # --- default values are from simu
  618 + param_dict, motion = self._get_simu_motions()
  619 + for axis in self.axis:
  620 + param_dict["motion_"+axis.axis_type] = param_dict["motion_"+axis.axis_type+"_simu"]
  621 + #print(f"A {param_dict=:}")
  622 + # --- replace values by real if possible
  623 + if self.real == True:
  624 + param_dict_real, motion_real = self._get_real_motions()
  625 + for axis in self.axis:
  626 + if axis.real:
  627 + param_dict["motion_"+axis.axis_type] = param_dict_real["motion_"+axis.axis_type+"_real"]
  628 + #print(f"B {param_dict=:}")
  629 + # --- mix values
  630 + for axis in self.axis:
  631 + # ---
  632 + motion = param_dict["motion_"+axis.axis_type]
  633 + # ---
  634 + slewing += 1 if motion == self.MOTION_STATE_SLEWING else 0
  635 + drifting += 1 if motion == self.MOTION_STATE_DRIFTING else 0
  636 + moving += 1 if motion == self.MOTION_STATE_MOVING else 0
  637 + # --- Global motion or dict
  638 + motion = self.MOTION_STATE_NOMOTION
  639 + if slewing>0:
  640 + motion = self.MOTION_STATE_SLEWING
  641 + elif moving>0:
  642 + motion = self.MOTION_STATE_MOVING
  643 + elif drifting>0:
  644 + motion = self.MOTION_STATE_DRIFTING
  645 + param = {}
  646 + param["motion"] = motion
  647 + param["motions"] = param_dict
  648 + #print(f"C {param=:}")
  649 + # --- Update database
  650 + self._queue.put(param)
  651 + return motion
  652 + else:
  653 + self._my_get_final(*args, **kwargs)
654 654 return None
655 655  
656 656 # =====================================================================
... ... @@ -676,49 +676,7 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
676 676 # =====================================================================
677 677 # =====================================================================
678 678  
679   - def compute_pier_target(self, celb_target:float, pierside_start:int, imposed_side:int=Mountaxis.PIERSIDE_AUTO):
680   - """
681   - Compute the predicted side of pier for the given ha,dec coordinates
682   -
683   - :param celb_target: Base angle target (unit is degrees).
684   - :type celb_target: float
685   - :param pierside_start: Current side
686   - :type pierside_start: int
687   - :param imposed_side: Side if it is imposed
688   - :type imposed_side: int
689   -
690   - :returns: Pier side
691   - :rtype: int
692   -
693   - * Pier side : Integer to indicate the back flip action:
694   -
695   - * PIERSIDE_POS1 (=1) pointing in normal position
696   - * PIERSIDE_POS2 (=-1) pointing in back flip position
697   -
698   - """
699   - # --- compute the target pierside
700   - if self._mount_params.get("CAN_REVERSE")==True:
701   - lim_side_east = self._mount_params.get("LIME_REVERSE") ; # Tube west = PIERSIDE_POS1 = [-180 : lim_side_east]
702   - lim_side_west = self._mount_params.get("LIMW_REVERSE") ; # Tube east = PIERSIDE_POS2 = [lim_side_west : +180]
703   - if imposed_side == Mountaxis.PIERSIDE_AUTO:
704   - if celb_target>lim_side_west and celb_target<lim_side_east:
705   - # --- the target position is in the both possibilitiy range
706   - pierside_target = pierside_start
707   - else:
708   - if celb_target>lim_side_east:
709   - # --- the target is after the limit of side=PIERSIDE_POS1
710   - pierside_target = Mountaxis.PIERSIDE_POS2
711   - else:
712   - # --- the target is before the limit of side=PIERSIDE_POS2
713   - pierside_target = Mountaxis.PIERSIDE_POS1
714   - else:
715   - pierside_target = imposed_side
716   - else:
717   - if pierside_start == Mountaxis.PIERSIDE_AUTO:
718   - pierside_target = Mountaxis.PIERSIDE_POS1
719   - else:
720   - pierside_target = pierside_start
721   - return pierside_target
  679 + # ------------ conversions target/astro <-> cel
722 680  
723 681 def cel2astro(self, cel:dict, simureal:dict= {'b':'simu', 'p':'simu'})->dict:
724 682 jd = cel['header']['jd']
... ... @@ -789,23 +747,6 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
789 747 raise ComponentMountPointingException(ComponentMountPointingException.ERR_MOUNT_TYPE_NOT_SUPPORTED, msg)
790 748 return cel
791 749  
792   - def _select_axis(self, symbol:str)->Mountaxis:
793   - """Return the Mountaxis object selected from its rotation symbol
794   -
795   - Args:
796   -
797   - symbol: Rotation symbol must amongst 'b', 'p', 'r'
798   -
799   - Returns:
800   -
801   - Mountaxis object corresponding to the input symbol
802   - """
803   - for axis in self.axis:
804   - if axis == None:
805   - continue
806   - if symbol == axis.symbol:
807   - return axis
808   -
809 750 def cel2inc(self, cel:dict, pierside:int, save:int)->tuple:
810 751 """Conversion from celestial mount coordinates (Cel) to rotation coordinates (Rot).
811 752  
... ... @@ -881,6 +822,65 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
881 822 # ---
882 823 return rot, inc
883 824  
  825 + # ------------ conversion inc -> cel
  826 +
  827 + def inc2cel(self, incsimu:dict=None, save:int=Mountaxis.SAVE_NONE)->tuple:
  828 + """Conversion from reading increments to rotation and celestial coordinates (Rot, Cel).
  829 +
  830 + The complete conversion chain is: Inc->Rot->Cel-Astro
  831 + This method makes conversion Inc->Cel
  832 +
  833 + The Rot system is cardinaly oriented to avoid singularities.
  834 +
  835 + """
  836 + inc = self.read_inc(incsimu)
  837 + #print(f"{inc=:}")
  838 + # --- rot dict
  839 + rot = {}
  840 + rot['header'] = {}
  841 + rot['header']['jd'] = inc['header']['jd']
  842 + rot['simu'] = {}
  843 + rot['real'] = {}
  844 + # --- cel dict
  845 + cel = {}
  846 + cel['header'] = {}
  847 + cel['header']['jd'] = inc['header']['jd']
  848 + cel['simu'] = {}
  849 + cel['real'] = {}
  850 + # --- save for simulations
  851 + if save==self.SAVE_ALL or save==self.SAVE_AS_SIMU:
  852 + savesimu = self.SAVE_AS_SIMU
  853 + else:
  854 + savesimu = self.SAVE_NONE
  855 + # --- save for real
  856 + if save==self.SAVE_ALL or save==self.SAVE_AS_REAL:
  857 + savereal = self.SAVE_AS_REAL
  858 + else:
  859 + savereal = self.SAVE_NONE
  860 + # --- loop over the mount axes
  861 + # --- First axe must be 'p' to determine the pierside
  862 + for axis in self.axes_first_p():
  863 + axis_symbol = axis.symbol
  864 + # --- update for simulations
  865 + rotsimu, pierside = axis.inc2rot(inc['simu'][axis_symbol], savesimu)
  866 + if axis_symbol == 'p':
  867 + piersidesimu = pierside
  868 + celsimu = axis.rot2cel(rotsimu, piersidesimu, savesimu)
  869 + rot['simu'][axis_symbol] = rotsimu
  870 + cel['simu'][axis_symbol] = celsimu
  871 + # --- update for real
  872 + rotreal, pierside = axis.inc2rot(inc['real'][axis_symbol], savereal)
  873 + if axis_symbol == 'p':
  874 + piersidereal = pierside
  875 + celreal = axis.rot2cel(rotreal, piersidereal, savereal)
  876 + rot['real'][axis_symbol] = rotreal
  877 + cel['real'][axis_symbol] = celreal
  878 + rot['simu']['pierside'] = piersidesimu
  879 + rot['real']['pierside'] = piersidereal
  880 + return inc, rot, cel
  881 +
  882 + # ------------ read inc
  883 +
884 884 def _my_read_inc(self, inc:dict)->dict:
885 885 """Read increments of encoders
886 886  
... ... @@ -937,6 +937,8 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
937 937 inc = self._my_read_inc(inc)
938 938 return inc
939 939  
  940 + # ------------ get the axis object from its axis symbol
  941 +
940 942 def symbol2axis(self, symbol:str)->object:
941 943 """Return the axis object from the symbol
942 944  
... ... @@ -946,6 +948,8 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
946 948 if symbol == axis.symbol:
947 949 return axis
948 950  
  951 + # ------------ sort the object axis list to have 'p' first
  952 +
949 953 def axes_first_p(self)->list:
950 954 """Return the axis objects list with symbol 'p' first
951 955  
... ... @@ -961,60 +965,119 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
961 965 axiss.append(axis)
962 966 return axiss
963 967  
964   - def inc2cel(self, incsimu:dict=None, save:int=Mountaxis.SAVE_NONE)->tuple:
965   - """Conversion from reading increments to rotation and celestial coordinates (Rot, Cel).
  968 + # ------------ get the predicted pierside
966 969  
967   - The complete conversion chain is: Inc->Rot->Cel-Astro
968   - This method makes conversion Inc->Cel
  970 + def compute_pier_target(self, celb_target:float, pierside_start:int, imposed_side:int=Mountaxis.PIERSIDE_AUTO):
  971 + """
  972 + Compute the predicted side of pier for the given ha,dec coordinates
969 973  
970   - The Rot system is cardinaly oriented to avoid singularities.
  974 + :param celb_target: Base angle target (unit is degrees).
  975 + :type celb_target: float
  976 + :param pierside_start: Current side
  977 + :type pierside_start: int
  978 + :param imposed_side: Side if it is imposed
  979 + :type imposed_side: int
  980 +
  981 + :returns: Pier side
  982 + :rtype: int
  983 +
  984 + * Pier side : Integer to indicate the back flip action:
  985 +
  986 + * PIERSIDE_POS1 (=1) pointing in normal position
  987 + * PIERSIDE_POS2 (=-1) pointing in back flip position
971 988  
972 989 """
973   - inc = self.read_inc(incsimu)
974   - #print(f"{inc=:}")
975   - # --- rot dict
976   - rot = {}
977   - rot['header'] = {}
978   - rot['header']['jd'] = inc['header']['jd']
979   - rot['simu'] = {}
980   - rot['real'] = {}
981   - # --- cel dict
982   - cel = {}
983   - cel['header'] = {}
984   - cel['header']['jd'] = inc['header']['jd']
985   - cel['simu'] = {}
986   - cel['real'] = {}
987   - # --- save for simulations
988   - if save==self.SAVE_ALL or save==self.SAVE_AS_SIMU:
989   - savesimu = self.SAVE_AS_SIMU
990   - else:
991   - savesimu = self.SAVE_NONE
992   - # --- save for real
993   - if save==self.SAVE_ALL or save==self.SAVE_AS_REAL:
994   - savereal = self.SAVE_AS_REAL
  990 + # --- compute the target pierside
  991 + if self._mount_params.get("CAN_REVERSE")==True:
  992 + lim_side_east = self._mount_params.get("LIME_REVERSE") ; # Tube west = PIERSIDE_POS1 = [-180 : lim_side_east]
  993 + lim_side_west = self._mount_params.get("LIMW_REVERSE") ; # Tube east = PIERSIDE_POS2 = [lim_side_west : +180]
  994 + if imposed_side == Mountaxis.PIERSIDE_AUTO:
  995 + if celb_target>lim_side_west and celb_target<lim_side_east:
  996 + # --- the target position is in the both possibilitiy range
  997 + pierside_target = pierside_start
  998 + else:
  999 + if celb_target>lim_side_east:
  1000 + # --- the target is after the limit of side=PIERSIDE_POS1
  1001 + pierside_target = Mountaxis.PIERSIDE_POS2
  1002 + else:
  1003 + # --- the target is before the limit of side=PIERSIDE_POS2
  1004 + pierside_target = Mountaxis.PIERSIDE_POS1
  1005 + else:
  1006 + pierside_target = imposed_side
995 1007 else:
996   - savereal = self.SAVE_NONE
997   - # --- loop over the mount axes
998   - # --- First axe must be 'p' to determine the pierside
999   - for axis in self.axes_first_p():
1000   - axis_symbol = axis.symbol
1001   - # --- update for simulations
1002   - rotsimu, pierside = axis.inc2rot(inc['simu'][axis_symbol], savesimu)
1003   - if axis_symbol == 'p':
1004   - piersidesimu = pierside
1005   - celsimu = axis.rot2cel(rotsimu, piersidesimu, savesimu)
1006   - rot['simu'][axis_symbol] = rotsimu
1007   - cel['simu'][axis_symbol] = celsimu
1008   - # --- update for real
1009   - rotreal, pierside = axis.inc2rot(inc['real'][axis_symbol], savereal)
1010   - if axis_symbol == 'p':
1011   - piersidereal = pierside
1012   - celreal = axis.rot2cel(rotreal, piersidereal, savereal)
1013   - rot['real'][axis_symbol] = rotreal
1014   - cel['real'][axis_symbol] = celreal
1015   - rot['simu']['pierside'] = piersidesimu
1016   - rot['real']['pierside'] = piersidereal
1017   - return inc, rot, cel
  1008 + if pierside_start == Mountaxis.PIERSIDE_AUTO:
  1009 + pierside_target = Mountaxis.PIERSIDE_POS1
  1010 + else:
  1011 + pierside_target = pierside_start
  1012 + return pierside_target
  1013 +
  1014 + # ------------ get the predicted time of slewing
  1015 +
  1016 + def travel_compute(self, inc_start:dict, inc_target:dict)->tuple:
  1017 + """Compute the duration of a slewing from the current position to a target.
  1018 +
  1019 + Args:
  1020 +
  1021 + inc_start: Dictionary of increments at start position
  1022 + inc_target: Dictionary of increments at end position
  1023 +
  1024 + Returns:
  1025 +
  1026 + delay_slew_maxi: A float value of the computed time travel
  1027 + delay_slew: A dictionary of the computed time travel for each axis. The key is the axis symbol.
  1028 + """
  1029 + # --- delta incs for the travel (signed incs)
  1030 + delay_slew = {}
  1031 + delay_slew_maxi = 0
  1032 + for axis in self.axis:
  1033 + symbol = axis.symbol
  1034 + if axis.real:
  1035 + key = "real"
  1036 + else:
  1037 + key = "simu"
  1038 + dinc = inc_target[key][symbol] - inc_start[key][symbol]
  1039 + inc_per_sec = axis.slew_deg_per_sec * axis.inc_per_deg
  1040 + delay_slew[symbol] = abs(dinc / inc_per_sec)
  1041 + if delay_slew[symbol] > delay_slew_maxi:
  1042 + delay_slew_maxi = delay_slew[symbol]
  1043 + return delay_slew_maxi, delay_slew
  1044 +
  1045 + # ------------ apply the pointing model
  1046 +
  1047 + def _modpoi_apply(self, cel:dict , rot:dict, sense=1)->tuple:
  1048 + """Corrige celestial coordinates of the pointing model.
  1049 +
  1050 + Args:
  1051 +
  1052 + cel: Input celestial coordinates
  1053 + rot: Input rotation coordinates for pierside
  1054 + sense: =1 for apparent to tel. -1 for tel to apparent.
  1055 +
  1056 + Returns:
  1057 +
  1058 + cel: Output celestial coordinates
  1059 +
  1060 + Mountaxis.PIERSIDE_POS1 for modpoi regular.
  1061 + Mountaxis.PIERSIDE_POS2 for modpoi flipped.
  1062 + """
  1063 + # ---
  1064 + if self._apply_model:
  1065 + simureal = 'real' if self.real else 'simu'
  1066 + b, p = cel[simureal]['b'], cel[simureal]['p']
  1067 + pierside = rot[simureal]['pierside']
  1068 + db, dp = (0, 0)
  1069 + if p<88 and p>-88:
  1070 + if pierside == Mountaxis.PIERSIDE_POS1:
  1071 + db, dp = self.modpoi_regular.coefs2ddata(b, p)
  1072 + elif pierside == Mountaxis.PIERSIDE_POS2:
  1073 + db, dp = self.modpoi_flipped.coefs2ddata(b, p)
  1074 + if sense == 1 or sense == -1:
  1075 + b = b - sense * db / 60.0
  1076 + p = p - sense * dp / 60.0
  1077 + cel['b'], cel['p'] = b, p
  1078 + return cel
  1079 +
  1080 + # ------------ conversion int -> string for human output
1018 1081  
1019 1082 def motion2string(self, motion:int, verb:bool= False):
1020 1083 if motion == self.MOTION_STATE_NOMOTION:
... ...
src/guitastro/device.py
... ... @@ -5,6 +5,11 @@ import shlex
5 5 #from atexist import register
6 6  
7 7 try:
  8 + from .siteobs import Siteobs
  9 +except:
  10 + from siteobs import Siteobs
  11 +
  12 +try:
8 13 # guitastro is installed with setup.py
9 14 from guitastro import Communication, GuitastroException, GuitastroTools, Ephemeris
10 15 except:
... ... @@ -262,6 +267,12 @@ class Device(DeviceException, GuitastroTools):
262 267 # =====================================================================
263 268 # =====================================================================
264 269  
  270 + def _my_init_params(self, params_optional: dict, unit_types: dict):
  271 + return params_optional, unit_types
  272 +
  273 + def _my_init_final(self, *args, **kwargs):
  274 + pass
  275 +
265 276 def __init__(self, *args, **kwargs):
266 277 """
267 278 Conversion from Uniform Python object into protocol language
... ... @@ -280,21 +291,27 @@ class Device(DeviceException, GuitastroTools):
280 291 params_optional["SERIAL_NUMBER"] = (str, "")
281 292 params_optional["REAL"] = (bool, False)
282 293 params_optional["DESCRIPTION"] = (str, "Just an abstract class. No component composed.")
  294 + params_optional["SITE"] = (Siteobs, "GPS 0 E 45 100")
283 295 # --- Dico of unit_types and their parameters
284 296 unit_types = {}
285 297 # --- unit choice
286   - unit_types["NOTHING"] = {"MANDATORY" : {"TRANSPORT":(str,"SERIAL")}, "OPTIONAL" : {"HOST":[str,"192.168.0.1"], "PORT":[int,1025]} }
  298 + unit_types["NOTHING"] = {"MANDATORY" : {"TRANSPORT":(str,"SERIAL")}, "OPTIONAL" : {"HOST":[str,"192.168.0.1"], "PORT":[int,1025], "BAUD":[int,115200]} }
  299 + # --- Add params and types when inherited
  300 + params_optional, unit_types = self._my_init_params(params_optional, unit_types)
287 301 # --- Decode args and kwargs parameters
288 302 self._unit_params = self.decode_args_kwargs(0, unit_types, params_optional, *args, **kwargs)
  303 + # === Observatory location for ephemeris
  304 + if str(type(self._unit_params["SITE"])) == "<class 'siteobs.Siteobs'>":
  305 + self.siteobs = self._unit_params["SITE"]
  306 + else:
  307 + self.siteobs = Siteobs(self._unit_params["SITE"])
289 308 # ===
290 309 self.unit_type = self._unit_params["SELECTED_ARG"]
291   - # --- init ephemeris
292   - eph = Ephemeris()
293   - eph.set_home("148")
294 310 # === Instanciate components of the device
295 311 # This is a composition of Component classes
296 312 self._comp = {}
297 313 # in a concrete class you have to add components here
  314 + self._my_init_final(self, *args, **kwargs)
298 315  
299 316 #@register
300 317 def __del__(self):
... ...
src/guitastro/ephemeris.py
... ... @@ -91,7 +91,8 @@ class Ephemeris(EphemerisException, GuitastroTools):
91 91 target = "sun"
92 92 eph.set_home("guitalens")
93 93 date = "Now"
94   - ra, dec, equinox, epoch = eph.radec(target, unit_ra="H0.2", unit_dec="d+090.1")
  94 + ephem = eph.radec(target, unit_ra="H0.2", unit_dec="d+090.1")
  95 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
95 96 print(f"{name} ra={ra} dec={dec}")
96 97  
97 98 To get the drift:
... ... @@ -99,16 +100,20 @@ class Ephemeris(EphemerisException, GuitastroTools):
99 100 target = "sun"
100 101 eph.set_home("guitalens")
101 102 date = "Now"
102   - ra, dec, equinox, epoch, dra, ddec = eph.radec_speed(target, unit_ra="H0.2", unit_dec="d+090.1")
  103 + ephem = eph.radec_speed(target, unit_ra="H0.2", unit_dec="d+090.1")
  104 + ra, dec, equinox, epoch, dra, ddec = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd'], ephem['dra_equinox'], ephem['ddec_equinox']
103 105 print(f"{name} ra={ra} dec={dec} dra={dra} ddec={ddec}")
104 106  
  107 + Note the output of the method radec is ephemeris dictionary.
  108 +
105 109 Other examples of inputs:
106 110  
107 111 ::
108 112  
109 113 eph.set_home("GPS 2.567 E -21.456 1205")
110 114 date = "2022-01-31T22:34:15"
111   - ra, dec, equinox, epoch = eph.radec(target, unit_ra="H0.2", unit_dec="d+090.1")
  115 + ephem = eph.radec(target, unit_ra="H0.2", unit_dec="d+090.1")
  116 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
112 117  
113 118 The target is a major planet of the solar system (+ Moon and Sun):
114 119  
... ... @@ -161,9 +166,11 @@ class Ephemeris(EphemerisException, GuitastroTools):
161 166 ::
162 167  
163 168 target = "CELESTRACK ISS"
164   - ra, dec, dra, ddec = eph.radec_speed(target, date="now")
  169 + ephem = eph.radec_speed(target, date="now")
  170 + ra, dec, equinox, epoch, dra, ddec = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd'], ephem['dra_equinox'], ephem['ddec_equinox']
165 171 print(f"{name} ra={ra} dec={dec} dra={dra*3600:.3f} ddec={ddec*3600:.3f}")
166 172  
  173 +
167 174 The target is a name which can be solved by SGP4 and its TLE.
168 175  
169 176 ::
... ... @@ -172,7 +179,8 @@ class Ephemeris(EphemerisException, GuitastroTools):
172 179 tle = f"TLE {name}\\n"
173 180 tle += "1 40544U 15017A 22029.59832018 -.00000064 00000+0 00000+0 0 9991\\n"
174 181 tle += "2 40544 56.7391 25.1641 0005059 269.6475 90.3121 1.70475734 42591\\n"
175   - ra, dec, equinox, epoch, dra, ddec = eph.radec_speed(tle)
  182 + ephem = eph.radec_speed(tle)
  183 + ra, dec, equinox, epoch, dra, ddec = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd'], ephem['dra_equinox'], ephem['ddec_equinox']
176 184 print(f"{name} ra={ra:.6f} dec={dec:.6f} dra={dra*3600:.5f} ddec={ddec*3600:.5f}")
177 185  
178 186 """
... ... @@ -376,6 +384,9 @@ class Ephemeris(EphemerisException, GuitastroTools):
376 384 def gcnc_download(self, name):
377 385 """Download the gcnc files if needed.
378 386 From https://gcn.gsfc.nasa.gov/selected.html
  387 + Valid only from 020305 to 230414B
  388 + TODO https://github.com/nasa-gcn/gcn-kafka-python for earliests.
  389 + or https://gcn.nasa.gov/docs/contributing
379 390 """
380 391 equinox = "J2000"
381 392 sra = "0"
... ...
src/guitastro/maps.py
... ... @@ -1045,7 +1045,8 @@ class Maps(MapsException, GuitastroTools):
1045 1045 eph.set_home(config['home'])
1046 1046 odate = Date(config['date'])
1047 1047 jd = odate.jd()
1048   - ra, dec, equinox, epoch = eph.radec(target, date=jd)
  1048 + ephem = eph.radec(target, date=jd)
  1049 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1049 1050 coord_target = (1, Angle(ra), Angle(dec))
1050 1051 coord = Coords()
1051 1052 coord.coords(coord_target)
... ... @@ -1053,7 +1054,8 @@ class Maps(MapsException, GuitastroTools):
1053 1054 dec = math.radians(dec)
1054 1055 sindec = math.sin(dec)
1055 1056 cosdec = math.cos(dec)
1056   - ra_sun, dec_sun, equinox, epoch = eph.radec("sun", date=jd)
  1057 + ephem = eph.radec("sun", date=jd)
  1058 + ra_sun, dec_sun = ephem['ra_equinox'], ephem['dec_equinox']
1057 1059 coord_sun = (1, Angle(ra_sun), Angle(dec_sun))
1058 1060 dist_sun = coord.great_circle_distance(coord_sun)
1059 1061 #print(f"coord={coord}")
... ... @@ -1062,7 +1064,8 @@ class Maps(MapsException, GuitastroTools):
1062 1064 dec_sun = math.radians(dec_sun)
1063 1065 sindec_sun = math.sin(dec_sun)
1064 1066 cosdec_sun = math.cos(dec_sun)
1065   - ra_moon, dec_moon, equinox, epoch = eph.radec("moon", date=jd)
  1067 + ephem = eph.radec("moon", date=jd)
  1068 + ra_moon, dec_moon = ephem['ra_equinox'], ephem['dec_equinox']
1066 1069 coord_moon = (1, Angle(ra_moon), Angle(dec_moon))
1067 1070 dist_moon = coord.great_circle_distance(coord_moon)
1068 1071 # --- ephems
... ... @@ -1425,7 +1428,8 @@ class Maps(MapsException, GuitastroTools):
1425 1428 if key in config.keys():
1426 1429 config[key] = val
1427 1430 eph = Ephemeris()
1428   - ra, dec, equinox, epoch = eph.radec(target)
  1431 + ephem = eph.radec(target)
  1432 + ra, dec = ephem['ra_equinox'], ephem['dec_equinox']
1429 1433 if config['date'] == "":
1430 1434 date = epoch
1431 1435 else:
... ... @@ -1554,7 +1558,8 @@ if __name__ == &quot;__main__&quot;:
1554 1558 name = "221120A"
1555 1559 target = f"GCNC {name}"
1556 1560 eph = Ephemeris()
1557   - ra, dec, equinox, epoch = eph.radec(target)
  1561 + ephem = eph.radec(target)
  1562 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1558 1563 date = epoch
1559 1564 title = f"Visibility (elevation) map of GRB {name} at {date}"
1560 1565 shortname = f"GRB {name}"
... ... @@ -1575,7 +1580,8 @@ if __name__ == &quot;__main__&quot;:
1575 1580 name = "221009A"
1576 1581 target = "RADEC 288.25375d +19.80947"
1577 1582 eph = Ephemeris()
1578   - ra, dec, equinox, epoch = eph.radec(target)
  1583 + ephem = eph.radec(target)
  1584 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1579 1585 date = "2022-10-09T14:10:17.99"
1580 1586 title = f"Visibility (elevation) map of GRB {name} at {date}"
1581 1587 shortname = f"GRB {name}"
... ... @@ -1612,7 +1618,8 @@ if __name__ == &quot;__main__&quot;:
1612 1618 name = "221120A"
1613 1619 target = f"GCNC {name}"
1614 1620 eph = Ephemeris()
1615   - ra, dec, equinox, epoch = eph.radec(target)
  1621 + ephem = eph.radec(target)
  1622 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1616 1623 date = "2022-04-12T20:44:00" # "2022-04-11T07:20:00" epoch
1617 1624 title = f"Visibility (elevation) map of GRB {name} at {date}"
1618 1625 output_file = os.path.join(path_products, f"map_GRB{name}.png")
... ... @@ -1655,7 +1662,8 @@ if __name__ == &quot;__main__&quot;:
1655 1662 eph = Ephemeris()
1656 1663 eph.set_home(home)
1657 1664 name = "jupiter"
1658   - ra, dec, equinox, epoch = eph.radec(name, date=date, unit_ra="H0.2", unit_dec="d+090.1")
  1665 + ephem = eph.radec(name, date=date, unit_ra="H0.2", unit_dec="d+090.1")
  1666 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1659 1667 ang.angle(ra)
1660 1668 target["ra"] = ang.deg()
1661 1669 ang.angle(dec)
... ... @@ -1664,7 +1672,8 @@ if __name__ == &quot;__main__&quot;:
1664 1672 target["color"] = "#00FF00"
1665 1673 targets.append(target.copy())
1666 1674 name = "saturn"
1667   - ra, dec, equinox, epoch = eph.radec(name, date=date, unit_ra="H0.2", unit_dec="d+090.1")
  1675 + ephem = eph.radec(name, date=date, unit_ra="H0.2", unit_dec="d+090.1")
  1676 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1668 1677 ang.angle(ra)
1669 1678 target["ra"] = ang.deg()
1670 1679 ang.angle(dec)
... ... @@ -1680,10 +1689,10 @@ if __name__ == &quot;__main__&quot;:
1680 1689 """ This example generate a web page for a GRB
1681 1690 """
1682 1691 map1 = Maps()
1683   - name = "221009A"
1684   - target = f"GCNC {name}"
1685   - #target = "RADEC 85.5770d +14.03340d"
1686   - date = "2022-10-09T13:16:59"
  1692 + name = "230414B"
  1693 + #target = f"GCNC {name}"
  1694 + target = "RADEC 181.09439d 53.15428d"
  1695 + date = "2023-04-14T16:14:21"
1687 1696 #date = ""
1688 1697 shortname = f"GRB {name}"
1689 1698 siteobss = map1.get_siteobss("GRANDMA")
... ... @@ -1703,6 +1712,9 @@ if __name__ == &quot;__main__&quot;:
1703 1712 name = "DART"
1704 1713 target = "RADEC 03h23m20.8s -33d26m33s"
1705 1714 date = "2022-09-26T23:14:38"
  1715 + name = "GRB 230414B"
  1716 + target = "RADEC 12h04m17s +53d07m36s"
  1717 + date = "2023-04-14T16:14:21"
1706 1718 shortname = f"Impact of {name}"
1707 1719 siteobss = []
1708 1720 siteobs = {}
... ... @@ -1739,13 +1751,15 @@ if __name__ == &quot;__main__&quot;:
1739 1751 name = "221024A"
1740 1752 target = "RADEC 169.7413d +33.0419"
1741 1753 eph = Ephemeris()
1742   - ra, dec, equinox, epoch = eph.radec(target)
  1754 + ephem = eph.radec(target)
  1755 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1743 1756 date = "2022-10-24T16:00:27.12"
1744 1757 else:
1745 1758 name = "220408A"
1746 1759 target = f"GCNC {name}"
1747 1760 eph = Ephemeris()
1748   - ra, dec, equinox, epoch = eph.radec(target)
  1761 + ephem = eph.radec(target)
  1762 + ra, dec, equinox, epoch = ephem['ra_equinox'], ephem['dec_equinox'], ephem['header']['equinox'], ephem['jd']
1749 1763 date = epoch
1750 1764 title = f"Visibility (elevation) map of GRB {name} at {date}"
1751 1765 output_file = os.path.join(path_products, f"map_GRB{name}.pdf")
... ...