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.
- {citrascope-0.3.0 → citrascope-0.5.0}/.github/copilot-instructions.md +2 -3
- {citrascope-0.3.0 → citrascope-0.5.0}/.gitignore +0 -3
- {citrascope-0.3.0 → citrascope-0.5.0}/.vscode/launch.json +4 -13
- {citrascope-0.3.0 → citrascope-0.5.0}/PKG-INFO +40 -32
- {citrascope-0.3.0 → citrascope-0.5.0}/README.md +38 -23
- citrascope-0.5.0/citrascope/__main__.py +23 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/api/citra_api_client.py +13 -1
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/citra_scope_daemon.py +91 -19
- citrascope-0.5.0/citrascope/constants.py +23 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/abstract_astro_hardware_adapter.py +61 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/nina_adv_http_adapter.py +106 -77
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/web_log_handler.py +9 -8
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/citrascope_settings.py +34 -45
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/runner.py +36 -2
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/app.py +137 -13
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/server.py +10 -1
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/app.js +246 -17
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/config.js +248 -9
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/style.css +32 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/templates/dashboard.html +143 -7
- {citrascope-0.3.0 → citrascope-0.5.0}/pyproject.toml +3 -10
- citrascope-0.3.0/citrascope/__main__.py +0 -26
- citrascope-0.3.0/docs/index.md +0 -47
- {citrascope-0.3.0 → citrascope-0.5.0}/.devcontainer/devcontainer.json +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.flake8 +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.github/dependabot.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.github/workflows/pypi-publish.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.github/workflows/pytest.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.pre-commit-config.yaml +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/.python-version +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/api/abstract_api_client.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/adapter_registry.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/indi_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/kstars_dbus_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/nina_adv_http_survey_template.json +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/logging/_citrascope_logger.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/settings/settings_file_manager.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/base_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/static_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/scope/tracking_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/tasks/task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/api.js +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/img/citra.png +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/img/favicon.png +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/citrascope/web/static/websocket.js +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_api_client.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_hardware_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.5.0}/tests/unit/test_task_manager.py +0 -0
- {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
|
|
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
|
-
-
|
|
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.
|
|
@@ -4,29 +4,20 @@
|
|
|
4
4
|
"version": "0.2.0",
|
|
5
5
|
"configurations": [
|
|
6
6
|
{
|
|
7
|
-
"name": "Python: citrascope
|
|
7
|
+
"name": "Python: citrascope",
|
|
8
8
|
"type": "debugpy",
|
|
9
9
|
"request": "launch",
|
|
10
10
|
"module": "citrascope",
|
|
11
|
-
"args": [
|
|
11
|
+
"args": [],
|
|
12
12
|
"console": "integratedTerminal",
|
|
13
13
|
"justMyCode": false
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
"name": "Python: citrascope
|
|
16
|
+
"name": "Python: citrascope (custom port)",
|
|
17
17
|
"type": "debugpy",
|
|
18
18
|
"request": "launch",
|
|
19
19
|
"module": "citrascope",
|
|
20
|
-
"args": ["--
|
|
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
|
+
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
|
-
|
|
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
|
-
|
|
69
|
+
**Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
### Check Your Python Version
|
|
73
72
|
|
|
74
73
|
```sh
|
|
75
|
-
|
|
74
|
+
python3 --version
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
|
|
79
78
|
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
```sh
|
|
84
|
-
pipx install citrascope[indi]
|
|
85
|
-
# or with pip: pip install citrascope[indi]
|
|
86
|
-
```
|
|
84
|
+
### Install CitraScope
|
|
87
85
|
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
### Starting the Daemon
|
|
111
|
+
|
|
112
|
+
Run the daemon with:
|
|
105
113
|
|
|
106
114
|
```sh
|
|
107
|
-
citrascope
|
|
115
|
+
citrascope
|
|
108
116
|
```
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
|
|
111
119
|
|
|
112
120
|
```sh
|
|
113
|
-
citrascope
|
|
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
|
|
178
|
-
- **Python: citrascope
|
|
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.
|
|
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
|
-
|
|
20
|
+
**Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
### Check Your Python Version
|
|
17
23
|
|
|
18
24
|
```sh
|
|
19
|
-
|
|
25
|
+
python3 --version
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
|
|
23
29
|
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
```sh
|
|
28
|
-
pipx install citrascope[indi]
|
|
29
|
-
# or with pip: pip install citrascope[indi]
|
|
30
|
-
```
|
|
35
|
+
### Install CitraScope
|
|
31
36
|
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
### Starting the Daemon
|
|
62
|
+
|
|
63
|
+
Run the daemon with:
|
|
49
64
|
|
|
50
65
|
```sh
|
|
51
|
-
citrascope
|
|
66
|
+
citrascope
|
|
52
67
|
```
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
|
|
55
70
|
|
|
56
71
|
```sh
|
|
57
|
-
citrascope
|
|
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
|
|
122
|
-
- **Python: citrascope
|
|
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.
|
|
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
|
-
|
|
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
|
|
44
|
-
|
|
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
|
|
281
|
+
# Start web server FIRST, so users can monitor/configure
|
|
210
282
|
# The web interface will remain available even if configuration is incomplete
|
|
211
|
-
|
|
212
|
-
|
|
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.
|
|
312
|
+
if self.web_server:
|
|
242
313
|
CITRASCOPE_LOGGER.info("Stopping web server...")
|
|
243
|
-
self.web_server.
|
|
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
|
{citrascope-0.3.0 → citrascope-0.5.0}/citrascope/hardware/abstract_astro_hardware_adapter.py
RENAMED
|
@@ -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
|