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.
@@ -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
- """Trigger autofocus routine on the hardware adapter.
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
- # Prevent concurrent autofocus operations
252
- if self._autofocus_in_progress:
253
- return False, "Autofocus already in progress"
237
+ if not self.task_manager:
238
+ return False, "Task manager not initialized"
254
239
 
255
- # Require task processing to be manually paused
256
- if self.task_manager:
257
- if self.task_manager.is_processing_active():
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
- if self.task_manager.current_task_id is not None:
261
- return False, "A task is currently executing. Please wait for it to complete and try again"
244
+ def cancel_autofocus(self) -> bool:
245
+ """Cancel pending autofocus request if queued.
262
246
 
263
- self._autofocus_in_progress = True
264
- try:
265
- CITRASCOPE_LOGGER.info("Starting autofocus routine...")
266
- self.hardware_adapter.do_autofocus()
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
- # Save updated filter configuration after autofocus
269
- self._save_filter_config()
254
+ def is_autofocus_requested(self) -> bool:
255
+ """Check if autofocus is currently queued.
270
256
 
271
- CITRASCOPE_LOGGER.info("Autofocus routine completed successfully")
272
- return True, None
273
- except Exception as e:
274
- error_msg = f"Autofocus failed: {str(e)}"
275
- CITRASCOPE_LOGGER.error(error_msg, exc_info=True)
276
- return False, error_msg
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
- return False
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