pyobs-gemini 1.0.0__tar.gz → 2.0.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyobs_gemini-2.0.0.dev1/PKG-INFO +12 -0
- pyobs_gemini-2.0.0.dev1/pyobs_gemini/__init__.py +3 -0
- {pyobs-gemini-1.0.0 → pyobs_gemini-2.0.0.dev1}/pyobs_gemini/api.py +12 -11
- {pyobs-gemini-1.0.0 → pyobs_gemini-2.0.0.dev1}/pyobs_gemini/gemini.py +48 -83
- {pyobs-gemini-1.0.0 → pyobs_gemini-2.0.0.dev1}/pyobs_gemini/geminidriver.py +18 -19
- pyobs_gemini-2.0.0.dev1/pyproject.toml +46 -0
- pyobs-gemini-1.0.0/LICENSE +0 -22
- pyobs-gemini-1.0.0/PKG-INFO +0 -15
- pyobs-gemini-1.0.0/pyobs_gemini/__init__.py +0 -1
- pyobs-gemini-1.0.0/pyproject.toml +0 -24
- pyobs-gemini-1.0.0/setup.py +0 -30
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyobs-gemini
|
|
3
|
+
Version: 2.0.0.dev1
|
|
4
|
+
Summary: pyobs module for Optec Gemini focusser/rotator
|
|
5
|
+
Author: Tim-Oliver Husser, Frederic V. Hessman
|
|
6
|
+
Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>, Frederic V. Hessman <fhessma@uni-goettingen.de>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: aioserial>=1.3,<2
|
|
9
|
+
Requires-Dist: astropy>=7.0.1,<8
|
|
10
|
+
Requires-Dist: numpy>=2.2.5,<3
|
|
11
|
+
Requires-Dist: pyobs-core>=2.0.0.dev38,<3
|
|
12
|
+
Requires-Python: >=3.11, <3.14
|
|
@@ -21,10 +21,11 @@ The GEMINI responses have the syntax:
|
|
|
21
21
|
{END|SET}
|
|
22
22
|
|
|
23
23
|
"""
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
25
26
|
|
|
26
27
|
# A COMMAND CONSISTS OF name,device,command_string,input_format,output_format,end_string,help
|
|
27
|
-
gemini_commands:
|
|
28
|
+
gemini_commands: dict[str, dict[str, Any]] = {
|
|
28
29
|
"CENTER": {
|
|
29
30
|
"cmd": "center",
|
|
30
31
|
"device": "F",
|
|
@@ -238,25 +239,25 @@ def gemini_cmd(cmd: str, *args: Any, dev: str = "", trans_id: int = 0) -> str:
|
|
|
238
239
|
transaction ID, and whatever input args are needed.
|
|
239
240
|
"""
|
|
240
241
|
if cmd not in gemini_commands:
|
|
241
|
-
raise ValueError("{
|
|
242
|
+
raise ValueError(f"{cmd} is not a GEMINI command!")
|
|
242
243
|
info = gemini_commands[cmd]
|
|
243
244
|
if dev not in info["device"]:
|
|
244
|
-
raise ValueError("{
|
|
245
|
+
raise ValueError("{} does not match GEMINI device {}".format(dev, info["device"]))
|
|
245
246
|
|
|
246
|
-
s1 = "<{
|
|
247
|
+
s1 = f"<{dev}1{trans_id:02d}"
|
|
247
248
|
s2 = cmd
|
|
248
249
|
if info["format"] is None:
|
|
249
250
|
s3 = ""
|
|
250
251
|
else:
|
|
251
252
|
try:
|
|
252
253
|
s3 = info["format"].format(*args)
|
|
253
|
-
except:
|
|
254
|
-
raise ValueError("format {
|
|
254
|
+
except Exception:
|
|
255
|
+
raise ValueError(f"format {info['format']} does not work for {cmd}")
|
|
255
256
|
s4 = ">"
|
|
256
257
|
return s1 + s2 + s3 + s4
|
|
257
258
|
|
|
258
259
|
|
|
259
|
-
def gemini_parse_output(cmd: str, raw_response:
|
|
260
|
+
def gemini_parse_output(cmd: str, raw_response: list[bytes]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
260
261
|
"""
|
|
261
262
|
Parse the raw GEMINI output, a list of bytearray's from serial.readline,
|
|
262
263
|
into a dictionary.
|
|
@@ -269,13 +270,13 @@ def gemini_parse_output(cmd: str, raw_response: List[bytes]) -> Tuple[Dict[str,
|
|
|
269
270
|
info = gemini_commands[cmd]
|
|
270
271
|
if "end" not in info:
|
|
271
272
|
raise IndexError("ending is not in info!")
|
|
272
|
-
results:
|
|
273
|
+
results: dict[str, Any] = {}
|
|
273
274
|
|
|
274
275
|
# decode and remove newlines
|
|
275
276
|
try:
|
|
276
277
|
rsp = [r.decode("utf8").replace("\\n", "") for r in raw_response]
|
|
277
278
|
except UnicodeDecodeError:
|
|
278
|
-
raise ValueError("{
|
|
279
|
+
raise ValueError(f"{raw_response!r} cannot be decoded")
|
|
279
280
|
|
|
280
281
|
# check for syntax
|
|
281
282
|
if len(rsp) == 0 or not rsp[0].strip().startswith("!"):
|
|
@@ -284,7 +285,7 @@ def gemini_parse_output(cmd: str, raw_response: List[bytes]) -> Tuple[Dict[str,
|
|
|
284
285
|
# get transaction ID
|
|
285
286
|
try:
|
|
286
287
|
trans_id = int(rsp[0].strip()[1:])
|
|
287
|
-
except:
|
|
288
|
+
except ValueError:
|
|
288
289
|
trans_id = None
|
|
289
290
|
results["trans_id"] = trans_id
|
|
290
291
|
errors = {}
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import astropy.units as u
|
|
4
6
|
import numpy as np
|
|
5
7
|
from astropy.coordinates import SkyCoord
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
from pyobs.events import Event, MotionStatusChangedEvent
|
|
9
|
+
from pyobs.interfaces import (
|
|
10
|
+
FitsHeaderEntry,
|
|
11
|
+
FocuserState,
|
|
12
|
+
ICalibrate,
|
|
13
|
+
IFitsHeaderBefore,
|
|
14
|
+
IFocuser,
|
|
15
|
+
IPointingRaDec,
|
|
16
|
+
IRotation,
|
|
17
|
+
RaDecState,
|
|
18
|
+
RotationState,
|
|
19
|
+
)
|
|
9
20
|
from pyobs.mixins import FitsNamespaceMixin, MotionStatusMixin
|
|
10
21
|
from pyobs.modules import Module, timeout
|
|
11
22
|
from pyobs.utils.enums import MotionStatus
|
|
12
23
|
from pyobs.utils.parallel import event_wait
|
|
13
24
|
from pyobs.utils.threads import LockWithAbort
|
|
14
|
-
from pyobs.interfaces import IRotation, IMotion, IFocuser, ICalibrate, IFitsHeaderBefore, IPointingRaDec
|
|
15
25
|
from pyobs.utils.time import Time
|
|
16
26
|
|
|
17
|
-
from .geminidriver import
|
|
27
|
+
from .geminidriver import GeminiCommException, GeminiDriver, Vocab
|
|
18
28
|
|
|
19
29
|
log = logging.getLogger(__name__)
|
|
20
30
|
|
|
@@ -33,8 +43,8 @@ class GeminiFocuserRotator(
|
|
|
33
43
|
|
|
34
44
|
def __init__(
|
|
35
45
|
self,
|
|
36
|
-
serial_config:
|
|
37
|
-
fits_config:
|
|
46
|
+
serial_config: dict[str, Any] | None = None,
|
|
47
|
+
fits_config: dict[str, Any] | None = None,
|
|
38
48
|
focus_offset: float = 0.0,
|
|
39
49
|
rotation_offset: float = 0.0,
|
|
40
50
|
*args: Any,
|
|
@@ -60,7 +70,7 @@ class GeminiFocuserRotator(
|
|
|
60
70
|
# ROTATOR STUFF
|
|
61
71
|
self._rotation_lock = asyncio.Lock()
|
|
62
72
|
self._rotation_abort = asyncio.Event()
|
|
63
|
-
self._skycoord:
|
|
73
|
+
self._skycoord: SkyCoord | None = None
|
|
64
74
|
self._rotation_accur = 0.0 # DEG
|
|
65
75
|
self._rotation_offset = rotation_offset
|
|
66
76
|
|
|
@@ -78,7 +88,7 @@ class GeminiFocuserRotator(
|
|
|
78
88
|
self._serial_config = serial_config
|
|
79
89
|
|
|
80
90
|
# driver
|
|
81
|
-
self._driver:
|
|
91
|
+
self._driver: GeminiDriver | None = None
|
|
82
92
|
|
|
83
93
|
# FITS HEADER CONFIGURATION
|
|
84
94
|
if fits_config is None:
|
|
@@ -111,8 +121,7 @@ class GeminiFocuserRotator(
|
|
|
111
121
|
# subscribe to events
|
|
112
122
|
if self.comm:
|
|
113
123
|
if self.follow:
|
|
114
|
-
|
|
115
|
-
self.comm.register_event(MotionStatusChangedEvent, self._telescope_event)
|
|
124
|
+
await self.comm.register_event(MotionStatusChangedEvent, self._telescope_event)
|
|
116
125
|
|
|
117
126
|
# create driver and open it
|
|
118
127
|
self._driver = GeminiDriver(**self._serial_config)
|
|
@@ -198,11 +207,15 @@ class GeminiFocuserRotator(
|
|
|
198
207
|
# TODO: find out
|
|
199
208
|
# self._T = fdict.response[Vocab.CURRENT_TEMP.value]
|
|
200
209
|
|
|
210
|
+
# publish current focus/rotation state
|
|
211
|
+
await self.comm.set_state(IFocuser, FocuserState(focus=self.focus, focus_offset=self._focus_offset))
|
|
212
|
+
await self.comm.set_state(IRotation, RotationState(rotation=self.rotation))
|
|
213
|
+
|
|
201
214
|
# get motion status
|
|
202
215
|
await self._change_motion_status(self._motion_status(fdict.response), interface="IFocuser")
|
|
203
216
|
await self._change_motion_status(self._motion_status(rdict.response), interface="IRotation")
|
|
204
217
|
|
|
205
|
-
def _motion_status(self, stat:
|
|
218
|
+
def _motion_status(self, stat: dict[str, Any]) -> MotionStatus:
|
|
206
219
|
"""
|
|
207
220
|
Extracts the IMotion status from a dictionary returned
|
|
208
221
|
by the driver's status method.
|
|
@@ -243,7 +256,7 @@ class GeminiFocuserRotator(
|
|
|
243
256
|
await event_wait(self._focus_abort, 1)
|
|
244
257
|
await self._update_status()
|
|
245
258
|
|
|
246
|
-
while not self._focus_abort.is_set() and
|
|
259
|
+
while not self._focus_abort.is_set() and self.motion_status("IFocuser") == MotionStatus.SLEWING:
|
|
247
260
|
# sleep a little
|
|
248
261
|
await event_wait(self._focus_abort, 1)
|
|
249
262
|
|
|
@@ -254,14 +267,6 @@ class GeminiFocuserRotator(
|
|
|
254
267
|
# success
|
|
255
268
|
log.info("Successfully set new focus.")
|
|
256
269
|
|
|
257
|
-
async def get_focus(self, **kwargs: Any) -> float:
|
|
258
|
-
"""Return current focus.
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
Current focus.
|
|
262
|
-
"""
|
|
263
|
-
return self.focus
|
|
264
|
-
|
|
265
270
|
@timeout(300000)
|
|
266
271
|
async def set_rotation(self, angle: float, **kwargs: Any) -> None:
|
|
267
272
|
"""Sets the rotation angle to the given value in degrees."""
|
|
@@ -281,9 +286,7 @@ class GeminiFocuserRotator(
|
|
|
281
286
|
await event_wait(self._rotation_abort, 1)
|
|
282
287
|
await self._update_status()
|
|
283
288
|
|
|
284
|
-
while (
|
|
285
|
-
not self._rotation_abort.is_set() and await self.get_motion_status("IRotation") == MotionStatus.SLEWING
|
|
286
|
-
):
|
|
289
|
+
while not self._rotation_abort.is_set() and self.motion_status("IRotation") == MotionStatus.SLEWING:
|
|
287
290
|
# sleep a little
|
|
288
291
|
await event_wait(self._rotation_abort, 1)
|
|
289
292
|
|
|
@@ -294,11 +297,7 @@ class GeminiFocuserRotator(
|
|
|
294
297
|
# success
|
|
295
298
|
log.info("Successfully set new rotation.")
|
|
296
299
|
|
|
297
|
-
async def
|
|
298
|
-
"""Returns the current rotation angle."""
|
|
299
|
-
return await self._driver.get_rotation()
|
|
300
|
-
|
|
301
|
-
async def stop_motion(self, device: Optional[str] = None, **kwargs: Any) -> None:
|
|
300
|
+
async def stop_motion(self, device: str | None = None, **kwargs: Any) -> None:
|
|
302
301
|
"""Stop the motion.
|
|
303
302
|
|
|
304
303
|
Args:
|
|
@@ -320,7 +319,7 @@ class GeminiFocuserRotator(
|
|
|
320
319
|
|
|
321
320
|
# valid coordinates?
|
|
322
321
|
if ra < 0.0 or ra > 360.0 or np.abs(dec) >= 90.0:
|
|
323
|
-
raise ValueError("RA, Dec out of limits (
|
|
322
|
+
raise ValueError(f"RA, Dec out of limits ({ra:.2f}, {dec:.2f}).")
|
|
324
323
|
skycoord = SkyCoord(ra * u.deg, dec * u.deg, frame="icrs")
|
|
325
324
|
|
|
326
325
|
# get parallactic angle
|
|
@@ -331,14 +330,12 @@ class GeminiFocuserRotator(
|
|
|
331
330
|
|
|
332
331
|
# start tracking and log it
|
|
333
332
|
self._skycoord = skycoord
|
|
333
|
+
await self.comm.set_state(IPointingRaDec, RaDecState(ra=ra, dec=dec))
|
|
334
334
|
log.info(
|
|
335
335
|
"Started target tracking of parallactic angle at %s...",
|
|
336
336
|
self._skycoord.to_string(),
|
|
337
337
|
)
|
|
338
338
|
|
|
339
|
-
async def get_radec(self, **kwargs: Any) -> Tuple[float, float]:
|
|
340
|
-
return 0.0, 0.0
|
|
341
|
-
|
|
342
339
|
async def _rotation_tracker_func(self) -> None:
|
|
343
340
|
if self._driver is None:
|
|
344
341
|
return
|
|
@@ -352,11 +349,8 @@ class GeminiFocuserRotator(
|
|
|
352
349
|
# get parallactic angle
|
|
353
350
|
pa = self.observer.parallactic_angle(Time.now(), self._skycoord).degree
|
|
354
351
|
|
|
355
|
-
#
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# need to rotate?
|
|
359
|
-
if np.abs(pa - rot) > self._rotation_accur:
|
|
352
|
+
# need to rotate? (self.rotation is kept fresh by _gdriver_update_func)
|
|
353
|
+
if np.abs(pa - self.rotation) > self._rotation_accur:
|
|
360
354
|
# rotate
|
|
361
355
|
await self._driver.set_rotation(pa + self._rotation_offset)
|
|
362
356
|
|
|
@@ -364,8 +358,8 @@ class GeminiFocuserRotator(
|
|
|
364
358
|
await asyncio.sleep(1)
|
|
365
359
|
|
|
366
360
|
async def get_fits_header_before(
|
|
367
|
-
self, namespaces:
|
|
368
|
-
) ->
|
|
361
|
+
self, namespaces: list[str] | None = None, **kwargs: Any
|
|
362
|
+
) -> dict[str, FitsHeaderEntry]:
|
|
369
363
|
"""Returns FITS header for the current status of this module.
|
|
370
364
|
|
|
371
365
|
Args:
|
|
@@ -374,34 +368,34 @@ class GeminiFocuserRotator(
|
|
|
374
368
|
Returns:
|
|
375
369
|
Dictionary containing FITS headers.
|
|
376
370
|
"""
|
|
377
|
-
hdr = {}
|
|
371
|
+
hdr: dict[str, FitsHeaderEntry] = {}
|
|
378
372
|
|
|
379
373
|
# SET FOCUS HEADERS
|
|
380
374
|
if "focus" in self._fits_config:
|
|
381
375
|
key, comment = self._fits_config["focus"]
|
|
382
|
-
hdr[key] = self.focus, comment
|
|
376
|
+
hdr[key] = FitsHeaderEntry(value=self.focus, comment=comment)
|
|
383
377
|
if "focus-motion" in self._fits_config:
|
|
384
378
|
key, comment = self._fits_config["focus-motion"]
|
|
385
|
-
hdr[key] = (
|
|
379
|
+
hdr[key] = FitsHeaderEntry(value=self.motion_status("IFocuser").value, comment=comment)
|
|
386
380
|
if "focus-offset" in self._fits_config:
|
|
387
381
|
key, comment = self._fits_config["focus-offset"]
|
|
388
|
-
hdr[key] = self._focus_offset, comment
|
|
382
|
+
hdr[key] = FitsHeaderEntry(value=self._focus_offset, comment=comment)
|
|
389
383
|
|
|
390
384
|
# SET ROTATION HEADERS
|
|
391
385
|
if "rotation" in self._fits_config:
|
|
392
386
|
key, comment = self._fits_config["rotation"]
|
|
393
|
-
hdr[key] = self.rotation, comment
|
|
387
|
+
hdr[key] = FitsHeaderEntry(value=self.rotation, comment=comment)
|
|
394
388
|
if "rotation-motion" in self._fits_config:
|
|
395
389
|
key, comment = self._fits_config["rotation-motion"]
|
|
396
|
-
hdr[key] = (
|
|
390
|
+
hdr[key] = FitsHeaderEntry(value=self.motion_status("IRotation").value, comment=comment)
|
|
397
391
|
if "rotation-offset" in self._fits_config:
|
|
398
392
|
key, comment = self._fits_config["rotation-offset"]
|
|
399
|
-
hdr[key] = self._rotation_offset, comment
|
|
393
|
+
hdr[key] = FitsHeaderEntry(value=self._rotation_offset, comment=comment)
|
|
400
394
|
|
|
401
395
|
# TEMPERATURE SENSOR
|
|
402
396
|
if "temperature" in self._fits_config:
|
|
403
397
|
key, comment = self._fits_config["temperature"]
|
|
404
|
-
hdr[key] = self._T, comment
|
|
398
|
+
hdr[key] = FitsHeaderEntry(value=self._T, comment=comment)
|
|
405
399
|
|
|
406
400
|
# return it
|
|
407
401
|
return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
|
|
@@ -418,14 +412,6 @@ class GeminiFocuserRotator(
|
|
|
418
412
|
"""
|
|
419
413
|
pass
|
|
420
414
|
|
|
421
|
-
async def get_focus_offset(self, **kwargs: Any) -> float:
|
|
422
|
-
"""Return current focus offset.
|
|
423
|
-
|
|
424
|
-
Returns:
|
|
425
|
-
Current focus offset.
|
|
426
|
-
"""
|
|
427
|
-
return 0.0
|
|
428
|
-
|
|
429
415
|
async def init(self, **kwargs: Any) -> None:
|
|
430
416
|
"""Initialize device.
|
|
431
417
|
|
|
@@ -442,19 +428,11 @@ class GeminiFocuserRotator(
|
|
|
442
428
|
"""
|
|
443
429
|
pass
|
|
444
430
|
|
|
445
|
-
async def is_ready(self, **kwargs: Any) -> bool:
|
|
446
|
-
"""Returns the device is "ready", whatever that means for the specific device.
|
|
447
|
-
|
|
448
|
-
Returns:
|
|
449
|
-
Whether device is ready
|
|
450
|
-
"""
|
|
451
|
-
return True
|
|
452
|
-
|
|
453
431
|
async def _telescope_event(self, ev: Event, sender: str) -> bool:
|
|
454
432
|
"""Moving events from telescope.
|
|
455
433
|
|
|
456
434
|
Args:
|
|
457
|
-
event:
|
|
435
|
+
event: A MotionStatusChangedEvent.
|
|
458
436
|
sender: Who sent it.
|
|
459
437
|
"""
|
|
460
438
|
|
|
@@ -462,23 +440,8 @@ class GeminiFocuserRotator(
|
|
|
462
440
|
if self.follow is None or self.follow != sender:
|
|
463
441
|
return False
|
|
464
442
|
|
|
465
|
-
#
|
|
466
|
-
if isinstance(ev,
|
|
467
|
-
# do we have RA/Dec?
|
|
468
|
-
if ev.ra is not None and ev.dec is not None:
|
|
469
|
-
# start tracking!
|
|
470
|
-
log.info("Received an event that telescope is about to start tracking. Following it... ")
|
|
471
|
-
await self.track(ev.ra, ev.dec)
|
|
472
|
-
|
|
473
|
-
else:
|
|
474
|
-
# presumably slewing to fixed coordinates
|
|
475
|
-
# are we currently tracking?
|
|
476
|
-
if self._skycoord is not None:
|
|
477
|
-
log.info("Received event that telescope is not tracking anymore, stopping derotator movement...")
|
|
478
|
-
await self.stop_motion("IRotation")
|
|
479
|
-
|
|
480
|
-
elif isinstance(ev, MotionStatusChangedEvent):
|
|
481
|
-
# we want the ITelescope event and anything except TRACKING and SLEWING (might end up in race condition)
|
|
443
|
+
# we want the ITelescope event and anything except TRACKING and SLEWING (might end up in race condition)
|
|
444
|
+
if isinstance(ev, MotionStatusChangedEvent):
|
|
482
445
|
if "ITelescope" in ev.interfaces and ev.interfaces["ITelescope"] not in [
|
|
483
446
|
MotionStatus.TRACKING.value,
|
|
484
447
|
MotionStatus.SLEWING.value,
|
|
@@ -489,5 +452,7 @@ class GeminiFocuserRotator(
|
|
|
489
452
|
log.info("Received event that telescope is not tracking anymore, stopping derotator movement...")
|
|
490
453
|
await self.stop_motion("IRotation")
|
|
491
454
|
|
|
455
|
+
return True
|
|
456
|
+
|
|
492
457
|
|
|
493
458
|
__all__ = ["GeminiFocuserRotator"]
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import enum
|
|
3
2
|
import datetime
|
|
3
|
+
import enum
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Optional, Any, Dict, List, cast
|
|
6
|
-
import numpy as np
|
|
7
|
-
import aioserial
|
|
8
5
|
from itertools import chain
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import aioserial
|
|
9
|
+
import numpy as np
|
|
9
10
|
|
|
10
|
-
from pyobs_gemini.api import
|
|
11
|
+
from pyobs_gemini.api import gemini_cmd, gemini_commands, gemini_parse_output
|
|
11
12
|
|
|
12
13
|
log = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -16,20 +17,20 @@ class GeminiTransaction:
|
|
|
16
17
|
def __init__(
|
|
17
18
|
self,
|
|
18
19
|
tid: int = 0,
|
|
19
|
-
cmd:
|
|
20
|
-
raw_cmd:
|
|
21
|
-
response:
|
|
22
|
-
errors:
|
|
20
|
+
cmd: str | None = None,
|
|
21
|
+
raw_cmd: str | None = None,
|
|
22
|
+
response: dict[str, Any] | None = None,
|
|
23
|
+
errors: dict[str, Any] | None = None,
|
|
23
24
|
):
|
|
24
25
|
"""Constructor for a gemini transaction dictionary"""
|
|
25
26
|
self.transaction = {"id": tid, "cmd": cmd, "raw_cmd": raw_cmd}
|
|
26
27
|
self.response = {} if response is None else response
|
|
27
28
|
self.errors = {} if errors is None else errors
|
|
28
29
|
self.time = datetime.datetime.now()
|
|
29
|
-
self.data:
|
|
30
|
+
self.data: dict[Vocab, Any] = {}
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def has_transaction_error(d:
|
|
33
|
+
def has_transaction_error(d: GeminiTransaction | None, keys: list[str] | None = None) -> bool:
|
|
33
34
|
"""Is there a problem with this transaction dictionary?"""
|
|
34
35
|
if d is None:
|
|
35
36
|
return True
|
|
@@ -42,7 +43,7 @@ def has_transaction_error(d: Optional[GeminiTransaction], keys: Optional[List[st
|
|
|
42
43
|
return False
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def dict_union(*args: Any) ->
|
|
46
|
+
def dict_union(*args: Any) -> dict[str, Any]:
|
|
46
47
|
return dict(chain.from_iterable(d.items() for d in args if d is not None))
|
|
47
48
|
|
|
48
49
|
|
|
@@ -96,11 +97,11 @@ class GeminiDriver:
|
|
|
96
97
|
self._lock = asyncio.Lock()
|
|
97
98
|
|
|
98
99
|
# init
|
|
99
|
-
self._serial:
|
|
100
|
+
self._serial: aioserial.AioSerial | None = None
|
|
100
101
|
self.transID = 0
|
|
101
102
|
self.transactions = np.array([None for i in range(99)], dtype=dict) # CIRCULAR BUFFER
|
|
102
|
-
self.focus_model:
|
|
103
|
-
self.rotation_model:
|
|
103
|
+
self.focus_model: dict[str, float] | None = None
|
|
104
|
+
self.rotation_model: dict[str, float] | None = None
|
|
104
105
|
self.focus_offset = 0.0
|
|
105
106
|
self.rotation_offset = 0.0
|
|
106
107
|
|
|
@@ -233,9 +234,7 @@ class GeminiDriver:
|
|
|
233
234
|
|
|
234
235
|
# conversion between hardware and position angle: deg = offset + scale * millideg
|
|
235
236
|
# native is 0-360000 milli-degrees, convert to -180 to +180 degrees
|
|
236
|
-
min_rsteps = 0
|
|
237
237
|
max_rsteps = 360000
|
|
238
|
-
rrange = 360.0 # DEG
|
|
239
238
|
self.rotation_model = {
|
|
240
239
|
"offset": -180.0,
|
|
241
240
|
"scale": 0.001,
|
|
@@ -384,7 +383,7 @@ class GeminiDriver:
|
|
|
384
383
|
await self._send_to_gemini("F", "DOHOME")
|
|
385
384
|
return True
|
|
386
385
|
except GeminiCommException as e:
|
|
387
|
-
log.error("Could not home focus: "
|
|
386
|
+
log.error("Could not home focus: %s", e)
|
|
388
387
|
return False
|
|
389
388
|
|
|
390
389
|
async def focus_is_homed(self) -> bool:
|
|
@@ -407,7 +406,7 @@ class GeminiDriver:
|
|
|
407
406
|
await self._send_to_gemini("R", "DOHOME")
|
|
408
407
|
return True
|
|
409
408
|
except GeminiCommException as e:
|
|
410
|
-
log.error("Could not home rotation: "
|
|
409
|
+
log.error("Could not home rotation: %s", e)
|
|
411
410
|
return False
|
|
412
411
|
|
|
413
412
|
async def rotation_is_homed(self) -> bool:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyobs-gemini"
|
|
3
|
+
version = "2.0.0.dev1"
|
|
4
|
+
description = "pyobs module for Optec Gemini focusser/rotator"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" },
|
|
7
|
+
{ name = "Frederic V. Hessman", email = "fhessma@uni-goettingen.de" },
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11,<3.14"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"aioserial>=1.3,<2",
|
|
13
|
+
"astropy>=7.0.1,<8",
|
|
14
|
+
"numpy>=2.2.5,<3",
|
|
15
|
+
"pyobs-core>=2.0.0.dev38,<3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"black>=25.1.0,<26",
|
|
21
|
+
"pre-commit>=4.2.0,<5",
|
|
22
|
+
"ruff>=0.9.0",
|
|
23
|
+
"pyrefly>=1.1.1",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.uv.build-backend]
|
|
27
|
+
module-root = ""
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.9.14,<0.10.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
32
|
+
|
|
33
|
+
[tool.black]
|
|
34
|
+
line-length = 120
|
|
35
|
+
target-version = ['py311']
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
target-version = "py311"
|
|
39
|
+
line-length = 120
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = ["E", "F", "W", "I", "UP", "G"]
|
|
43
|
+
|
|
44
|
+
[tool.pyrefly]
|
|
45
|
+
python-version = "3.11"
|
|
46
|
+
ignore-missing-imports = []
|
pyobs-gemini-1.0.0/LICENSE
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Tim-Oliver Husser, Frederic V. Hessman
|
|
4
|
-
thusser@uni-goettingen.de
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
pyobs-gemini-1.0.0/PKG-INFO
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: pyobs-gemini
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: pyobs module for Optec Gemini focusser/rotator
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Tim-Oliver Husser
|
|
7
|
-
Author-email: thusser@uni-goettingen.de
|
|
8
|
-
Requires-Python: >=3.9,<3.11
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Requires-Dist: aioserial (>=1.3,<2.0)
|
|
14
|
-
Requires-Dist: astropy (>=5.1,<6.0)
|
|
15
|
-
Requires-Dist: pyobs-core (>=1.0,<2.0)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .gemini import GeminiFocuserRotator
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "pyobs-gemini"
|
|
3
|
-
version = "1.0.0"
|
|
4
|
-
description = "pyobs module for Optec Gemini focusser/rotator"
|
|
5
|
-
authors = ["Tim-Oliver Husser <thusser@uni-goettingen.de>", "Frederic V. Hessman <fhessma@uni-goettingen.de>"]
|
|
6
|
-
license = "MIT"
|
|
7
|
-
|
|
8
|
-
[tool.poetry.dependencies]
|
|
9
|
-
python = ">=3.9,<3.11"
|
|
10
|
-
astropy = "^5.1"
|
|
11
|
-
aioserial = "^1.3"
|
|
12
|
-
pyobs-core = "^1.0"
|
|
13
|
-
|
|
14
|
-
[tool.poetry.dev-dependencies]
|
|
15
|
-
black = "^21.12b0"
|
|
16
|
-
pre-commit = "^2.16"
|
|
17
|
-
|
|
18
|
-
[build-system]
|
|
19
|
-
requires = ["poetry-core>=1.0.0"]
|
|
20
|
-
build-backend = "poetry.core.masonry.api"
|
|
21
|
-
|
|
22
|
-
[tool.black]
|
|
23
|
-
line-length = 120
|
|
24
|
-
target-version = ['py39']
|
pyobs-gemini-1.0.0/setup.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['pyobs_gemini']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['aioserial>=1.3,<2.0', 'astropy>=5.1,<6.0', 'pyobs-core>=1.0,<2.0']
|
|
12
|
-
|
|
13
|
-
setup_kwargs = {
|
|
14
|
-
'name': 'pyobs-gemini',
|
|
15
|
-
'version': '1.0.0',
|
|
16
|
-
'description': 'pyobs module for Optec Gemini focusser/rotator',
|
|
17
|
-
'long_description': 'None',
|
|
18
|
-
'author': 'Tim-Oliver Husser',
|
|
19
|
-
'author_email': 'thusser@uni-goettingen.de',
|
|
20
|
-
'maintainer': 'None',
|
|
21
|
-
'maintainer_email': 'None',
|
|
22
|
-
'url': 'None',
|
|
23
|
-
'packages': packages,
|
|
24
|
-
'package_data': package_data,
|
|
25
|
-
'install_requires': install_requires,
|
|
26
|
-
'python_requires': '>=3.9,<3.11',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
setup(**setup_kwargs)
|