citrascope 0.3.0__tar.gz → 0.5.0__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.
Files changed (53) hide show
  1. {citrascope-0.3.0 → citrascope-0.5.0}/.github/copilot-instructions.md +2 -3
  2. {citrascope-0.3.0 → citrascope-0.5.0}/.gitignore +0 -3
  3. {citrascope-0.3.0 → citrascope-0.5.0}/.vscode/launch.json +4 -13
  4. {citrascope-0.3.0 → citrascope-0.5.0}/PKG-INFO +40 -32
  5. {citrascope-0.3.0 → citrascope-0.5.0}/README.md +38 -23
  6. citrascope-0.5.0/citrascope/__main__.py +23 -0
  7. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/api/citra_api_client.py +13 -1
  8. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/citra_scope_daemon.py +91 -19
  9. citrascope-0.5.0/citrascope/constants.py +23 -0
  10. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/abstract_astro_hardware_adapter.py +61 -0
  11. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/nina_adv_http_adapter.py +106 -77
  12. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/web_log_handler.py +9 -8
  13. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/citrascope_settings.py +34 -45
  14. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/runner.py +36 -2
  15. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/app.py +137 -13
  16. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/server.py +10 -1
  17. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/app.js +246 -17
  18. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/config.js +248 -9
  19. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/style.css +32 -0
  20. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/templates/dashboard.html +143 -7
  21. {citrascope-0.3.0 → citrascope-0.5.0}/pyproject.toml +3 -10
  22. citrascope-0.3.0/citrascope/__main__.py +0 -26
  23. citrascope-0.3.0/docs/index.md +0 -47
  24. {citrascope-0.3.0 → citrascope-0.5.0}/.devcontainer/devcontainer.json +0 -0
  25. {citrascope-0.3.0 → citrascope-0.5.0}/.flake8 +0 -0
  26. {citrascope-0.3.0 → citrascope-0.5.0}/.github/dependabot.yml +0 -0
  27. {citrascope-0.3.0 → citrascope-0.5.0}/.github/workflows/pypi-publish.yml +0 -0
  28. {citrascope-0.3.0 → citrascope-0.5.0}/.github/workflows/pytest.yml +0 -0
  29. {citrascope-0.3.0 → citrascope-0.5.0}/.pre-commit-config.yaml +0 -0
  30. {citrascope-0.3.0 → citrascope-0.5.0}/.python-version +0 -0
  31. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/__init__.py +0 -0
  32. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/api/abstract_api_client.py +0 -0
  33. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/adapter_registry.py +0 -0
  34. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/indi_adapter.py +0 -0
  35. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/kstars_dbus_adapter.py +0 -0
  36. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/nina_adv_http_survey_template.json +0 -0
  37. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/__init__.py +0 -0
  38. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/_citrascope_logger.py +0 -0
  39. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/__init__.py +0 -0
  40. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/settings_file_manager.py +0 -0
  41. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/base_telescope_task.py +0 -0
  42. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/static_telescope_task.py +0 -0
  43. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/tracking_telescope_task.py +0 -0
  44. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/task.py +0 -0
  45. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/__init__.py +0 -0
  46. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/api.js +0 -0
  47. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/img/citra.png +0 -0
  48. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/img/favicon.png +0 -0
  49. {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/websocket.js +0 -0
  50. {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_api_client.py +0 -0
  51. {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_hardware_adapter.py +0 -0
  52. {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_task_manager.py +0 -0
  53. {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/utils.py +0 -0
@@ -25,7 +25,6 @@ This project is a Python package for interacting with astronomical data and serv
25
25
  - `citrascope/web/templates/`: HTML templates
26
26
  - `citrascope/web/static/`: CSS and JavaScript files
27
27
  - `tests/`: Unit and integration tests
28
- - `docs/`: Project documentation
29
28
 
30
29
  ## Testing
31
30
  - All new features and bug fixes should include corresponding tests in `tests/`.
@@ -77,7 +76,7 @@ The web interface provides real-time monitoring and configuration for telescope
77
76
 
78
77
  This project relies on several key Python packages. Below are some of the most important ones and their roles:
79
78
 
80
- - **Click**: Used for building the command-line interface (CLI). The main entry point for the application (`python -m citrascope start`) is implemented using Click.
79
+ - **Click**: Used for building the command-line interface (CLI). The main entry point for the application (`python -m citrascope`) is implemented using Click.
81
80
  - **Pydantic-Settings**: Manages configuration and settings, ensuring type safety and validation for environment variables.
82
81
  - **Requests** and **HTTPX**: Handle HTTP requests for interacting with the Citra.space API.
83
82
  - **Python-Dateutil**: Provides robust date and time parsing utilities.
@@ -102,5 +101,5 @@ For a complete list of dependencies, refer to the `pyproject.toml` file.
102
101
 
103
102
  ## Additional Notes
104
103
  - Keep dependencies minimal and update `pyproject.toml` as needed.
105
- - Document any major changes in `docs/index.md`.
104
+ - Documentation is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository under `docs/citrascope/`.
106
105
  - Use pre-commit hooks for code quality.
@@ -68,9 +68,6 @@ instance/
68
68
  # Scrapy stuff:
69
69
  .scrapy
70
70
 
71
- # Sphinx documentation
72
- docs/build/
73
-
74
71
  # PyBuilder
75
72
  target/
76
73
 
@@ -4,29 +4,20 @@
4
4
  "version": "0.2.0",
5
5
  "configurations": [
6
6
  {
7
- "name": "Python: citrascope dev start",
7
+ "name": "Python: citrascope",
8
8
  "type": "debugpy",
9
9
  "request": "launch",
10
10
  "module": "citrascope",
11
- "args": ["--dev", "start"],
11
+ "args": [],
12
12
  "console": "integratedTerminal",
13
13
  "justMyCode": false
14
14
  },
15
15
  {
16
- "name": "Python: citrascope dev start, keep images",
16
+ "name": "Python: citrascope (custom port)",
17
17
  "type": "debugpy",
18
18
  "request": "launch",
19
19
  "module": "citrascope",
20
- "args": ["--dev", "--keep-images", "start"],
21
- "console": "integratedTerminal",
22
- "justMyCode": false
23
- },
24
- {
25
- "name": "Python: citrascope dev start DEBUG logging",
26
- "type": "debugpy",
27
- "request": "launch",
28
- "module": "citrascope",
29
- "args": ["--dev", "--log-level", "DEBUG", "start"],
20
+ "args": ["--web-port", "8080"],
30
21
  "console": "integratedTerminal",
31
22
  "justMyCode": false
32
23
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: citrascope
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Remotely control a telescope while it polls for tasks, collects and edge processes data, and delivers results and data for further processing.
5
5
  Author-email: Patrick McDavid <patrick@citra.space>
6
6
  License-Expression: MIT
@@ -9,13 +9,9 @@ Requires-Dist: click
9
9
  Requires-Dist: fastapi>=0.104.0
10
10
  Requires-Dist: httpx
11
11
  Requires-Dist: platformdirs>=4.0.0
12
- Requires-Dist: pydantic-settings
13
- Requires-Dist: pytest-cov
14
12
  Requires-Dist: python-dateutil
15
- Requires-Dist: python-json-logger
16
13
  Requires-Dist: requests
17
14
  Requires-Dist: skyfield
18
- Requires-Dist: types-python-dateutil
19
15
  Requires-Dist: uvicorn[standard]>=0.24.0
20
16
  Requires-Dist: websockets>=12.0
21
17
  Provides-Extra: all
@@ -38,10 +34,7 @@ Requires-Dist: mypy; extra == 'dev'
38
34
  Requires-Dist: pre-commit; extra == 'dev'
39
35
  Requires-Dist: pytest; extra == 'dev'
40
36
  Requires-Dist: pytest-cov; extra == 'dev'
41
- Provides-Extra: docs
42
- Requires-Dist: sphinx; extra == 'docs'
43
- Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
44
- Requires-Dist: sphinx-markdown-builder; extra == 'docs'
37
+ Requires-Dist: types-python-dateutil; extra == 'dev'
45
38
  Provides-Extra: indi
46
39
  Requires-Dist: pixelemon; extra == 'indi'
47
40
  Requires-Dist: plotly; extra == 'indi'
@@ -65,33 +58,46 @@ Remotely control a telescope while it polls for tasks, collects observations, an
65
58
  - Connects to configured telescope and camera hardware
66
59
  - Acts as a task daemon carrying out and remitting photography tasks
67
60
 
61
+ ## Documentation
62
+
63
+ Full documentation is available at [docs.citra.space](https://docs.citra.space/citrascope/).
64
+
65
+ Documentation source is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository.
66
+
68
67
  ## Installation
69
68
 
70
- ### Install with pipx
69
+ **Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
71
70
 
72
- [pipx](https://pipx.pypa.io/) installs the CLI tool in an isolated environment while making it globally available:
71
+ ### Check Your Python Version
73
72
 
74
73
  ```sh
75
- pipx install citrascope
74
+ python3 --version
76
75
  ```
77
76
 
78
- ### Optional Dependencies
77
+ If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
79
78
 
80
- CitraScope supports different hardware adapters through optional dependency groups:
79
+ ```sh
80
+ pyenv install 3.12.0
81
+ pyenv local 3.12.0 # Sets Python 3.12.0 for the current directory
82
+ ```
81
83
 
82
- - **INDI adapter** (for Linux-based telescope control):
83
- ```sh
84
- pipx install citrascope[indi]
85
- # or with pip: pip install citrascope[indi]
86
- ```
84
+ ### Install CitraScope
87
85
 
88
- - **All optional dependencies**:
89
- ```sh
90
- pipx install citrascope[all]
91
- # or with pip: pip install citrascope[all]
92
- ```
86
+ **Recommended: Using pip in a virtual environment**
93
87
 
94
- The base installation without optional dependencies supports the NINA adapter, which works via HTTP API calls and does not require additional Python packages.
88
+ ```sh
89
+ python3 -m venv citrascope-env
90
+ source citrascope-env/bin/activate # On Windows: citrascope-env\Scripts\activate
91
+ pip install citrascope
92
+ ```
93
+
94
+ ### Optional Dependencies
95
+
96
+ For Linux-based telescope control (INDI):
97
+
98
+ ```sh
99
+ pip install citrascope[indi]
100
+ ```
95
101
 
96
102
  This provides the `citrascope` command-line tool. To see available commands:
97
103
 
@@ -101,16 +107,18 @@ citrascope --help
101
107
 
102
108
  ## Usage
103
109
 
104
- Run the CLI tool:
110
+ ### Starting the Daemon
111
+
112
+ Run the daemon with:
105
113
 
106
114
  ```sh
107
- citrascope start
115
+ citrascope
108
116
  ```
109
117
 
110
- To connect to the Citra Dev server:
118
+ By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
111
119
 
112
120
  ```sh
113
- citrascope start --dev
121
+ citrascope --web-port 8080
114
122
  ```
115
123
 
116
124
  ## Developer Setup
@@ -174,10 +182,10 @@ Then create a release in the GitHub UI from the new tag. This triggers automatic
174
182
 
175
183
  If you are using Visual Studio Code, you can run or debug the project directly using the pre-configured launch options in `.vscode/launch.json`:
176
184
 
177
- - **Python: citrascope dev start** — Runs the main entry point with development options.
178
- - **Python: citrascope dev start DEBUG logging** — Runs with development options and sets log level to DEBUG for more detailed output.
185
+ - **Python: citrascope** — Runs the daemon with default settings
186
+ - **Python: citrascope (custom port)** — Runs with web interface on port 8080
179
187
 
180
- To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button. This is a convenient way to start or debug the app without manually entering commands.
188
+ To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
181
189
 
182
190
  ## Running Tests
183
191
 
@@ -9,33 +9,46 @@ Remotely control a telescope while it polls for tasks, collects observations, an
9
9
  - Connects to configured telescope and camera hardware
10
10
  - Acts as a task daemon carrying out and remitting photography tasks
11
11
 
12
+ ## Documentation
13
+
14
+ Full documentation is available at [docs.citra.space](https://docs.citra.space/citrascope/).
15
+
16
+ Documentation source is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository.
17
+
12
18
  ## Installation
13
19
 
14
- ### Install with pipx
20
+ **Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
15
21
 
16
- [pipx](https://pipx.pypa.io/) installs the CLI tool in an isolated environment while making it globally available:
22
+ ### Check Your Python Version
17
23
 
18
24
  ```sh
19
- pipx install citrascope
25
+ python3 --version
20
26
  ```
21
27
 
22
- ### Optional Dependencies
28
+ If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
23
29
 
24
- CitraScope supports different hardware adapters through optional dependency groups:
30
+ ```sh
31
+ pyenv install 3.12.0
32
+ pyenv local 3.12.0 # Sets Python 3.12.0 for the current directory
33
+ ```
25
34
 
26
- - **INDI adapter** (for Linux-based telescope control):
27
- ```sh
28
- pipx install citrascope[indi]
29
- # or with pip: pip install citrascope[indi]
30
- ```
35
+ ### Install CitraScope
31
36
 
32
- - **All optional dependencies**:
33
- ```sh
34
- pipx install citrascope[all]
35
- # or with pip: pip install citrascope[all]
36
- ```
37
+ **Recommended: Using pip in a virtual environment**
37
38
 
38
- The base installation without optional dependencies supports the NINA adapter, which works via HTTP API calls and does not require additional Python packages.
39
+ ```sh
40
+ python3 -m venv citrascope-env
41
+ source citrascope-env/bin/activate # On Windows: citrascope-env\Scripts\activate
42
+ pip install citrascope
43
+ ```
44
+
45
+ ### Optional Dependencies
46
+
47
+ For Linux-based telescope control (INDI):
48
+
49
+ ```sh
50
+ pip install citrascope[indi]
51
+ ```
39
52
 
40
53
  This provides the `citrascope` command-line tool. To see available commands:
41
54
 
@@ -45,16 +58,18 @@ citrascope --help
45
58
 
46
59
  ## Usage
47
60
 
48
- Run the CLI tool:
61
+ ### Starting the Daemon
62
+
63
+ Run the daemon with:
49
64
 
50
65
  ```sh
51
- citrascope start
66
+ citrascope
52
67
  ```
53
68
 
54
- To connect to the Citra Dev server:
69
+ By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
55
70
 
56
71
  ```sh
57
- citrascope start --dev
72
+ citrascope --web-port 8080
58
73
  ```
59
74
 
60
75
  ## Developer Setup
@@ -118,10 +133,10 @@ Then create a release in the GitHub UI from the new tag. This triggers automatic
118
133
 
119
134
  If you are using Visual Studio Code, you can run or debug the project directly using the pre-configured launch options in `.vscode/launch.json`:
120
135
 
121
- - **Python: citrascope dev start** — Runs the main entry point with development options.
122
- - **Python: citrascope dev start DEBUG logging** — Runs with development options and sets log level to DEBUG for more detailed output.
136
+ - **Python: citrascope** — Runs the daemon with default settings
137
+ - **Python: citrascope (custom port)** — Runs with web interface on port 8080
123
138
 
124
- To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button. This is a convenient way to start or debug the app without manually entering commands.
139
+ To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
125
140
 
126
141
  ## Running Tests
127
142
 
@@ -0,0 +1,23 @@
1
+ import click
2
+
3
+ from citrascope.citra_scope_daemon import CitraScopeDaemon
4
+ from citrascope.constants import DEFAULT_WEB_PORT
5
+ from citrascope.settings.citrascope_settings import CitraScopeSettings
6
+
7
+
8
+ @click.command()
9
+ @click.option(
10
+ "--web-port",
11
+ default=DEFAULT_WEB_PORT,
12
+ type=int,
13
+ help=f"Web server port (default: {DEFAULT_WEB_PORT})",
14
+ )
15
+ def cli(web_port):
16
+ """CitraScope daemon - configure via web UI at http://localhost:24872"""
17
+ settings = CitraScopeSettings(web_port=web_port)
18
+ daemon = CitraScopeDaemon(settings)
19
+ daemon.run()
20
+
21
+
22
+ if __name__ == "__main__":
23
+ cli()
@@ -41,7 +41,19 @@ class CitraApiClient(AbstractCitraApiClient):
41
41
  return resp.json()
42
42
  except httpx.HTTPStatusError as e:
43
43
  if self.logger:
44
- self.logger.error(f"HTTP error: {e.response.status_code} {e.response.text}")
44
+ # Check if response is HTML (e.g., Cloudflare error pages)
45
+ content_type = e.response.headers.get("content-type", "")
46
+ response_text = e.response.text
47
+
48
+ if "text/html" in content_type or response_text.strip().startswith("<"):
49
+ # Log only status and a brief message for HTML responses, sometimes we get Cloudflare error pages
50
+ self.logger.error(
51
+ f"HTTP error: {e.response.status_code} - "
52
+ f"Received HTML error page (likely Cloudflare or server error) for {method} {endpoint}"
53
+ )
54
+ else:
55
+ # Log full response for non-HTML errors (JSON, plain text, etc.)
56
+ self.logger.error(f"HTTP error: {e.response.status_code} {response_text}")
45
57
  return None
46
58
  except Exception as e:
47
59
  if self.logger:
@@ -17,9 +17,6 @@ class CitraScopeDaemon:
17
17
  settings: CitraScopeSettings,
18
18
  api_client: Optional[AbstractCitraApiClient] = None,
19
19
  hardware_adapter: Optional[AbstractAstroHardwareAdapter] = None,
20
- enable_web: bool = True,
21
- web_host: str = "0.0.0.0",
22
- web_port: int = 24872,
23
20
  ):
24
21
  self.settings = settings
25
22
  CITRASCOPE_LOGGER.setLevel(self.settings.log_level)
@@ -33,16 +30,15 @@ class CitraScopeDaemon:
33
30
 
34
31
  self.api_client = api_client
35
32
  self.hardware_adapter = hardware_adapter
36
- self.enable_web = enable_web
37
33
  self.web_server = None
38
34
  self.task_manager = None
39
35
  self.ground_station = None
40
36
  self.telescope_record = None
41
37
  self.configuration_error: Optional[str] = None
38
+ self._autofocus_in_progress = False
42
39
 
43
- # Create web server instance if enabled (always start web server)
44
- if self.enable_web:
45
- self.web_server = CitraScopeWebServer(daemon=self, host=web_host, port=web_port)
40
+ # Create web server instance (always enabled)
41
+ self.web_server = CitraScopeWebServer(daemon=self, host="0.0.0.0", port=self.settings.web_port)
46
42
 
47
43
  def _create_hardware_adapter(self) -> AbstractAstroHardwareAdapter:
48
44
  """Factory method to create the appropriate hardware adapter based on settings."""
@@ -73,12 +69,8 @@ class CitraScopeDaemon:
73
69
  try:
74
70
  if reload_settings:
75
71
  CITRASCOPE_LOGGER.info("Reloading configuration...")
76
- # Reload settings from file
77
- new_settings = CitraScopeSettings(
78
- dev=("dev.api" in self.settings.host),
79
- log_level=self.settings.log_level,
80
- keep_images=self.settings.keep_images,
81
- )
72
+ # Reload settings from file (preserving web_port)
73
+ new_settings = CitraScopeSettings(web_port=self.settings.web_port)
82
74
  self.settings = new_settings
83
75
  CITRASCOPE_LOGGER.setLevel(self.settings.log_level)
84
76
 
@@ -186,6 +178,9 @@ class CitraScopeDaemon:
186
178
  f"Hardware connected. Slew rate: {self.hardware_adapter.scope_slew_rate_degrees_per_second} deg/sec"
187
179
  )
188
180
 
181
+ # Save filter configuration if adapter supports it
182
+ self._save_filter_config()
183
+
189
184
  self.task_manager = TaskManager(
190
185
  self.api_client,
191
186
  citra_telescope_record,
@@ -205,12 +200,88 @@ class CitraScopeDaemon:
205
200
  CITRASCOPE_LOGGER.error(error_msg, exc_info=True)
206
201
  return False, error_msg
207
202
 
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
+ def _save_filter_config(self):
212
+ """Save filter configuration from adapter to settings if supported.
213
+
214
+ This method is called:
215
+ - After hardware initialization to save discovered filters
216
+ - After autofocus to save updated focus positions
217
+ - After manual filter focus updates via web API
218
+
219
+ Thread safety: This modifies self.settings and writes to disk.
220
+ Should be called from main daemon thread or properly synchronized.
221
+ """
222
+ if not self.hardware_adapter or not self.hardware_adapter.supports_filter_management():
223
+ return
224
+
225
+ try:
226
+ filter_config = self.hardware_adapter.get_filter_config()
227
+ if filter_config:
228
+ self.settings.adapter_settings["filters"] = filter_config
229
+ self.settings.save()
230
+ CITRASCOPE_LOGGER.info(f"Saved filter configuration with {len(filter_config)} filters")
231
+ except Exception as e:
232
+ CITRASCOPE_LOGGER.warning(f"Failed to save filter configuration: {e}")
233
+
234
+ 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
241
+
242
+ Returns:
243
+ Tuple of (success, error_message)
244
+ """
245
+ if not self.hardware_adapter:
246
+ return False, "No hardware adapter initialized"
247
+
248
+ if not self.hardware_adapter.supports_filter_management():
249
+ return False, "Hardware adapter does not support filter management"
250
+
251
+ # Prevent concurrent autofocus operations
252
+ if self._autofocus_in_progress:
253
+ return False, "Autofocus already in progress"
254
+
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"
259
+
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"
262
+
263
+ self._autofocus_in_progress = True
264
+ try:
265
+ CITRASCOPE_LOGGER.info("Starting autofocus routine...")
266
+ self.hardware_adapter.do_autofocus()
267
+
268
+ # Save updated filter configuration after autofocus
269
+ self._save_filter_config()
270
+
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
279
+
208
280
  def run(self):
209
- # Start web server FIRST if enabled, so users can monitor/configure
281
+ # Start web server FIRST, so users can monitor/configure
210
282
  # The web interface will remain available even if configuration is incomplete
211
- if self.enable_web:
212
- self.web_server.start()
213
- CITRASCOPE_LOGGER.info(f"Web interface available at http://{self.web_server.host}:{self.web_server.port}")
283
+ self.web_server.start()
284
+ CITRASCOPE_LOGGER.info(f"Web interface available at http://{self.web_server.host}:{self.web_server.port}")
214
285
 
215
286
  try:
216
287
  # Try to initialize components
@@ -238,6 +309,7 @@ class CitraScopeDaemon:
238
309
  """Clean up resources on shutdown."""
239
310
  if self.task_manager:
240
311
  self.task_manager.stop()
241
- if self.enable_web and self.web_server:
312
+ if self.web_server:
242
313
  CITRASCOPE_LOGGER.info("Stopping web server...")
243
- self.web_server.stop()
314
+ if self.web_server.web_log_handler:
315
+ CITRASCOPE_LOGGER.removeHandler(self.web_server.web_log_handler)
@@ -0,0 +1,23 @@
1
+ """Application-wide constants for CitraScope.
2
+
3
+ This module contains all shared constants used across different parts of the application.
4
+ Centralizing these values prevents duplication and circular import issues.
5
+ """
6
+
7
+ # ============================================================================
8
+ # API ENDPOINTS
9
+ # ============================================================================
10
+ PROD_API_HOST = "api.citra.space"
11
+ DEV_API_HOST = "dev.api.citra.space"
12
+ DEFAULT_API_PORT = 443
13
+
14
+ # ============================================================================
15
+ # WEB APP URLs
16
+ # ============================================================================
17
+ PROD_APP_URL = "https://app.citra.space"
18
+ DEV_APP_URL = "https://dev.app.citra.space"
19
+
20
+ # ============================================================================
21
+ # WEB SERVER
22
+ # ============================================================================
23
+ DEFAULT_WEB_PORT = 24872 # "CITRA" on phone keypad
@@ -20,6 +20,18 @@ class SettingSchemaEntry(TypedDict, total=False):
20
20
  options: list[str] # List of valid options for select/dropdown inputs
21
21
 
22
22
 
23
+ class FilterConfig(TypedDict):
24
+ """Type definition for filter configuration.
25
+
26
+ Attributes:
27
+ name: Human-readable filter name (e.g., 'Luminance', 'Red', 'Ha')
28
+ focus_position: Focuser position for this filter in steps
29
+ """
30
+
31
+ name: str
32
+ focus_position: int
33
+
34
+
23
35
  class ObservationStrategy(Enum):
24
36
  MANUAL = 1
25
37
  SEQUENCE_TO_CONTROLLER = 2
@@ -176,3 +188,52 @@ class AbstractAstroHardwareAdapter(ABC):
176
188
  bool: True if alignment was successful, False otherwise.
177
189
  """
178
190
  pass
191
+
192
+ def do_autofocus(self) -> None:
193
+ """Perform autofocus routine for all filters.
194
+
195
+ This is an optional method for adapters that support filter management.
196
+ Default implementation raises NotImplementedError. Override in subclasses
197
+ that support autofocus.
198
+
199
+ Raises:
200
+ NotImplementedError: If the adapter doesn't support autofocus
201
+ """
202
+ raise NotImplementedError(f"{self.__class__.__name__} does not support autofocus")
203
+
204
+ def supports_filter_management(self) -> bool:
205
+ """Indicates whether this adapter supports filter/focus management.
206
+
207
+ Returns:
208
+ bool: True if the adapter manages filters and focus positions, False otherwise.
209
+ """
210
+ return False
211
+
212
+ def get_filter_config(self) -> dict[str, FilterConfig]:
213
+ """Get the current filter configuration including focus positions.
214
+
215
+ Returns:
216
+ dict: Dictionary mapping filter IDs (as strings) to FilterConfig.
217
+ Each FilterConfig contains:
218
+ - name (str): Filter name
219
+ - focus_position (int): Focuser position for this filter
220
+
221
+ Example:
222
+ {
223
+ "1": {"name": "Luminance", "focus_position": 9000},
224
+ "2": {"name": "Red", "focus_position": 9050}
225
+ }
226
+ """
227
+ return {}
228
+
229
+ def update_filter_focus(self, filter_id: str, focus_position: int) -> bool:
230
+ """Update the focus position for a specific filter.
231
+
232
+ Args:
233
+ filter_id: Filter ID as string
234
+ focus_position: New focus position in steps
235
+
236
+ Returns:
237
+ bool: True if update was successful, False otherwise
238
+ """
239
+ return False