arcsecond 3.20.1__tar.gz → 4.0.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 (131) hide show
  1. arcsecond-4.0.0/.gitattributes +6 -0
  2. {arcsecond-3.20.1 → arcsecond-4.0.0}/.github/workflows/tests.yml +22 -0
  3. {arcsecond-3.20.1 → arcsecond-4.0.0}/PKG-INFO +34 -5
  4. {arcsecond-3.20.1 → arcsecond-4.0.0}/README.md +33 -0
  5. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/alpaca/commands.py +55 -26
  6. arcsecond-4.0.0/arcsecond/alpaca/dome_probe.py +90 -0
  7. arcsecond-3.20.1/arcsecond/alpaca/dome_probe.py → arcsecond-4.0.0/arcsecond/alpaca/probe.py +114 -120
  8. arcsecond-4.0.0/arcsecond/alpaca/telescope_probe.py +182 -0
  9. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/config.py +125 -5
  10. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cli.py +31 -15
  11. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/__init__.py +3 -5
  12. arcsecond-4.0.0/arcsecond/cloud/auth.py +176 -0
  13. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploads.py +13 -2
  14. arcsecond-4.0.0/arcsecond/docgen.py +295 -0
  15. arcsecond-4.0.0/arcsecond/errors.py +18 -0
  16. arcsecond-4.0.0/arcsecond/hosting/__init__.py +17 -0
  17. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/hosting/database.py +9 -31
  18. arcsecond-4.0.0/arcsecond/hosting/docker/__init__.py +1 -0
  19. arcsecond-4.0.0/arcsecond/hosting/lifecycle.py +328 -0
  20. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/hosting/local.py +111 -17
  21. arcsecond-4.0.0/arcsecond/hosting/stack.py +401 -0
  22. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/hosting/utils.py +40 -0
  23. arcsecond-4.0.0/arcsecond/imagesources/autostart.py +270 -0
  24. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/commands.py +57 -21
  25. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/options.py +6 -16
  26. {arcsecond-3.20.1 → arcsecond-4.0.0}/pyproject.toml +1 -5
  27. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/api/test_api_endpoint.py +21 -6
  28. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/api/test_config.py +7 -1
  29. arcsecond-4.0.0/tests/api/test_config_pointer.py +119 -0
  30. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_alpaca_probe.py +348 -15
  31. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_cli.py +2 -1
  32. arcsecond-4.0.0/tests/test_cli_api_commands.py +147 -0
  33. arcsecond-4.0.0/tests/test_docgen.py +128 -0
  34. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_hosting_database.py +1 -1
  35. arcsecond-4.0.0/tests/test_hosting_lifecycle.py +327 -0
  36. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_hosting_local.py +84 -0
  37. arcsecond-4.0.0/tests/test_hosting_stack.py +289 -0
  38. arcsecond-4.0.0/tests/test_imagesources_autostart.py +112 -0
  39. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_commands.py +93 -8
  40. {arcsecond-3.20.1 → arcsecond-4.0.0}/uv.lock +1 -84
  41. arcsecond-3.20.1/arcsecond/cloud/auth.py +0 -120
  42. arcsecond-3.20.1/arcsecond/errors.py +0 -7
  43. arcsecond-3.20.1/arcsecond/hosting/__init__.py +0 -7
  44. arcsecond-3.20.1/arcsecond/hosting/checks.py +0 -94
  45. arcsecond-3.20.1/arcsecond/hosting/constants.py +0 -8
  46. arcsecond-3.20.1/arcsecond/hosting/docker/__init__.py +0 -33
  47. arcsecond-3.20.1/arcsecond/hosting/docker/constants.py +0 -12
  48. arcsecond-3.20.1/arcsecond/hosting/docker/containers.py +0 -216
  49. arcsecond-3.20.1/arcsecond/hosting/docker/images.py +0 -43
  50. arcsecond-3.20.1/arcsecond/hosting/docker/utils.py +0 -69
  51. arcsecond-3.20.1/arcsecond/hosting/keygen/__init__.py +0 -3
  52. arcsecond-3.20.1/arcsecond/hosting/keygen/client.py +0 -233
  53. arcsecond-3.20.1/arcsecond/hosting/keygen/utils.py +0 -7
  54. arcsecond-3.20.1/arcsecond/hosting/main.py +0 -74
  55. arcsecond-3.20.1/arcsecond/hosting/postgres/init-db.sh +0 -8
  56. arcsecond-3.20.1/arcsecond/hosting/setup.py +0 -75
  57. arcsecond-3.20.1/arcsecond/hosting/validation.py +0 -47
  58. {arcsecond-3.20.1 → arcsecond-4.0.0}/.docker/Dockerfile_postgres +0 -0
  59. {arcsecond-3.20.1 → arcsecond-4.0.0}/.docker/Dockerfile_redis +0 -0
  60. {arcsecond-3.20.1 → arcsecond-4.0.0}/.flake8 +0 -0
  61. {arcsecond-3.20.1 → arcsecond-4.0.0}/.github/dependabot.yml +0 -0
  62. {arcsecond-3.20.1 → arcsecond-4.0.0}/.github/workflows/pythonpublish.yml +0 -0
  63. {arcsecond-3.20.1 → arcsecond-4.0.0}/.gitignore +0 -0
  64. {arcsecond-3.20.1 → arcsecond-4.0.0}/LICENSE +0 -0
  65. {arcsecond-3.20.1 → arcsecond-4.0.0}/Makefile +0 -0
  66. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/__init__.py +0 -0
  67. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/__version__.py +0 -0
  68. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/alpaca/__init__.py +0 -0
  69. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/__init__.py +0 -0
  70. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/constants.py +0 -0
  71. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/endpoint.py +0 -0
  72. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/main.py +0 -0
  73. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/api/resources.py +0 -0
  74. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/resources.py +0 -0
  75. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/__init__.py +0 -0
  76. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/constants.py +0 -0
  77. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/context.py +0 -0
  78. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  79. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  80. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  81. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  82. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  83. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/errors.py +0 -0
  84. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/logger.py +0 -0
  85. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/uploader.py +0 -0
  86. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/utils.py +0 -0
  87. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/cloud/uploader/walker.py +0 -0
  88. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/hosting/backups.py +0 -0
  89. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/hosting/docker/docker-compose.yml +0 -0
  90. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/__init__.py +0 -0
  91. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/detection.py +0 -0
  92. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/proxy.py +0 -0
  93. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/registry.py +0 -0
  94. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/runtime.py +0 -0
  95. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/__init__.py +0 -0
  96. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/base.py +0 -0
  97. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/filewatch.py +0 -0
  98. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/mdns.py +0 -0
  99. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/network.py +0 -0
  100. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/sources/opencv.py +0 -0
  101. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/imagesources/store.py +0 -0
  102. {arcsecond-3.20.1 → arcsecond-4.0.0}/arcsecond/targets.py +0 -0
  103. {arcsecond-3.20.1 → arcsecond-4.0.0}/examples/example_upload_files.py +0 -0
  104. {arcsecond-3.20.1 → arcsecond-4.0.0}/requirements.txt +0 -0
  105. {arcsecond-3.20.1 → arcsecond-4.0.0}/setup.cfg +0 -0
  106. {arcsecond-3.20.1 → arcsecond-4.0.0}/setup.py +0 -0
  107. {arcsecond-3.20.1 → arcsecond-4.0.0}/sonar-project.properties +0 -0
  108. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/__init__.py +0 -0
  109. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/api/__init__.py +0 -0
  110. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/api/test_api.py +0 -0
  111. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/api/test_targets.py +0 -0
  112. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/__init__.py +0 -0
  113. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/__init__.py +0 -0
  114. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/__init__.py +0 -0
  115. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  116. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  117. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  118. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  119. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  120. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/conftest.py +0 -0
  121. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/fixtures/file1.fits +0 -0
  122. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_hosting_backups_restore.py +0 -0
  123. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_hosting_utils_secret_key.py +0 -0
  124. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_detection.py +0 -0
  125. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_mdns.py +0 -0
  126. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_network.py +0 -0
  127. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_proxy.py +0 -0
  128. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_store.py +0 -0
  129. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_imagesources_webcam.py +0 -0
  130. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/test_targets_planning.py +0 -0
  131. {arcsecond-3.20.1 → arcsecond-4.0.0}/tests/utils.py +0 -0
@@ -0,0 +1,6 @@
1
+ # The packaged compose template is compared byte for byte with what an
2
+ # installation holds; a CRLF checkout on Windows would make every file look
3
+ # customised. Markdown and YAML stay LF everywhere.
4
+ *.yml text eol=lf
5
+ *.yaml text eol=lf
6
+ *.md text eol=lf
@@ -40,6 +40,28 @@ jobs:
40
40
  name: coverage
41
41
  path: coverage.xml
42
42
 
43
+ # The CLI's operators run it on Windows — the observatory PC — and until
44
+ # now nothing here ever executed it there. No Docker on this runner; this
45
+ # covers the pure-Python paths: path handling, the console, the registry
46
+ # backend of the proxy's login item, and every Windows branch in the suite.
47
+ tests-windows:
48
+ runs-on: windows-latest
49
+
50
+ steps:
51
+ - uses: actions/checkout@v5
52
+
53
+ - name: Install uv
54
+ uses: astral-sh/setup-uv@v6
55
+ with:
56
+ python-version: '3.12'
57
+ enable-cache: true
58
+
59
+ - name: Install dependencies
60
+ run: uv sync --extra webcam --group dev
61
+
62
+ - name: Test
63
+ run: uv run pytest -q tests/
64
+
43
65
  scan:
44
66
  runs-on: ubuntu-latest
45
67
  needs: tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arcsecond
3
- Version: 3.20.1
3
+ Version: 4.0.0
4
4
  Summary: CLI for arcsecond.io
5
5
  Project-URL: Homepage, https://github.com/arcsecond-io/cli
6
6
  Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
@@ -40,12 +40,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
40
  Requires-Python: >=3.12
41
41
  Requires-Dist: alpyca==3.1.2
42
42
  Requires-Dist: click>=8
43
- Requires-Dist: configparser
44
- Requires-Dist: docker
45
43
  Requires-Dist: httpx
46
- Requires-Dist: py-machineid
47
44
  Requires-Dist: tqdm<5.0.0,>=4.67.1
48
- Requires-Dist: wait-for-it
49
45
  Provides-Extra: webcam
50
46
  Requires-Dist: aiohttp>=3.9; extra == 'webcam'
51
47
  Requires-Dist: opencv-python-headless<5,>=4.10; extra == 'webcam'
@@ -62,6 +58,39 @@ public and private ones.
62
58
 
63
59
  [Read the docs](https://docs.arcsecond.io/cli)
64
60
 
61
+ ## Running Arcsecond.local
62
+
63
+ The CLI owns the whole lifecycle of a self-hosted installation; there is no
64
+ `docker` command to type:
65
+
66
+ ```bash
67
+ $ arcsecond setup --lan-host 192.168.1.42 # writes .env and docker-compose.yml
68
+ $ arcsecond start # first run downloads the images
69
+ $ arcsecond status
70
+ $ arcsecond logs backend -f
71
+ $ arcsecond restart backend worker # after editing .env
72
+ $ arcsecond update # newer images, refreshed compose file
73
+ $ arcsecond stop
74
+ ```
75
+
76
+ Every one of these finds the installation in the current folder, or in the
77
+ folder `arcsecond setup` last ran in, or where `--dir` points.
78
+
79
+ ## Which server the CLI talks to
80
+
81
+ The CLI points at one API server at a time — the cloud by default, or a
82
+ server you registered, typically your own Arcsecond.local — and every command
83
+ follows that pointer:
84
+
85
+ ```bash
86
+ $ arcsecond api # list them, * marks the current one
87
+ $ arcsecond api use local # `setup` registered `local` for you
88
+ $ arcsecond login # on that server
89
+ ```
90
+
91
+ Credentials are kept per server. For a script or a cron job, `ARCSECOND_API=<name>`
92
+ selects a server for that process alone, without moving the pointer.
93
+
65
94
  ## Configuration
66
95
 
67
96
  Credentials and settings live in `~/.config/arcsecond/`, alongside the list of
@@ -9,6 +9,39 @@ public and private ones.
9
9
 
10
10
  [Read the docs](https://docs.arcsecond.io/cli)
11
11
 
12
+ ## Running Arcsecond.local
13
+
14
+ The CLI owns the whole lifecycle of a self-hosted installation; there is no
15
+ `docker` command to type:
16
+
17
+ ```bash
18
+ $ arcsecond setup --lan-host 192.168.1.42 # writes .env and docker-compose.yml
19
+ $ arcsecond start # first run downloads the images
20
+ $ arcsecond status
21
+ $ arcsecond logs backend -f
22
+ $ arcsecond restart backend worker # after editing .env
23
+ $ arcsecond update # newer images, refreshed compose file
24
+ $ arcsecond stop
25
+ ```
26
+
27
+ Every one of these finds the installation in the current folder, or in the
28
+ folder `arcsecond setup` last ran in, or where `--dir` points.
29
+
30
+ ## Which server the CLI talks to
31
+
32
+ The CLI points at one API server at a time — the cloud by default, or a
33
+ server you registered, typically your own Arcsecond.local — and every command
34
+ follows that pointer:
35
+
36
+ ```bash
37
+ $ arcsecond api # list them, * marks the current one
38
+ $ arcsecond api use local # `setup` registered `local` for you
39
+ $ arcsecond login # on that server
40
+ ```
41
+
42
+ Credentials are kept per server. For a script or a cron job, `ARCSECOND_API=<name>`
43
+ selects a server for that process alone, without moving the pointer.
44
+
12
45
  ## Configuration
13
46
 
14
47
  Credentials and settings live in `~/.config/arcsecond/`, alongside the list of
@@ -1,14 +1,14 @@
1
1
  """
2
2
  Click command group: ``arcsecond alpaca``.
3
3
 
4
- First citizen: ``arcsecond alpaca probe dome`` — a read-only diagnostic that
5
- captures what surface a given Alpaca dome server exposes (device metadata,
6
- ``SupportedActions``, ``CommandString`` / ``CommandBool`` / ``CommandBlind``
7
- passthrough behaviour, and local host hints when the target is this machine).
8
-
9
- Structure leaves room for ``arcsecond alpaca probe telescope`` /
10
- ``probe camera`` / ``probe focuser`` and ``arcsecond alpaca discover`` later
11
- under the same group.
4
+ ``arcsecond alpaca probe dome`` and ``arcsecond alpaca probe telescope`` are
5
+ read-only diagnostics that capture what surface a given Alpaca device server
6
+ exposes (device metadata, ``SupportedActions``, ``CommandString`` /
7
+ ``CommandBool`` / ``CommandBlind`` passthrough behaviour, and local host hints
8
+ when the target is this machine), one JSON report per run.
9
+
10
+ Structure leaves room for ``probe camera`` / ``probe focuser`` and
11
+ ``arcsecond alpaca discover`` later under the same group.
12
12
  """
13
13
 
14
14
  from __future__ import annotations
@@ -17,11 +17,14 @@ import json
17
17
  import os
18
18
  from datetime import datetime, timezone
19
19
  from pathlib import Path
20
+ from typing import Callable
20
21
 
21
22
  import click
22
23
 
23
24
  from ..errors import ArcsecondError
24
- from .dome_probe import HOST_HINTS_REASON_LOCAL, ProbeProgress, probe_dome
25
+ from .dome_probe import probe_dome
26
+ from .probe import HOST_HINTS_REASON_LOCAL, ProbeProgress, ProbeResult
27
+ from .telescope_probe import probe_telescope
25
28
 
26
29
 
27
30
  @click.group(name="alpaca", help="Diagnostics for local ASCOM Alpaca devices.")
@@ -122,20 +125,10 @@ def _format_progress(label: str, ok: bool, detail: str | None) -> str:
122
125
  return f" {tag} {label}{suffix}"
123
126
 
124
127
 
125
- @probe_group.command(
126
- name="dome",
127
- help=(
128
- "Probe a local Alpaca dome device read-only and write a JSON report.\n\n"
129
- "Captures device metadata, SupportedActions, and the behaviour of the "
130
- "legacy CommandString / CommandBool / CommandBlind passthroughs. "
131
- "Useful for figuring out whether a proprietary driver (e.g. TCSGalil) "
132
- "exposes any vendor-specific extension surface beyond the standard "
133
- "ASCOM IDome interface. Local host hints (open ports, COM ProgIDs) "
134
- "are added when HOST is this machine."
135
- ),
136
- )
137
- @_add_options(_COMMON_OPTIONS)
138
- def probe_dome_cmd(
128
+ def _run_probe(
129
+ kind: str,
130
+ runner: Callable[..., ProbeResult],
131
+ *,
139
132
  host: str,
140
133
  port: int,
141
134
  device_number: int,
@@ -144,10 +137,11 @@ def probe_dome_cmd(
144
137
  collect_host_info: bool | None,
145
138
  output_path: str | None,
146
139
  ) -> None:
147
- output_path = output_path or _default_output_path("dome")
140
+ """Drive one probe from the terminal: banner, progress lines, report, summary."""
141
+ output_path = output_path or _default_output_path(kind)
148
142
 
149
143
  click.echo(
150
- click.style("Alpaca dome probe", bold=True)
144
+ click.style(f"Alpaca {kind} probe", bold=True)
151
145
  + f" → {protocol}://{host}:{port} (device {device_number})"
152
146
  )
153
147
  if allow_active:
@@ -163,7 +157,7 @@ def probe_dome_cmd(
163
157
  )
164
158
 
165
159
  try:
166
- result = probe_dome(
160
+ result = runner(
167
161
  host=host,
168
162
  port=port,
169
163
  device_number=device_number,
@@ -192,3 +186,38 @@ def probe_dome_cmd(
192
186
  click.echo(f" SupportedActions: {supported_n} entries")
193
187
  click.echo(f" Host hints: {_describe_host_hints(result.report)}")
194
188
  click.echo(f" Report written to {click.style(os.fspath(output_path), fg='cyan')}")
189
+
190
+
191
+ @probe_group.command(
192
+ name="dome",
193
+ help=(
194
+ "Probe a local Alpaca dome device read-only and write a JSON report.\n\n"
195
+ "Captures device metadata, SupportedActions, and the behaviour of the "
196
+ "legacy CommandString / CommandBool / CommandBlind passthroughs. "
197
+ "Useful for figuring out whether a proprietary driver (e.g. TCSGalil) "
198
+ "exposes any vendor-specific extension surface beyond the standard "
199
+ "ASCOM IDome interface. Local host hints (open ports, COM ProgIDs) "
200
+ "are added when HOST is this machine."
201
+ ),
202
+ )
203
+ @_add_options(_COMMON_OPTIONS)
204
+ def probe_dome_cmd(**options) -> None:
205
+ _run_probe("dome", probe_dome, **options)
206
+
207
+
208
+ @probe_group.command(
209
+ name="telescope",
210
+ help=(
211
+ "Probe a local Alpaca telescope (mount) device read-only and write a "
212
+ "JSON report.\n\n"
213
+ "Captures device metadata, optics, site, pointing and tracking state, "
214
+ "capabilities, CanMoveAxis and AxisRates per movable axis, "
215
+ "SupportedActions, and the behaviour of the legacy CommandString / "
216
+ "CommandBool / CommandBlind passthroughs. Never moves the mount. "
217
+ "Local host hints (open ports, COM ProgIDs) are added when HOST is "
218
+ "this machine."
219
+ ),
220
+ )
221
+ @_add_options(_COMMON_OPTIONS)
222
+ def probe_telescope_cmd(**options) -> None:
223
+ _run_probe("telescope", probe_telescope, **options)
@@ -0,0 +1,90 @@
1
+ """
2
+ Read-only diagnostic for an ASCOM Alpaca *dome* device.
3
+
4
+ The standard ASCOM Alpaca ``IDome`` interface only exposes a single
5
+ ``OpenShutter`` / ``CloseShutter`` pair. Multi-shutter domes (e.g. DFM domes
6
+ driven by TCSGalil) coordinate their shutters internally; if the driver does
7
+ not advertise per-shutter custom actions via ``SupportedActions``, there is
8
+ no canonical client-side way to drive the upper and lower shutters
9
+ independently. This probe writes down exactly what surface a dome server
10
+ exposes, so that decision can be made on evidence.
11
+
12
+ The engine, the report layout and the safety rules live in :mod:`.probe`;
13
+ this module only says which dome properties to read.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Callable, Iterable
19
+
20
+ from .probe import ProbeProgress, ProbeResult
21
+ from .probe import iter_probe_labels as _iter_probe_labels
22
+ from .probe import probe_device
23
+
24
+ # Read-only device descriptors and dome state, in the order we want them
25
+ # presented in the report. Names are PascalCase to match the alpyca surface.
26
+ _READ_ONLY_PROPERTIES: tuple[str, ...] = (
27
+ "Name",
28
+ "Description",
29
+ "DriverInfo",
30
+ "DriverVersion",
31
+ "InterfaceVersion",
32
+ "ShutterStatus",
33
+ "Altitude",
34
+ "Azimuth",
35
+ "AtHome",
36
+ "AtPark",
37
+ "Slewing",
38
+ "Slaved",
39
+ "CanFindHome",
40
+ "CanPark",
41
+ "CanSetAltitude",
42
+ "CanSetPark",
43
+ "CanSetShutter",
44
+ "CanSlave",
45
+ "CanSyncAzimuth",
46
+ )
47
+
48
+
49
+ def probe_dome(
50
+ host: str,
51
+ port: int,
52
+ device_number: int = 0,
53
+ *,
54
+ protocol: str = "http",
55
+ allow_active: bool = False,
56
+ collect_host_info: bool | None = None,
57
+ dome_factory: Callable[[str, int, str], Any] | None = None,
58
+ progress: ProbeProgress | None = None,
59
+ ) -> ProbeResult:
60
+ """
61
+ Build a JSON-serialisable diagnostic report for an Alpaca dome at
62
+ ``protocol://host:port`` (device_number). See :func:`.probe.probe_device`
63
+ for the ``collect_host_info`` semantics.
64
+
65
+ ``dome_factory(address, device_number, protocol)`` is only there for
66
+ tests — production callers leave it ``None`` and the real
67
+ ``alpaca.dome.Dome`` is used.
68
+ """
69
+ if dome_factory is None:
70
+ from alpaca.dome import Dome # local import: keep cost off cold paths
71
+
72
+ dome_factory = Dome
73
+
74
+ return probe_device(
75
+ "dome",
76
+ host,
77
+ port,
78
+ device_number,
79
+ properties=_READ_ONLY_PROPERTIES,
80
+ device_factory=dome_factory,
81
+ protocol=protocol,
82
+ allow_active=allow_active,
83
+ collect_host_info=collect_host_info,
84
+ progress=progress,
85
+ )
86
+
87
+
88
+ def iter_probe_labels() -> Iterable[str]:
89
+ """Convenience for tests / docs: enumerate the labels emitted by a run."""
90
+ return _iter_probe_labels(_READ_ONLY_PROPERTIES)