pyobs-zwoeaf 2.0.0.dev8__tar.gz → 2.0.0.dev9__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_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/PKG-INFO +1 -1
- pyobs_zwoeaf-2.0.0.dev9/lib/lib/mac/libEAFFocuser.dylib.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/pyproject.toml +1 -1
- pyobs_zwoeaf-2.0.0.dev9/src/pyobs_zwoeaf/eaffocuser.py +246 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/uv.lock +1 -1
- pyobs_zwoeaf-2.0.0.dev8/lib/lib/mac/libEAFFocuser.dylib +0 -1
- pyobs_zwoeaf-2.0.0.dev8/src/pyobs_zwoeaf/eaffocuser.py +0 -148
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/.github/workflows/pypi.yml +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/.github/workflows/ruff.yml +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/.gitignore +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/.pre-commit-config.yaml +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/.readthedocs.yml +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/CMakeLists.txt +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/LICENSE +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/README.md +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/Makefile +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/make.bat +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/requirements.txt +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/source/conf.py +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/docs/source/index.rst +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/demo/c/Makefile +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/demo/c/bin/x86/test_console +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/demo/c/main.cpp +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/include/EAF_focuser.h +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv6/libEAFFocuser.a +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv6/libEAFFocuser.so.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv7/libEAFFocuser.a +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv7/libEAFFocuser.so.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv8/libEAFFocuser.a +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/armv8/libEAFFocuser.so.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/eaf.rules +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/mac/libEAFFocuser.a +0 -0
- /pyobs_zwoeaf-2.0.0.dev8/lib/lib/mac/libEAFFocuser.dylib.1.6 → /pyobs_zwoeaf-2.0.0.dev9/lib/lib/mac/libEAFFocuser.dylib +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/x64/libEAFFocuser.a +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/x64/libEAFFocuser.so.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/x86/libEAFFocuser.a +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/lib/x86/libEAFFocuser.so.1.6 +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/lib/license.txt +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/src/pyobs_zwoeaf/EAF_focuser.cpp +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/src/pyobs_zwoeaf/__init__.py +0 -0
- {pyobs_zwoeaf-2.0.0.dev8 → pyobs_zwoeaf-2.0.0.dev9}/src/pyobs_zwoeaf/cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyobs-zwoeaf
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev9
|
|
4
4
|
Summary: pyobs model for ZWO EAF focus motors
|
|
5
5
|
Author-Email: Lennart Jonas Buhrke <l.buhrke@stud.uni-goettingen.de>, Tim-Oliver Husser <thusser@uni-goettingen.de>
|
|
6
6
|
License-Expression: MIT
|
|
Binary file
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import pyobs.utils.exceptions as exc
|
|
9
|
+
from pyobs.interfaces import IFocuser, IReady, ITemperatures
|
|
10
|
+
from pyobs.interfaces.IFocuser import FocuserState
|
|
11
|
+
from pyobs.interfaces.IReady import ReadyState
|
|
12
|
+
from pyobs.interfaces.ITemperatures import SensorReading, TemperaturesState
|
|
13
|
+
from pyobs.mixins import MotionStatusMixin
|
|
14
|
+
from pyobs.modules import Module
|
|
15
|
+
from pyobs.utils.enums import MotionStatus
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
_STEP_TO_MM = 0.00242105263
|
|
20
|
+
|
|
21
|
+
# the ZWO EAF SDK's calls are blocking and are made directly on the event loop thread (see
|
|
22
|
+
# _run_blocking). If the focuser has gone unresponsive, they can hang indefinitely, so they're
|
|
23
|
+
# bounded with a timeout rather than let a single dead connection freeze the whole module.
|
|
24
|
+
_SDK_CALL_TIMEOUT = 5.0
|
|
25
|
+
|
|
26
|
+
# set_focus()'s move-and-wait-until-done sequence legitimately takes longer than the other SDK
|
|
27
|
+
# calls above, so it gets its own, more generous timeout.
|
|
28
|
+
_MOVE_TIMEOUT = 60.0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EAFFocuser(Module, MotionStatusMixin, IFocuser, ITemperatures):
|
|
32
|
+
"""A pyobs module for the ZWO EAF electronic auto focuser."""
|
|
33
|
+
|
|
34
|
+
__module__ = "pyobs_zwoeaf"
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
device_number: int = 0,
|
|
39
|
+
max_steps: int = 60000,
|
|
40
|
+
backlash: int = 0,
|
|
41
|
+
direction: bool = True,
|
|
42
|
+
sound: bool = True,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> None:
|
|
45
|
+
Module.__init__(self, **kwargs)
|
|
46
|
+
MotionStatusMixin.__init__(self)
|
|
47
|
+
|
|
48
|
+
self._device_number = device_number
|
|
49
|
+
self._max_steps = max_steps
|
|
50
|
+
self._backlash = backlash
|
|
51
|
+
self._direction = direction
|
|
52
|
+
self._sound = sound
|
|
53
|
+
self._eaf: Any | None = None
|
|
54
|
+
self._focus_setpoint = 0.0
|
|
55
|
+
self._focus_offset = 0.0
|
|
56
|
+
|
|
57
|
+
self.add_background_task(self._poll_temperature)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
async def _run_blocking(
|
|
61
|
+
func: Callable[[], None], timeout: float = _SDK_CALL_TIMEOUT
|
|
62
|
+
) -> bool:
|
|
63
|
+
"""Run a blocking EAF SDK call in a daemon thread, so a hung call can't freeze the module.
|
|
64
|
+
|
|
65
|
+
A plain executor isn't used here, since its worker threads are non-daemon and Python joins
|
|
66
|
+
them on interpreter shutdown -- a hung call would then just move the freeze to process exit.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if func completed within timeout, False if it's still running in the background.
|
|
70
|
+
"""
|
|
71
|
+
loop = asyncio.get_running_loop()
|
|
72
|
+
future: asyncio.Future[None] = loop.create_future()
|
|
73
|
+
|
|
74
|
+
def _wrapper() -> None:
|
|
75
|
+
try:
|
|
76
|
+
func()
|
|
77
|
+
finally:
|
|
78
|
+
loop.call_soon_threadsafe(future.set_result, None)
|
|
79
|
+
|
|
80
|
+
threading.Thread(target=_wrapper, daemon=True).start()
|
|
81
|
+
try:
|
|
82
|
+
await asyncio.wait_for(future, timeout=timeout)
|
|
83
|
+
return True
|
|
84
|
+
except TimeoutError:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
async def open(self) -> None:
|
|
88
|
+
"""Open module."""
|
|
89
|
+
await Module.open(self)
|
|
90
|
+
|
|
91
|
+
from .EAF_focuser import EAF # type: ignore[import-untyped]
|
|
92
|
+
|
|
93
|
+
self._eaf = EAF()
|
|
94
|
+
eaf = self._eaf
|
|
95
|
+
|
|
96
|
+
result: list[tuple[bool, float, float]] = []
|
|
97
|
+
|
|
98
|
+
def _connect() -> None:
|
|
99
|
+
if not eaf.connect(self._device_number):
|
|
100
|
+
result.append((False, 0.0, 0.0))
|
|
101
|
+
return
|
|
102
|
+
eaf.setMaximalStep(self._max_steps)
|
|
103
|
+
eaf.setBacklash(self._backlash)
|
|
104
|
+
eaf.setDirection(self._direction)
|
|
105
|
+
eaf.setSound(self._sound)
|
|
106
|
+
result.append((True, eaf.getTemperature(), eaf.getPosition()))
|
|
107
|
+
|
|
108
|
+
if not await self._run_blocking(_connect):
|
|
109
|
+
raise TimeoutError(
|
|
110
|
+
f"Timed out connecting to EAF focuser after {_SDK_CALL_TIMEOUT}s."
|
|
111
|
+
)
|
|
112
|
+
connected, temperature, position = result[0]
|
|
113
|
+
if not connected:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"EAF focuser failed to connect. Is the device connected via USB? Correct device_number?"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
log.info("Connected to EAF focuser, temperature: %.2f°C", temperature)
|
|
119
|
+
|
|
120
|
+
await MotionStatusMixin.open(self)
|
|
121
|
+
await self._change_motion_status(MotionStatus.IDLE)
|
|
122
|
+
|
|
123
|
+
self._focus_setpoint = position * _STEP_TO_MM
|
|
124
|
+
await self.comm.set_state(
|
|
125
|
+
IFocuser,
|
|
126
|
+
FocuserState(focus=self._focus_setpoint, focus_offset=self._focus_offset),
|
|
127
|
+
)
|
|
128
|
+
await self.comm.set_state(IReady, ReadyState(ready=True))
|
|
129
|
+
|
|
130
|
+
async def close(self) -> None:
|
|
131
|
+
"""Close module."""
|
|
132
|
+
if self._eaf is not None:
|
|
133
|
+
eaf = self._eaf
|
|
134
|
+
self._eaf = None
|
|
135
|
+
if not await self._run_blocking(eaf.disconnect):
|
|
136
|
+
log.error(
|
|
137
|
+
"Timed out disconnecting EAF focuser after %.1fs.",
|
|
138
|
+
_SDK_CALL_TIMEOUT,
|
|
139
|
+
)
|
|
140
|
+
await Module.close(self)
|
|
141
|
+
|
|
142
|
+
async def init(self, **kwargs: Any) -> None:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
async def park(self, **kwargs: Any) -> None:
|
|
146
|
+
"""Park focuser at position zero."""
|
|
147
|
+
await self.stop_motion()
|
|
148
|
+
await self.set_focus(0.0)
|
|
149
|
+
|
|
150
|
+
async def stop_motion(self, device: str | None = None, **kwargs: Any) -> None:
|
|
151
|
+
"""Stop focuser motion."""
|
|
152
|
+
if self._eaf is not None:
|
|
153
|
+
if not await self._run_blocking(self._eaf.stop):
|
|
154
|
+
log.error(
|
|
155
|
+
"Timed out stopping EAF focuser motion after %.1fs.",
|
|
156
|
+
_SDK_CALL_TIMEOUT,
|
|
157
|
+
)
|
|
158
|
+
await self._change_motion_status(MotionStatus.IDLE)
|
|
159
|
+
|
|
160
|
+
async def set_focus(self, focus: float, **kwargs: Any) -> None:
|
|
161
|
+
"""Move focuser to given position.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
focus: New focus position in mm.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
MoveError: If focuser cannot be moved.
|
|
168
|
+
"""
|
|
169
|
+
if self._eaf is None:
|
|
170
|
+
raise ValueError("Not connected.")
|
|
171
|
+
eaf = self._eaf
|
|
172
|
+
|
|
173
|
+
total_mm = focus + self._focus_offset
|
|
174
|
+
step = int(total_mm / _STEP_TO_MM)
|
|
175
|
+
log.info(
|
|
176
|
+
"Moving EAF to %.4f mm (offset %.4f mm, step %d)...",
|
|
177
|
+
focus,
|
|
178
|
+
self._focus_offset,
|
|
179
|
+
step,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
await self._change_motion_status(MotionStatus.SLEWING)
|
|
183
|
+
|
|
184
|
+
result: list[bool] = []
|
|
185
|
+
|
|
186
|
+
def _move() -> None:
|
|
187
|
+
if not eaf.move(step):
|
|
188
|
+
result.append(False)
|
|
189
|
+
return
|
|
190
|
+
# run the whole "move, then poll until done" sequence as a single blocking call, so
|
|
191
|
+
# only one thread gets spawned per move rather than one per 0.5s poll (see _run_blocking)
|
|
192
|
+
while eaf.isMoving():
|
|
193
|
+
time.sleep(0.1)
|
|
194
|
+
result.append(True)
|
|
195
|
+
|
|
196
|
+
if not await self._run_blocking(_move, timeout=_MOVE_TIMEOUT):
|
|
197
|
+
await self._change_motion_status(MotionStatus.ERROR)
|
|
198
|
+
raise exc.MoveError(f"Timed out moving EAF motor after {_MOVE_TIMEOUT}s.")
|
|
199
|
+
if not result[0]:
|
|
200
|
+
await self._change_motion_status(MotionStatus.ERROR)
|
|
201
|
+
raise exc.MoveError("Could not move EAF motor.")
|
|
202
|
+
|
|
203
|
+
self._focus_setpoint = focus
|
|
204
|
+
await self._change_motion_status(MotionStatus.POSITIONED)
|
|
205
|
+
await self.comm.set_state(
|
|
206
|
+
IFocuser,
|
|
207
|
+
FocuserState(focus=self._focus_setpoint, focus_offset=self._focus_offset),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def set_focus_offset(self, offset: float, **kwargs: Any) -> None:
|
|
211
|
+
"""Set focus offset and re-apply.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
offset: New focus offset in mm.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
MoveError: If focuser cannot be moved.
|
|
218
|
+
"""
|
|
219
|
+
log.info("Setting focus offset to %.4f mm.", offset)
|
|
220
|
+
self._focus_offset = offset
|
|
221
|
+
await self.set_focus(self._focus_setpoint)
|
|
222
|
+
|
|
223
|
+
async def _poll_temperature(self) -> None:
|
|
224
|
+
"""Background task: periodically reads EAF temperature."""
|
|
225
|
+
while True:
|
|
226
|
+
try:
|
|
227
|
+
if self._eaf is not None:
|
|
228
|
+
eaf = self._eaf
|
|
229
|
+
result: list[float] = []
|
|
230
|
+
|
|
231
|
+
def _get_temp() -> None:
|
|
232
|
+
result.append(eaf.getTemperature())
|
|
233
|
+
|
|
234
|
+
if await self._run_blocking(_get_temp):
|
|
235
|
+
await self.comm.set_state(
|
|
236
|
+
ITemperatures,
|
|
237
|
+
TemperaturesState(
|
|
238
|
+
readings=[SensorReading(name="EAF", value=result[0])]
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
except Exception:
|
|
242
|
+
pass
|
|
243
|
+
await asyncio.sleep(10)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
__all__ = ["EAFFocuser"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
libEAFFocuser.dylib.1.6
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import pyobs.utils.exceptions as exc
|
|
6
|
-
from pyobs.interfaces import IFocuser, IReady, ITemperatures
|
|
7
|
-
from pyobs.interfaces.IFocuser import FocuserState
|
|
8
|
-
from pyobs.interfaces.IReady import ReadyState
|
|
9
|
-
from pyobs.interfaces.ITemperatures import SensorReading, TemperaturesState
|
|
10
|
-
from pyobs.mixins import MotionStatusMixin
|
|
11
|
-
from pyobs.modules import Module
|
|
12
|
-
from pyobs.utils.enums import MotionStatus
|
|
13
|
-
|
|
14
|
-
log = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
_STEP_TO_MM = 0.00242105263
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class EAFFocuser(Module, MotionStatusMixin, IFocuser, ITemperatures):
|
|
20
|
-
"""A pyobs module for the ZWO EAF electronic auto focuser."""
|
|
21
|
-
|
|
22
|
-
__module__ = "pyobs_zwoeaf"
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
device_number: int = 0,
|
|
27
|
-
max_steps: int = 60000,
|
|
28
|
-
backlash: int = 0,
|
|
29
|
-
direction: bool = True,
|
|
30
|
-
sound: bool = True,
|
|
31
|
-
**kwargs: Any,
|
|
32
|
-
) -> None:
|
|
33
|
-
Module.__init__(self, **kwargs)
|
|
34
|
-
MotionStatusMixin.__init__(self)
|
|
35
|
-
|
|
36
|
-
self._device_number = device_number
|
|
37
|
-
self._max_steps = max_steps
|
|
38
|
-
self._backlash = backlash
|
|
39
|
-
self._direction = direction
|
|
40
|
-
self._sound = sound
|
|
41
|
-
self._eaf: Any | None = None
|
|
42
|
-
self._focus_setpoint = 0.0
|
|
43
|
-
self._focus_offset = 0.0
|
|
44
|
-
|
|
45
|
-
self.add_background_task(self._poll_temperature)
|
|
46
|
-
|
|
47
|
-
async def open(self) -> None:
|
|
48
|
-
"""Open module."""
|
|
49
|
-
await Module.open(self)
|
|
50
|
-
|
|
51
|
-
from .EAF_focuser import EAF # type: ignore[import-untyped]
|
|
52
|
-
|
|
53
|
-
self._eaf = EAF()
|
|
54
|
-
if not self._eaf.connect(self._device_number):
|
|
55
|
-
raise ValueError("EAF focuser failed to connect. Is the device connected via USB? Correct device_number?")
|
|
56
|
-
|
|
57
|
-
self._eaf.setMaximalStep(self._max_steps)
|
|
58
|
-
self._eaf.setBacklash(self._backlash)
|
|
59
|
-
self._eaf.setDirection(self._direction)
|
|
60
|
-
self._eaf.setSound(self._sound)
|
|
61
|
-
|
|
62
|
-
log.info("Connected to EAF focuser, temperature: %.2f°C", self._eaf.getTemperature())
|
|
63
|
-
|
|
64
|
-
await MotionStatusMixin.open(self)
|
|
65
|
-
await self._change_motion_status(MotionStatus.IDLE)
|
|
66
|
-
|
|
67
|
-
self._focus_setpoint = self._eaf.getPosition() * _STEP_TO_MM
|
|
68
|
-
await self.comm.set_state(IFocuser, FocuserState(focus=self._focus_setpoint, focus_offset=self._focus_offset))
|
|
69
|
-
await self.comm.set_state(IReady, ReadyState(ready=True))
|
|
70
|
-
|
|
71
|
-
async def close(self) -> None:
|
|
72
|
-
"""Close module."""
|
|
73
|
-
if self._eaf is not None:
|
|
74
|
-
self._eaf.disconnect()
|
|
75
|
-
self._eaf = None
|
|
76
|
-
await Module.close(self)
|
|
77
|
-
|
|
78
|
-
async def init(self, **kwargs: Any) -> None:
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
async def park(self, **kwargs: Any) -> None:
|
|
82
|
-
"""Park focuser at position zero."""
|
|
83
|
-
await self.stop_motion()
|
|
84
|
-
await self.set_focus(0.0)
|
|
85
|
-
|
|
86
|
-
async def stop_motion(self, device: str | None = None, **kwargs: Any) -> None:
|
|
87
|
-
"""Stop focuser motion."""
|
|
88
|
-
if self._eaf is not None:
|
|
89
|
-
self._eaf.stop()
|
|
90
|
-
await self._change_motion_status(MotionStatus.IDLE)
|
|
91
|
-
|
|
92
|
-
async def set_focus(self, focus: float, **kwargs: Any) -> None:
|
|
93
|
-
"""Move focuser to given position.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
focus: New focus position in mm.
|
|
97
|
-
|
|
98
|
-
Raises:
|
|
99
|
-
MoveError: If focuser cannot be moved.
|
|
100
|
-
"""
|
|
101
|
-
if self._eaf is None:
|
|
102
|
-
raise ValueError("Not connected.")
|
|
103
|
-
|
|
104
|
-
total_mm = focus + self._focus_offset
|
|
105
|
-
step = int(total_mm / _STEP_TO_MM)
|
|
106
|
-
log.info("Moving EAF to %.4f mm (offset %.4f mm, step %d)...", focus, self._focus_offset, step)
|
|
107
|
-
|
|
108
|
-
await self._change_motion_status(MotionStatus.SLEWING)
|
|
109
|
-
if not self._eaf.move(step):
|
|
110
|
-
await self._change_motion_status(MotionStatus.ERROR)
|
|
111
|
-
raise exc.MoveError("Could not move EAF motor.")
|
|
112
|
-
|
|
113
|
-
while self._eaf.isMoving():
|
|
114
|
-
await asyncio.sleep(0.5)
|
|
115
|
-
|
|
116
|
-
self._focus_setpoint = focus
|
|
117
|
-
await self._change_motion_status(MotionStatus.POSITIONED)
|
|
118
|
-
await self.comm.set_state(IFocuser, FocuserState(focus=self._focus_setpoint, focus_offset=self._focus_offset))
|
|
119
|
-
|
|
120
|
-
async def set_focus_offset(self, offset: float, **kwargs: Any) -> None:
|
|
121
|
-
"""Set focus offset and re-apply.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
offset: New focus offset in mm.
|
|
125
|
-
|
|
126
|
-
Raises:
|
|
127
|
-
MoveError: If focuser cannot be moved.
|
|
128
|
-
"""
|
|
129
|
-
log.info("Setting focus offset to %.4f mm.", offset)
|
|
130
|
-
self._focus_offset = offset
|
|
131
|
-
await self.set_focus(self._focus_setpoint)
|
|
132
|
-
|
|
133
|
-
async def _poll_temperature(self) -> None:
|
|
134
|
-
"""Background task: periodically reads EAF temperature."""
|
|
135
|
-
while True:
|
|
136
|
-
try:
|
|
137
|
-
if self._eaf is not None:
|
|
138
|
-
temp = self._eaf.getTemperature()
|
|
139
|
-
await self.comm.set_state(
|
|
140
|
-
ITemperatures,
|
|
141
|
-
TemperaturesState(readings=[SensorReading(name="EAF", value=temp)]),
|
|
142
|
-
)
|
|
143
|
-
except Exception:
|
|
144
|
-
pass
|
|
145
|
-
await asyncio.sleep(10)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
__all__ = ["EAFFocuser"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|