citrascope 0.5.2__py3-none-any.whl → 0.7.0__py3-none-any.whl
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.
- citrascope/citra_scope_daemon.py +22 -38
- citrascope/hardware/abstract_astro_hardware_adapter.py +64 -6
- citrascope/hardware/kstars_dbus_adapter.py +875 -30
- citrascope/hardware/kstars_scheduler_template.esl +30 -0
- citrascope/hardware/kstars_sequence_template.esq +16 -0
- citrascope/hardware/nina_adv_http_adapter.py +74 -59
- citrascope/hardware/nina_adv_http_survey_template.json +4 -4
- citrascope/settings/citrascope_settings.py +25 -4
- citrascope/tasks/runner.py +103 -0
- citrascope/tasks/scope/static_telescope_task.py +6 -1
- citrascope/web/app.py +82 -37
- citrascope/web/static/app.js +83 -0
- citrascope/web/static/config.js +244 -39
- citrascope/web/templates/dashboard.html +62 -27
- {citrascope-0.5.2.dist-info → citrascope-0.7.0.dist-info}/METADATA +19 -1
- {citrascope-0.5.2.dist-info → citrascope-0.7.0.dist-info}/RECORD +19 -17
- {citrascope-0.5.2.dist-info → citrascope-0.7.0.dist-info}/WHEEL +0 -0
- {citrascope-0.5.2.dist-info → citrascope-0.7.0.dist-info}/entry_points.txt +0 -0
- {citrascope-0.5.2.dist-info → citrascope-0.7.0.dist-info}/licenses/LICENSE +0 -0
citrascope/citra_scope_daemon.py
CHANGED
|
@@ -35,7 +35,6 @@ class CitraScopeDaemon:
|
|
|
35
35
|
self.ground_station = None
|
|
36
36
|
self.telescope_record = None
|
|
37
37
|
self.configuration_error: Optional[str] = None
|
|
38
|
-
self._autofocus_in_progress = False
|
|
39
38
|
|
|
40
39
|
# Create web server instance (always enabled)
|
|
41
40
|
self.web_server = CitraScopeWebServer(daemon=self, host="0.0.0.0", port=self.settings.web_port)
|
|
@@ -200,14 +199,6 @@ class CitraScopeDaemon:
|
|
|
200
199
|
CITRASCOPE_LOGGER.error(error_msg, exc_info=True)
|
|
201
200
|
return False, error_msg
|
|
202
201
|
|
|
203
|
-
def is_autofocus_in_progress(self) -> bool:
|
|
204
|
-
"""Check if autofocus routine is currently running.
|
|
205
|
-
|
|
206
|
-
Returns:
|
|
207
|
-
bool: True if autofocus is in progress, False otherwise
|
|
208
|
-
"""
|
|
209
|
-
return self._autofocus_in_progress
|
|
210
|
-
|
|
211
202
|
def _save_filter_config(self):
|
|
212
203
|
"""Save filter configuration from adapter to settings if supported.
|
|
213
204
|
|
|
@@ -232,12 +223,7 @@ class CitraScopeDaemon:
|
|
|
232
223
|
CITRASCOPE_LOGGER.warning(f"Failed to save filter configuration: {e}")
|
|
233
224
|
|
|
234
225
|
def trigger_autofocus(self) -> tuple[bool, Optional[str]]:
|
|
235
|
-
"""
|
|
236
|
-
|
|
237
|
-
Requires task processing to be manually paused before running.
|
|
238
|
-
Checks that both:
|
|
239
|
-
1. Task processing is paused
|
|
240
|
-
2. No task is currently in-flight
|
|
226
|
+
"""Request autofocus to run at next safe point between tasks.
|
|
241
227
|
|
|
242
228
|
Returns:
|
|
243
229
|
Tuple of (success, error_message)
|
|
@@ -248,34 +234,32 @@ class CitraScopeDaemon:
|
|
|
248
234
|
if not self.hardware_adapter.supports_filter_management():
|
|
249
235
|
return False, "Hardware adapter does not support filter management"
|
|
250
236
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return False, "Autofocus already in progress"
|
|
237
|
+
if not self.task_manager:
|
|
238
|
+
return False, "Task manager not initialized"
|
|
254
239
|
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return False, "Task processing must be paused before running autofocus"
|
|
240
|
+
# Request autofocus - will run between tasks
|
|
241
|
+
self.task_manager.request_autofocus()
|
|
242
|
+
return True, None
|
|
259
243
|
|
|
260
|
-
|
|
261
|
-
|
|
244
|
+
def cancel_autofocus(self) -> bool:
|
|
245
|
+
"""Cancel pending autofocus request if queued.
|
|
262
246
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
247
|
+
Returns:
|
|
248
|
+
bool: True if autofocus was cancelled, False if nothing to cancel.
|
|
249
|
+
"""
|
|
250
|
+
if not self.task_manager:
|
|
251
|
+
return False
|
|
252
|
+
return self.task_manager.cancel_autofocus()
|
|
267
253
|
|
|
268
|
-
|
|
269
|
-
|
|
254
|
+
def is_autofocus_requested(self) -> bool:
|
|
255
|
+
"""Check if autofocus is currently queued.
|
|
270
256
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
finally:
|
|
278
|
-
self._autofocus_in_progress = False
|
|
257
|
+
Returns:
|
|
258
|
+
bool: True if autofocus is queued, False otherwise.
|
|
259
|
+
"""
|
|
260
|
+
if not self.task_manager:
|
|
261
|
+
return False
|
|
262
|
+
return self.task_manager.is_autofocus_requested()
|
|
279
263
|
|
|
280
264
|
def run(self):
|
|
281
265
|
# Start web server FIRST, so users can monitor/configure
|
|
@@ -26,10 +26,12 @@ class FilterConfig(TypedDict):
|
|
|
26
26
|
Attributes:
|
|
27
27
|
name: Human-readable filter name (e.g., 'Luminance', 'Red', 'Ha')
|
|
28
28
|
focus_position: Focuser position for this filter in steps
|
|
29
|
+
enabled: Whether this filter is enabled for observations (default: True)
|
|
29
30
|
"""
|
|
30
31
|
|
|
31
32
|
name: str
|
|
32
33
|
focus_position: int
|
|
34
|
+
enabled: bool
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class ObservationStrategy(Enum):
|
|
@@ -43,14 +45,28 @@ class AbstractAstroHardwareAdapter(ABC):
|
|
|
43
45
|
|
|
44
46
|
_slew_min_distance_deg: float = 2.0
|
|
45
47
|
scope_slew_rate_degrees_per_second: float = 0.0
|
|
48
|
+
DEFAULT_FOCUS_POSITION: int = 0 # Default focus position, can be overridden by subclasses
|
|
46
49
|
|
|
47
|
-
def __init__(self, images_dir: Path):
|
|
48
|
-
"""Initialize the adapter with images directory.
|
|
50
|
+
def __init__(self, images_dir: Path, **kwargs):
|
|
51
|
+
"""Initialize the adapter with images directory and optional filter configuration.
|
|
49
52
|
|
|
50
53
|
Args:
|
|
51
54
|
images_dir: Path to the images directory
|
|
55
|
+
**kwargs: Additional configuration including 'filters' dict
|
|
52
56
|
"""
|
|
53
57
|
self.images_dir = images_dir
|
|
58
|
+
self.filter_map = {}
|
|
59
|
+
|
|
60
|
+
# Load filter configuration from settings if available
|
|
61
|
+
saved_filters = kwargs.get("filters", {})
|
|
62
|
+
for filter_id, filter_data in saved_filters.items():
|
|
63
|
+
try:
|
|
64
|
+
# Default enabled to True for backward compatibility
|
|
65
|
+
if "enabled" not in filter_data:
|
|
66
|
+
filter_data["enabled"] = True
|
|
67
|
+
self.filter_map[int(filter_id)] = filter_data
|
|
68
|
+
except (ValueError, TypeError):
|
|
69
|
+
pass # Skip invalid filter IDs
|
|
54
70
|
|
|
55
71
|
@classmethod
|
|
56
72
|
@abstractmethod
|
|
@@ -201,6 +217,14 @@ class AbstractAstroHardwareAdapter(ABC):
|
|
|
201
217
|
"""
|
|
202
218
|
raise NotImplementedError(f"{self.__class__.__name__} does not support autofocus")
|
|
203
219
|
|
|
220
|
+
def supports_autofocus(self) -> bool:
|
|
221
|
+
"""Indicates whether this adapter supports autofocus functionality.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
bool: True if the adapter can perform autofocus, False otherwise.
|
|
225
|
+
"""
|
|
226
|
+
return False
|
|
227
|
+
|
|
204
228
|
def supports_filter_management(self) -> bool:
|
|
205
229
|
"""Indicates whether this adapter supports filter/focus management.
|
|
206
230
|
|
|
@@ -217,14 +241,22 @@ class AbstractAstroHardwareAdapter(ABC):
|
|
|
217
241
|
Each FilterConfig contains:
|
|
218
242
|
- name (str): Filter name
|
|
219
243
|
- focus_position (int): Focuser position for this filter
|
|
244
|
+
- enabled (bool): Whether filter is enabled for observations
|
|
220
245
|
|
|
221
246
|
Example:
|
|
222
247
|
{
|
|
223
|
-
"1": {"name": "Luminance", "focus_position": 9000},
|
|
224
|
-
"2": {"name": "Red", "focus_position": 9050}
|
|
248
|
+
"1": {"name": "Luminance", "focus_position": 9000, "enabled": True},
|
|
249
|
+
"2": {"name": "Red", "focus_position": 9050, "enabled": False}
|
|
225
250
|
}
|
|
226
251
|
"""
|
|
227
|
-
return {
|
|
252
|
+
return {
|
|
253
|
+
str(filter_id): {
|
|
254
|
+
"name": filter_data["name"],
|
|
255
|
+
"focus_position": filter_data["focus_position"],
|
|
256
|
+
"enabled": filter_data.get("enabled", True),
|
|
257
|
+
}
|
|
258
|
+
for filter_id, filter_data in self.filter_map.items()
|
|
259
|
+
}
|
|
228
260
|
|
|
229
261
|
def update_filter_focus(self, filter_id: str, focus_position: int) -> bool:
|
|
230
262
|
"""Update the focus position for a specific filter.
|
|
@@ -236,4 +268,30 @@ class AbstractAstroHardwareAdapter(ABC):
|
|
|
236
268
|
Returns:
|
|
237
269
|
bool: True if update was successful, False otherwise
|
|
238
270
|
"""
|
|
239
|
-
|
|
271
|
+
try:
|
|
272
|
+
filter_id_int = int(filter_id)
|
|
273
|
+
if filter_id_int in self.filter_map:
|
|
274
|
+
self.filter_map[filter_id_int]["focus_position"] = focus_position
|
|
275
|
+
return True
|
|
276
|
+
return False
|
|
277
|
+
except (ValueError, KeyError):
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
def update_filter_enabled(self, filter_id: str, enabled: bool) -> bool:
|
|
281
|
+
"""Update the enabled state for a specific filter.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
filter_id: Filter ID as string
|
|
285
|
+
enabled: New enabled state
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
bool: True if update was successful, False otherwise
|
|
289
|
+
"""
|
|
290
|
+
try:
|
|
291
|
+
filter_id_int = int(filter_id)
|
|
292
|
+
if filter_id_int in self.filter_map:
|
|
293
|
+
self.filter_map[filter_id_int]["enabled"] = enabled
|
|
294
|
+
return True
|
|
295
|
+
return False
|
|
296
|
+
except (ValueError, KeyError):
|
|
297
|
+
return False
|