Skip to content

Commit ac76058

Browse files
vincentberenzVincent Berenz
andauthored
version 0.1.16: aperture runner tested
* test_aperture, under construction * fixed bug in aperture runner. Tests passing. * updated nightskycam-focus to 0.1.5 --------- Co-authored-by: Vincent Berenz <vincent.berenz@tuebingen.mpg.de>
1 parent cdb52f0 commit ac76058

File tree

7 files changed

+275
-26
lines changed

7 files changed

+275
-26
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
*#*
55
*mypy_cache
66
*flycheck_*.py
7-
7+
.vscode
-72.8 KB
Binary file not shown.

dist/nightskycam-0.1.14.tar.gz

-53.8 KB
Binary file not shown.

nightskycam/aperture/runner.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
set_aperture,
1616
set_focus,
1717
)
18-
from nightskycam_serialization.status import (
19-
ApertureRunnerEntries,
20-
CamRunnerEntries,
21-
)
18+
from nightskycam_serialization.status import ApertureRunnerEntries, CamRunnerEntries
2219
from nightskyrunner.config_getter import ConfigGetter
2320
from nightskyrunner.runner import ThreadRunner, status_error
2421
from nightskyrunner.status import Level, NoSuchStatusError, Status
@@ -125,17 +122,13 @@ def _open_aperture(self, focus: int, reason: str) -> None:
125122
or self._focus != focus
126123
or self._opening in (Opening.CLOSED, Opening.UNSET)
127124
):
128-
self.log(
129-
Level.info, f"opening aperture (focus: {focus}): {reason}"
130-
)
125+
self.log(Level.info, f"opening aperture (focus: {focus}): {reason}")
131126
try:
132127
with adapter():
133128
set_focus(focus)
134129
set_aperture(Aperture.MAX)
135130
except Exception as e:
136-
raise RuntimeError(
137-
f"failed to set focus and open aperture: {e}"
138-
)
131+
raise RuntimeError(f"failed to set focus and open aperture: {e}")
139132
else:
140133
self._opening = Opening.OPENED
141134
self._focus = focus
@@ -205,20 +198,16 @@ def iterate(self) -> None:
205198

206199
# aperture not used, keep open
207200
if not use:
208-
self._return(status_entries, focus, True, "aperture not used")
201+
return self._return(status_entries, focus, True, "aperture not used")
209202

210203
# opening / closing based on the status of the camera
211204
if use_zwo_camera:
212205
active: Optional[bool] = self._camera_active()
213206
if active is not None:
214207
if active:
215-
return self._return(
216-
status_entries, focus, True, "camera active"
217-
)
208+
return self._return(status_entries, focus, True, "camera active")
218209
else:
219-
return self._return(
220-
status_entries, focus, False, "camera inactive"
221-
)
210+
return self._return(status_entries, focus, False, "camera inactive")
222211

223212
# if not using use_zwo_camera, then must be using start/end time
224213
time_now = datetime.now().time()

poetry.lock

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "nightskycam"
3-
version = "0.1.15"
3+
version = "0.1.16"
44
description = "taking pictures at night"
55
authors = ["Vincent Berenz <vberenz@tuebingen.mpg.de>"]
66
packages = [{ include = "nightskycam" }]

tests/test_aperture.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import tempfile
2+
from contextlib import contextmanager
3+
from datetime import datetime
4+
from datetime import time as datetime_time
5+
from pathlib import Path
6+
7+
# from nightskycam_focus.adapter import set_aperture, set_focus, Aperture
8+
from typing import Generator
9+
10+
import pytest
11+
import tomli_w
12+
from nightskycam_serialization.status import ApertureRunnerEntries, AsiCamRunnerEntries
13+
from nightskyrunner.config import Config
14+
from nightskyrunner.status import State, Status, wait_for_status
15+
16+
from nightskycam.aperture.runner import (
17+
Aperture,
18+
ApertureRunner,
19+
adapter,
20+
set_aperture,
21+
set_focus,
22+
)
23+
from nightskycam.utils.test_utils import (
24+
ConfigTester,
25+
configuration_test,
26+
get_manager,
27+
runner_started,
28+
wait_for,
29+
)
30+
31+
# --
32+
# mocking set_focus and set_aperture as the corresponding hardware
33+
# is likely not available on the test server.
34+
# --
35+
36+
37+
class _MockedHardware:
38+
activated: bool = False
39+
focus: int = -1
40+
aperture: Aperture = Aperture.MIN
41+
42+
43+
def mock_set_focus(target_value: int) -> None:
44+
if not _MockedHardware.activated:
45+
raise RuntimeError("hardware not activated")
46+
_MockedHardware.focus = target_value
47+
48+
49+
def mock_set_aperture(target_value: Aperture) -> None:
50+
if not _MockedHardware.activated:
51+
raise RuntimeError("hardware not activated")
52+
_MockedHardware.aperture = target_value
53+
54+
55+
@contextmanager
56+
def mock_adapter():
57+
try:
58+
_MockedHardware.activated = True
59+
yield
60+
finally:
61+
_MockedHardware.activated = False
62+
63+
64+
def _datetime_now_time():
65+
return datetime_time(hour=9, minute=0, second=0)
66+
67+
68+
@pytest.fixture(autouse=True)
69+
def patch_set_focus(mocker):
70+
mocker.patch(__name__ + ".set_focus", side_effect=mock_set_focus)
71+
mocker.patch("nightskycam.aperture.runner.set_focus", side_effect=mock_set_focus)
72+
73+
74+
@pytest.fixture(autouse=True)
75+
def patch_set_aperture(mocker):
76+
mocker.patch(__name__ + ".set_aperture", side_effect=mock_set_aperture)
77+
mocker.patch(
78+
"nightskycam.aperture.runner.set_aperture",
79+
side_effect=mock_set_aperture,
80+
)
81+
82+
83+
@pytest.fixture(autouse=True)
84+
def patch_adapter(mocker):
85+
mocker.patch(__name__ + ".adapter", side_effect=mock_adapter)
86+
mocker.patch("nightskycam.aperture.runner.adapter", side_effect=mock_adapter)
87+
88+
89+
@pytest.fixture(autouse=True)
90+
def patch_datetime_now_time(mocker):
91+
# Create a mock datetime object with the desired time
92+
class MockDateTime(datetime):
93+
@classmethod
94+
def now(cls):
95+
return cls(2023, 1, 1, 9, 0) # January 1, 2023, 9:00 AM
96+
97+
# Patch the datetime class in the runner module
98+
mocker.patch("nightskycam.aperture.runner.datetime", MockDateTime)
99+
100+
101+
def test_mocked_functions() -> None:
102+
with pytest.raises(RuntimeError):
103+
set_focus(100)
104+
with adapter():
105+
set_focus(400)
106+
set_aperture(Aperture.V1)
107+
assert _MockedHardware.focus == 400
108+
assert _MockedHardware.aperture == Aperture.V1
109+
110+
111+
# --------------------------------------------------------------
112+
113+
114+
@pytest.fixture
115+
def tmp_dir(request, scope="function") -> Generator[Path, None, None]:
116+
"""
117+
Fixture yielding a temp directory and a temp file
118+
"""
119+
folder_ = tempfile.TemporaryDirectory()
120+
folder = Path(folder_.name)
121+
try:
122+
yield folder
123+
finally:
124+
folder_.cleanup()
125+
126+
127+
class _RunnerConfig:
128+
@classmethod
129+
def get_config(cls, destination_folder: Path, unsupported: bool = False) -> Config:
130+
if unsupported:
131+
return {
132+
"use": 1, # not a bool
133+
"use_zwo_camera": 0, # not a bool
134+
"start_time": "10-00", # not expected format
135+
"stop_time": "noon", # not supported
136+
"focus": -600, # out of bound
137+
}
138+
else:
139+
return {
140+
"frequency": 10,
141+
"use": True,
142+
"use_zwo_camera": False,
143+
"start_time": "10:00",
144+
"stop_time": "17:30",
145+
"focus": 600,
146+
}
147+
148+
@classmethod
149+
def get_config_tester(cls, destination_folder: Path) -> ConfigTester:
150+
return ConfigTester(
151+
cls.get_config(destination_folder, unsupported=False),
152+
cls.get_config(destination_folder, unsupported=True),
153+
)
154+
155+
156+
def test_configuration(tmp_dir) -> None:
157+
"""
158+
Testing instances of CamRunner behave correctly
159+
to changes of configuration.
160+
"""
161+
config_tester = _RunnerConfig.get_config_tester(tmp_dir)
162+
configuration_test(ApertureRunner, config_tester, timeout=30.0)
163+
164+
165+
def test_open_close(tmp_dir) -> None:
166+
167+
def _write_test_runner_config(config: Config, toml_path: Path) -> None:
168+
with open(toml_path, "wb") as f:
169+
tomli_w.dump(config, f)
170+
171+
def _aperture_closed() -> bool:
172+
return _MockedHardware.aperture == Aperture.MIN
173+
174+
def _aperture_opened() -> bool:
175+
return _MockedHardware.aperture == Aperture.MAX
176+
177+
def _focus_value() -> int:
178+
return _MockedHardware.focus
179+
180+
config: Config = {
181+
"frequency": 10,
182+
"use": True,
183+
"use_zwo_camera": False,
184+
"start_time": "10:00",
185+
"stop_time": "17:30",
186+
"focus": 600,
187+
}
188+
config_file = tmp_dir / "test_aperture_config.toml"
189+
_write_test_runner_config(
190+
config,
191+
config_file,
192+
)
193+
194+
asi_cam_runner_status = Status("asi_cam_runner", "AsiCamRunner")
195+
asi_cam_entries = AsiCamRunnerEntries(active="yes")
196+
asi_cam_runner_status.entries(asi_cam_entries)
197+
198+
def _set_asi_cam_active():
199+
status = Status.retrieve("asi_cam_runner")
200+
asi_cam_entries = AsiCamRunnerEntries(active="yes")
201+
status.entries(asi_cam_entries)
202+
203+
def _set_asi_cam_inactive():
204+
status = Status.retrieve("asi_cam_runner")
205+
asi_cam_entries = AsiCamRunnerEntries(active="no")
206+
status.entries(asi_cam_entries)
207+
208+
with get_manager((ApertureRunner, config_file)):
209+
wait_for(runner_started, True, args=(ApertureRunner.__name__,))
210+
wait_for_status(ApertureRunner.__name__, State.running, timeout=2.0)
211+
# the starting config uses the interval 10AM to 5:30PM, and time now
212+
# is mocked to 9AM. Therefore the aperture should open.
213+
wait_for(_aperture_opened, True)
214+
# changing the inverval to 8AM. Therefore the aperture should close
215+
config["start_time"] = "8:00"
216+
_write_test_runner_config(config, config_file)
217+
wait_for(_aperture_closed, True)
218+
# now activating the zwo asi camera and setting "use_zwo_camera" to True:
219+
# the aperture should open
220+
_set_asi_cam_active()
221+
config["use_zwo_camera"] = True
222+
_write_test_runner_config(config, config_file)
223+
wait_for(_aperture_opened, True)
224+
# setting the camera to inactive, the aperture should close
225+
_set_asi_cam_inactive()
226+
wait_for(_aperture_closed, True)
227+
# stop using the zwo asi camera, the aperture should open
228+
# (because 8am)
229+
_set_asi_cam_active()
230+
config["use_zwo_asi"] = False
231+
_write_test_runner_config(config, config_file)
232+
wait_for(_aperture_opened, True)
233+
# changing the focus
234+
assert _MockedHardware.focus == 600
235+
config["focus"] = 550
236+
_write_test_runner_config(config, config_file)
237+
wait_for(_focus_value, 550)
238+
# using zwo asi again
239+
_set_asi_cam_inactive()
240+
config["use_zwo_asi"] = True
241+
_write_test_runner_config(config, config_file)
242+
wait_for(_aperture_closed, True)
243+
# stop using the aperture adapter altogether
244+
config["use"] = False
245+
_write_test_runner_config(config, config_file)
246+
wait_for(_aperture_opened, True)
247+
# sanity check the runner is still running
248+
wait_for_status(ApertureRunner.__name__, State.running, timeout=2.0)

0 commit comments

Comments
 (0)