citrascope 0.1.0__py3-none-any.whl → 0.3.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.
Files changed (35) hide show
  1. citrascope/__main__.py +8 -5
  2. citrascope/api/abstract_api_client.py +7 -0
  3. citrascope/api/citra_api_client.py +30 -1
  4. citrascope/citra_scope_daemon.py +214 -61
  5. citrascope/hardware/abstract_astro_hardware_adapter.py +70 -2
  6. citrascope/hardware/adapter_registry.py +94 -0
  7. citrascope/hardware/indi_adapter.py +456 -16
  8. citrascope/hardware/kstars_dbus_adapter.py +179 -0
  9. citrascope/hardware/nina_adv_http_adapter.py +593 -0
  10. citrascope/hardware/nina_adv_http_survey_template.json +328 -0
  11. citrascope/logging/__init__.py +2 -1
  12. citrascope/logging/_citrascope_logger.py +80 -1
  13. citrascope/logging/web_log_handler.py +74 -0
  14. citrascope/settings/citrascope_settings.py +145 -0
  15. citrascope/settings/settings_file_manager.py +126 -0
  16. citrascope/tasks/runner.py +124 -28
  17. citrascope/tasks/scope/base_telescope_task.py +25 -10
  18. citrascope/tasks/scope/static_telescope_task.py +11 -3
  19. citrascope/web/__init__.py +1 -0
  20. citrascope/web/app.py +470 -0
  21. citrascope/web/server.py +123 -0
  22. citrascope/web/static/api.js +82 -0
  23. citrascope/web/static/app.js +500 -0
  24. citrascope/web/static/config.js +362 -0
  25. citrascope/web/static/img/citra.png +0 -0
  26. citrascope/web/static/img/favicon.png +0 -0
  27. citrascope/web/static/style.css +120 -0
  28. citrascope/web/static/websocket.js +127 -0
  29. citrascope/web/templates/dashboard.html +354 -0
  30. {citrascope-0.1.0.dist-info → citrascope-0.3.0.dist-info}/METADATA +68 -36
  31. citrascope-0.3.0.dist-info/RECORD +38 -0
  32. {citrascope-0.1.0.dist-info → citrascope-0.3.0.dist-info}/WHEEL +1 -1
  33. citrascope/settings/_citrascope_settings.py +0 -42
  34. citrascope-0.1.0.dist-info/RECORD +0 -21
  35. {citrascope-0.1.0.dist-info → citrascope-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,179 @@
1
+ import base64
2
+ import logging
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from citrascope.hardware.abstract_astro_hardware_adapter import (
7
+ AbstractAstroHardwareAdapter,
8
+ ObservationStrategy,
9
+ SettingSchemaEntry,
10
+ )
11
+
12
+
13
+ class KStarsDBusAdapter(AbstractAstroHardwareAdapter):
14
+ """Adapter for controlling astronomical equipment through KStars via DBus."""
15
+
16
+ def __init__(self, logger: logging.Logger, images_dir: Path, **kwargs):
17
+ """
18
+ Initialize the KStars DBus adapter.
19
+
20
+ Args:
21
+ logger: Logger instance for logging messages
22
+ images_dir: Path to the images directory
23
+ **kwargs: Configuration including bus_name
24
+ """
25
+ super().__init__(images_dir=images_dir)
26
+ self.logger: logging.Logger = logger
27
+ self.bus_name = kwargs.get("bus_name", "org.kde.kstars")
28
+ self.bus = None
29
+ self.kstars = None
30
+ self.ekos = None
31
+ self.mount = None
32
+ self.camera = None
33
+ self.scheduler = None
34
+
35
+ @classmethod
36
+ def get_settings_schema(cls) -> list[SettingSchemaEntry]:
37
+ """
38
+ Return a schema describing configurable settings for the KStars DBus adapter.
39
+ """
40
+ return [
41
+ {
42
+ "name": "bus_name",
43
+ "friendly_name": "D-Bus Service Name",
44
+ "type": "str",
45
+ "default": "org.kde.kstars",
46
+ "description": "D-Bus service name for KStars",
47
+ "required": True,
48
+ "placeholder": "org.kde.kstars",
49
+ }
50
+ ]
51
+
52
+ def _do_point_telescope(self, ra: float, dec: float):
53
+ raise NotImplementedError
54
+
55
+ def get_observation_strategy(self) -> ObservationStrategy:
56
+ return ObservationStrategy.SEQUENCE_TO_CONTROLLER
57
+
58
+ def perform_observation_sequence(self, task_id, satellite_data) -> str:
59
+ raise NotImplementedError
60
+
61
+ def connect(self) -> bool:
62
+ """
63
+ Connect to KStars via DBus and initialize the Ekos session.
64
+
65
+ Returns:
66
+ bool: True if connection successful, False otherwise
67
+ """
68
+ try:
69
+ # Import dbus here to make it an optional dependency
70
+ try:
71
+ import dbus
72
+ except ImportError:
73
+ self.logger.error("dbus-python is not installed. Install with: pip install dbus-python")
74
+ return False
75
+
76
+ # Connect to the session bus
77
+ self.logger.info("Connecting to DBus session bus...")
78
+ self.bus = dbus.SessionBus()
79
+
80
+ # Get the KStars service
81
+ try:
82
+ kstars_obj = self.bus.get_object(self.bus_name, "/KStars")
83
+ self.kstars = dbus.Interface(kstars_obj, dbus_interface="org.kde.kstars")
84
+ self.logger.info("Connected to KStars DBus interface")
85
+ except dbus.DBusException as e:
86
+ self.logger.error(f"Failed to connect to KStars: {e}")
87
+ self.logger.error("Make sure KStars is running and DBus is enabled")
88
+ return False
89
+
90
+ # Get the Ekos interface
91
+ try:
92
+ ekos_obj = self.bus.get_object(self.bus_name, "/KStars/Ekos")
93
+ self.ekos = dbus.Interface(ekos_obj, dbus_interface="org.kde.kstars.Ekos")
94
+ self.logger.info("Connected to Ekos interface")
95
+ except dbus.DBusException as e:
96
+ self.logger.warning(f"Failed to connect to Ekos interface: {e}")
97
+ self.logger.warning("Attempting to start Ekos...")
98
+
99
+ # Try to start Ekos if it's not running
100
+ try:
101
+ self.kstars.startEkos()
102
+ time.sleep(2) # Give Ekos time to start
103
+ ekos_obj = self.bus.get_object(self.bus_name, "/KStars/Ekos")
104
+ self.ekos = dbus.Interface(ekos_obj, dbus_interface="org.kde.kstars.Ekos")
105
+ self.logger.info("Started and connected to Ekos interface")
106
+ except Exception as start_error:
107
+ self.logger.error(f"Failed to start Ekos: {start_error}")
108
+ return False
109
+
110
+ # Get Mount interface
111
+ try:
112
+ mount_obj = self.bus.get_object(self.bus_name, "/KStars/Ekos/Mount")
113
+ self.mount = dbus.Interface(mount_obj, dbus_interface="org.kde.kstars.Ekos.Mount")
114
+ self.logger.info("Connected to Mount interface")
115
+ except dbus.DBusException as e:
116
+ self.logger.warning(f"Mount interface not available: {e}")
117
+
118
+ # Get Camera interface
119
+ try:
120
+ camera_obj = self.bus.get_object(self.bus_name, "/KStars/Ekos/Camera")
121
+ self.camera = dbus.Interface(camera_obj, dbus_interface="org.kde.kstars.Ekos.Camera")
122
+ self.logger.info("Connected to Camera interface")
123
+ except dbus.DBusException as e:
124
+ self.logger.warning(f"Camera interface not available: {e}")
125
+
126
+ # Get Scheduler/Sequence interface
127
+ try:
128
+ scheduler_obj = self.bus.get_object(self.bus_name, "/KStars/Ekos/Scheduler")
129
+ self.scheduler = dbus.Interface(scheduler_obj, dbus_interface="org.kde.kstars.Ekos.Scheduler")
130
+ self.logger.info("Connected to Scheduler interface")
131
+ except dbus.DBusException as e:
132
+ self.logger.warning(f"Scheduler interface not available: {e}")
133
+
134
+ self.logger.info("Successfully connected to KStars via DBus")
135
+ return True
136
+
137
+ except Exception as e:
138
+ self.logger.error(f"Failed to connect to KStars via DBus: {e}")
139
+ return False
140
+
141
+ def disconnect(self):
142
+ raise NotImplementedError
143
+
144
+ def is_telescope_connected(self) -> bool:
145
+ """Check if telescope is connected and responsive."""
146
+ # KStars adapter is incomplete - return False for now
147
+ return self.mount is not None
148
+
149
+ def is_camera_connected(self) -> bool:
150
+ """Check if camera is connected and responsive."""
151
+ # KStars adapter is incomplete - return False for now
152
+ return self.camera is not None
153
+
154
+ def list_devices(self) -> list[str]:
155
+ raise NotImplementedError
156
+
157
+ def select_telescope(self, device_name: str) -> bool:
158
+ raise NotImplementedError
159
+
160
+ def get_telescope_direction(self) -> tuple[float, float]:
161
+ raise NotImplementedError
162
+
163
+ def telescope_is_moving(self) -> bool:
164
+ raise NotImplementedError
165
+
166
+ def select_camera(self, device_name: str) -> bool:
167
+ raise NotImplementedError
168
+
169
+ def take_image(self, task_id: str, exposure_duration_seconds=1) -> str:
170
+ raise NotImplementedError
171
+
172
+ def set_custom_tracking_rate(self, ra_rate: float, dec_rate: float):
173
+ raise NotImplementedError
174
+
175
+ def get_tracking_rate(self) -> tuple[float, float]:
176
+ raise NotImplementedError
177
+
178
+ def perform_alignment(self, target_ra: float, target_dec: float) -> bool:
179
+ raise NotImplementedError