Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/ax/positioning/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,17 @@ _Avoid_: orientation, mount direction.
### Alignment

**Camera-to-telescope alignment**:
The process of learning which camera pixel coincides with the centre of the eyepiece view. Outcome: an updated **target pixel**. From then on, every solve reports RA/Dec at that pixel as `pointing.aligned`.
The process of learning which camera pixel coincides with the centre of the eyepiece view. Outcome: an updated **target pixel**. From then on, every solve reports RA/Dec at that pixel as `pointing.aligned`. There are **two paths** to that outcome — *solve-based alignment* and *daytime (manual) alignment* — which differ only in how the target pixel is discovered; both write the same `Config["target_pixel"]` / `shared_state.set_target_pixel()`.
_Avoid_: telescope alignment, eyepiece alignment, scope alignment.

**Solve-based alignment**:
The default alignment path (`ui/align.py` `UIAlign` + `align_on_radec`): the user picks a star on the starfield chart, the solver plate-solves the live frame against that star's RA/Dec, and tetra3 reports the pixel where it landed (`AlignedResult` → target pixel). Requires a successful plate-solve, so it is a night-sky path.
_Avoid_: star alignment, chart alignment (describe it as solve-based).

**Daytime alignment** (manual alignment):
The manual alignment path (`ui/align_daytime.py` `UIAlignDaytime`): with the scope pointed at a distant **daytime** object centred in the eyepiece, the user looks at the live camera image and marks, by eye, the pixel showing that same object. That pixel **is** the target pixel — no plate-solve, no `align_command_queue` round-trip; the module writes `target_pixel` directly. The use case is daylight (no stars to solve); the mechanic is a manual pixel pick.
_Avoid_: daytime mode (it is an alignment, not a camera mode), visual alignment, manual solve (there is no solve).

**Alignment target** (`align_ra` / `align_dec`):
Local state in the solver process holding the user's chosen sky coordinate while an alignment request is pending. Cleared once the result is posted.
_Avoid_: aim target.
Expand Down
14 changes: 14 additions & 0 deletions docs/ax/ui/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ _Avoid_: current screen, focused module, top module.
The flow where `MenuManager.key_*` forwards a keypad event to `stack[-1].key_*`, after first checking for help mode and marking-menu mode.
_Avoid_: event routing, input handling.

**Keypad layout**:
The physical pad is **TKL / calculator style — `7 8 9` is the TOP row**, not phone style. The full grid (from `keyboard_pi.py`'s `keymap`) is:

```
7 8 9 (na)
4 5 6 PLUS
1 2 3 MINUS
0 SQUARE
LEFT UP DOWN RIGHT
```

So when a module maps number keys to on-screen **2×2 screen quadrants**, the spatially-faithful corners are `7`=top-left, `9`=top-right, `1`=bottom-left, `3`=bottom-right (used by daytime alignment's quadrant picker). `SQUARE`+key sends the `ALT_*` variant; a long press sends the `LNG_*` variant (long-`SQUARE` opens the marking menu).
_Avoid_: assuming phone-style `1 2 3` on top — it is inverted.

**Display mode**:
A per-module variant cycled by the square key via `cycle_display_mode()` over the class's `_display_mode_list` (default `[None]`; e.g. `UIGPSStatus` has `["large", "detailed"]`).
_Avoid_: view mode, layout, skin.
Expand Down
Binary file added docs/source/images/quick_start/align_day_fine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions docs/source/quick_start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,49 @@ the PiFinder to your telescope.
eyepiece you'd like objects placed if your initial alignment wasn't quite right.


Daytime alignment
^^^^^^^^^^^^^^^^^^^
The alignment above relies on a plate solve, so it only works once there are stars to see. To
set things up during the day, choose 'Align (Day)' from the 'Start' menu. It reaches the same
result by hand: you point your telescope at a distant object, find where the camera sees it,
and mark that spot directly — no solve required.

.. image:: images/quick_start/align_day_start.png

Opening the screen switches the camera to a short daytime exposure so you can see what it's
pointing at. Press **SQUARE** to begin.

The view splits into four quadrants labelled to match the keypad corners — **7** top-left,
**9** top-right, **1** bottom-left, **3** bottom-right. Press the key for the quadrant your
object sits in.

.. image:: images/quick_start/align_day_quad1.png

That quarter fills the screen and divides into quadrants again, so each press narrows the
marker into a smaller area. Repeat for up to three rounds:

.. image:: images/quick_start/align_day_quad2.png
:width: 45%
.. image:: images/quick_start/align_day_quad3.png
:width: 45%

From there the arrow keys nudge the marker a pixel at a time for fine placement. The first
arrow press also leaves quadrant mode, so you can switch to fine adjustment at any point.

.. image:: images/quick_start/align_day_fine.png

Center your distant object in the eyepiece, move the marker onto it on screen, then press
**SQUARE** to save. Press **0** to exit without changing the alignment. Pick something far
enough away that the focus matches the night sky — a distant treetop, chimney, or hilltop,
not something across the room.

.. note::
In daylight the image is shown in plain white rather than the usual red, since night vision
isn't a concern. If the view is washed out or too dark, **+** and **-** adjust the exposure
by hand. Hold **SQUARE** for the marking menu, where 'Exp Auto' hands exposure back to the
camera and 'Center' returns the marker to the middle.


GPS Status
^^^^^^^^^^^

Expand Down
50 changes: 49 additions & 1 deletion python/PiFinder/camera_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@

logger = logging.getLogger("Camera.Interface")

# Daytime alignment uses the camera's native (driver) auto-exposure where it is
# available. On backends with no native AE (debug / non-Pi), `set_exp:native`
# falls back to this fixed short exposure -- short enough not to saturate in
# daylight while still usable for framing a distant object.
DAYTIME_AE_FALLBACK_EXPOSURE = 1000 # microseconds


class CameraInterface:
"""The CameraInterface interface."""
Expand All @@ -44,6 +50,18 @@ class CameraInterface:
_auto_exposure_pid: Optional[ExposurePIDController] = None
_auto_exposure_snr: Optional[ExposureSNRController] = None
_last_solve_time: Optional[float] = None
# Native (camera-driver) auto-exposure, distinct from the solver-driven
# auto-exposure above. Enabled for daytime alignment via `set_exp:native`.
_native_ae_enabled = False

def set_native_ae(self, enabled: bool) -> bool:
"""Enable/disable the camera's native (driver) auto-exposure.

Returns True if the backend actually supports native AE; False otherwise
(the caller then falls back to a fixed short exposure). The base
implementation has no native AE.
"""
return False

def initialize(self) -> None:
pass
Expand Down Expand Up @@ -320,16 +338,43 @@ def get_image_loop(
if exp_value == "auto":
# Enable auto-exposure mode
self._auto_exposure_enabled = True
self._native_ae_enabled = False
self._last_solve_time = None # Reset solve tracking
if self._auto_exposure_pid is None:
self._auto_exposure_pid = ExposurePIDController()
else:
self._auto_exposure_pid.reset()
console_queue.put("CAM: Auto-Exposure Enabled")
logger.info("Auto-exposure mode enabled")
elif exp_value == "native":
# Native (driver) auto-exposure for daytime align.
# Disable the solver-driven AE so it doesn't fight
# the driver; leave the saved camera_exp config
# untouched so the prior mode can be restored.
self._auto_exposure_enabled = False
self._last_solve_time = None
if self.set_native_ae(True):
self._native_ae_enabled = True
console_queue.put("CAM: Native AE")
logger.info("Native auto-exposure enabled")
else:
# No native AE on this backend (debug / non-Pi):
# fall back to a fixed short daylight exposure.
self._native_ae_enabled = False
self.exposure_time = DAYTIME_AE_FALLBACK_EXPOSURE
self.set_camera_config(
self.exposure_time, self.gain
)
console_queue.put("CAM: Day exposure")
logger.info(
"Native AE unsupported; fixed exposure "
f"{self.exposure_time}µs"
)
else:
# Disable auto-exposure and set manual exposure
# Disable auto-exposure and set manual exposure.
# set_camera_config also clears native AE on Pi.
self._auto_exposure_enabled = False
self._native_ae_enabled = False
self.exposure_time = int(exp_value)
self.set_camera_config(self.exposure_time, self.gain)
# Update config to reflect manual exposure value
Expand Down Expand Up @@ -404,7 +449,10 @@ def get_image_loop(

if command == "exp_up" or command == "exp_dn":
# Manual exposure adjustments disable auto-exposure
# (both solver-driven and native; set_camera_config
# also clears native AeEnable on Pi).
self._auto_exposure_enabled = False
self._native_ae_enabled = False
if command == "exp_up":
self.exposure_time = int(self.exposure_time * 1.25)
else:
Expand Down
16 changes: 15 additions & 1 deletion python/PiFinder/camera_pi.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ def capture_raw_file(self, filename) -> None:
def set_camera_config(
self, exposure_time: float, gain: float
) -> Tuple[float, float]:
# picamera2 supports changing controls on-the-fly without restart
# picamera2 supports changing controls on-the-fly without restart.
# Setting a manual exposure always disables native auto-exposure, so a
# prior `set_exp:native` (daytime align) can't keep overriding it.
self.camera.set_controls({"AeEnable": False})
self.camera.set_controls({"AnalogueGain": gain})
self.camera.set_controls({"ExposureTime": exposure_time})

Expand All @@ -240,6 +243,17 @@ def set_camera_config(
self.start_camera()
return exposure_time, gain

def set_native_ae(self, enabled: bool) -> bool:
"""Enable/disable picamera2's native auto-exposure (AEC/AGC).

Used by the daytime alignment screen so the driver picks a short
daylight exposure automatically. Returns True (Pi cameras support it).
"""
self.camera.set_controls({"AeEnable": enabled})
if not self._camera_started:
self.start_camera()
return True

def get_cam_type(self) -> str:
return self.camType

Expand Down
Loading
Loading