argos.core.alpaca¶
Devices¶
Low-level ASCOM Alpaca HTTP client.
Handles GET and PUT requests to any Alpaca device endpoint, manages ClientID / ClientTransactionID, and maps Alpaca error numbers to typed Python exceptions.
All methods are synchronous and intended to run inside a QThread worker. Never call them from the Qt main thread.
- class argos.core.alpaca.client.AlpacaClient(host: str, port: int, get_timeout: float = 5.0, put_timeout: float = 10.0)[source]¶
Bases:
objectSynchronous ASCOM Alpaca client for a single device endpoint.
- Parameters:
host – IP address or hostname of the Alpaca server.
port – TCP port of the Alpaca server (default 4700 for Seestar).
get_timeout – Seconds before a GET request times out.
put_timeout – Seconds before a PUT request times out.
- BASE_PATH = '/api/v1'¶
- get(device_type: str, device_number: int, attribute: str, **extra_params: Any) Any[source]¶
Send a GET request and return the Alpaca Value field.
- Parameters:
device_type – e.g. “telescope”, “camera”, “focuser”, “filterwheel”
device_number – Usually 0 for the Seestar.
attribute – Alpaca property name, e.g. “rightascension”.
**extra_params – Additional query parameters.
- Returns:
The
Valuefield from the Alpaca JSON response.- Raises:
AlpacaConnectionError – Device not reachable.
AlpacaTimeoutError – Request timed out.
AlpacaError – Device returned a non-zero error.
- put(device_type: str, device_number: int, method: str, **body_params: Any) Any[source]¶
Send a PUT request and return the Alpaca Value field (if any).
- Parameters:
device_type – e.g. “telescope”, “camera”.
device_number – Usually 0.
method – Alpaca method name, e.g. “tracking”.
**body_params – Form fields to include in the request body.
- Returns:
The
Valuefield from the Alpaca JSON response, or None.- Raises:
AlpacaConnectionError – Device not reachable.
AlpacaTimeoutError – Request timed out.
AlpacaError – Device returned a non-zero error.
- exception argos.core.alpaca.client.AlpacaConnectionError(host: str, port: int, reason: str = '')[source]¶
Bases:
AlpacaErrorCould not reach the Alpaca device.
- exception argos.core.alpaca.client.AlpacaError(number: int, message: str)[source]¶
Bases:
ExceptionAlpaca device returned a non-zero ErrorNumber.
- exception argos.core.alpaca.client.AlpacaTimeoutError(host: str, port: int)[source]¶
Bases:
AlpacaErrorRequest to the Alpaca device timed out.
ASCOM Alpaca discovery — UDP broadcast plus TCP fallbacks.
Layered strategy (discover_all), designed for field networks where the
UDP broadcast often fails (phone hotspots block it, some APs isolate clients):
UDP broadcast on port 32227 (the standard Alpaca discovery protocol).
Direct HTTP probes of known candidates — the last-used host and the Seestar’s fixed address in AP mode.
A TCP sweep of the local /24 subnet, confirming hits against the Alpaca management endpoint.
This module is pure Python with no Qt dependency. It is designed to be called from a QThread worker (DiscoveryWorker).
- class argos.core.alpaca.discovery.AlpacaDevice(host: str, port: int)[source]¶
Bases:
objectA discovered Alpaca server on the local network.
- argos.core.alpaca.discovery.discover(timeout: float = 8.0) list[AlpacaDevice][source]¶
Broadcast a discovery packet and return all responding Alpaca devices.
- Parameters:
timeout – How long to wait for responses (seconds).
- Returns:
List of discovered AlpacaDevice instances (may be empty).
- argos.core.alpaca.discovery.discover_all(port: int, candidates: tuple[str, ...] = (), broadcast_timeout: float = 8.0) list[AlpacaDevice][source]¶
Layered discovery: UDP broadcast, then candidate probes, then /24 scan.
- Parameters:
port – Alpaca HTTP port for the probe/scan fallbacks (the broadcast reply carries its own port).
candidates – Hosts worth probing directly when the broadcast fails — typically the last-used host and the profile’s AP-mode address.
broadcast_timeout – How long to wait for broadcast replies.
- Returns:
Discovered devices, possibly empty. The first entry is the best pick.
- argos.core.alpaca.discovery.local_ipv4() str | None[source]¶
Best-guess primary local IPv4, or None when no route exists.
UDP connect() sends no packet — it only asks the OS which source address it would route through, so this works with no internet.
- argos.core.alpaca.discovery.probe_alpaca(host: str, port: int, timeout: float = 2.0) bool[source]¶
True if
host:portanswers like an Alpaca server.Hits the management API (the one endpoint every Alpaca server must serve) rather than a bare TCP connect, so a random web server on the same port is not mistaken for the Seestar.
- argos.core.alpaca.discovery.scan_subnet(port: int, timeout: float = 0.4) list[AlpacaDevice][source]¶
Sweep the local /24 for an open Alpaca port, then confirm via HTTP.
~254 TCP connects across a thread pool finish in a couple of seconds; only hosts with the port open get the (slower) management probe.
ASCOM Alpaca telescope wrapper — backed by alpyca.
Uses the official alpyca library (ASCOM Initiative) instead of raw HTTP calls. Key benefits: - Automatic ImageBytes support (8x faster image transfers) - Typed exceptions with error codes - Thread-safe (safe to call from QThread workers)
All methods are synchronous and must run inside a QThread worker. Never call them from the Qt main thread.
- Seestar S30 Pro known limitations:
SlewToAltAzAsync→ DriverException (error 1024) — not implemented
Unpark → does not open the arm physically
MoveAxis → confirmed working on firmware 7.18+ (tested 2026-05)
- class argos.core.alpaca.telescope.MountPosition(ra: float, dec: float, altitude: float, azimuth: float, tracking: bool, slewing: bool)[source]¶
Bases:
objectCurrent pointing position of the mount.
- class argos.core.alpaca.telescope.Telescope(host: str, port: int)[source]¶
Bases:
objectASCOM Alpaca telescope controller, backed by alpyca.
- Parameters:
host – IP address of the Seestar.
port – Alpaca HTTP port (discovered via UDP, typically 4700).
- property alignment_mode: str | None¶
"Alt-Az","EQ","EQ (GEM)", or None when the firmware doesn’t say.- Type:
Mount geometry reported at connect
- connect() str[source]¶
Connect to the mount and return its name.
- Sequence:
Connected = True
Sync UTC date (fixes Seestar clock-sync bug on firmware < 3.0.2)
Check CanSlewAsync
- Raises:
AlpacaError – Connection or device error.
- get_position() MountPosition[source]¶
Read the current position from the mount.
- Raises:
AlpacaError – Communication or device error.
- get_tracking_rate() int | None[source]¶
Return the current ASCOM
DriveRatesvalue, or None if unsupported.
- move_axis(axis: int, rate: float) None[source]¶
Start continuous jog on one axis at the given rate (deg/s).
The mount keeps moving until
stop_axis()is called.- Parameters:
axis – 0 = Primary (RA/Az), 1 = Secondary (Dec/Alt).
rate – Speed in deg/s. Positive = North/East, negative = South/West. Pass 0.0 to stop.
- Raises:
AlpacaError – Device error or axis not supported.
- park() None[source]¶
Park the mount (closes the mechanical arm on Seestar S30 Pro).
If atpark=True (e.g. native app opened arm without Alpaca Unpark), sends Unpark first so the subsequent Park triggers physical movement.
- Raises:
AlpacaError – Mount does not support Park, or driver error.
- pulse_guide(direction: int, duration_ms: int) None[source]¶
Send a PulseGuide command (guide-rate only — not for manual jogging).
- Parameters:
direction – 0=North, 1=South, 2=East, 3=West (ASCOM constants).
duration_ms – Duration in milliseconds.
- set_target(ra: float, dec: float) None[source]¶
Set the mount’s TargetRightAscension/TargetDeclination without slewing.
Useful before a slew or sync so the mount’s pointing model records what we were aiming at — independent of the actual pointing it eventually achieves. Failures are logged and re-raised; some firmwares do not allow writing these properties (in which case the slew code path tolerates the failure on its own).
- set_tracking_rate(rate: int) None[source]¶
Select the tracking rate model.
- Parameters:
rate – ASCOM
DriveRatesenum value: 0=Sidereal, 1=Lunar, 2=Solar, 3=King.- Raises:
AlpacaError – Mount rejected the rate (e.g. only Sidereal supported).
- side_of_pier() str | None[source]¶
Return ‘EAST’ / ‘WEST’ / ‘UNKNOWN’, or None if the mount can’t tell.
For an alt-az like the Seestar this concept is meaningless — the property may simply not be exposed.
- slew_to(ra: float, dec: float) None[source]¶
Start an asynchronous GoTo slew (mirrors NINA sequence).
Check AtPark — raise if parked
Enable tracking
Set TargetRightAscension / TargetDeclination (log failures, don’t hide them)
Call SlewToCoordinatesAsync
Verify Slewing=True was set (warn if not — some mounts report False when already at target, which is acceptable)
- Parameters:
ra – Target RA in decimal hours (J2000), 0–24.
dec – Target Dec in decimal degrees (J2000), -90 to +90.
- Raises:
AlpacaError – Mount parked or slew rejected.
- stop_axis(axis: int) None[source]¶
Stop a jogging axis (sets rate to 0).
- Parameters:
axis – 0 = Primary (RA/Az), 1 = Secondary (Dec/Alt).
ASCOM Alpaca camera wrapper — backed by alpyca.
- Uses alpyca which automatically handles:
ImageBytes (binary, 8x faster than JSON) vs JSON imagearray
Typed exceptions
Thread-safe HTTP session
All methods are synchronous — run inside a QThread worker only.
- Seestar constraints:
Do NOT use ROI/subframing (firmware bug in Alpaca driver)
Sensor geometry comes from the active telescope profile; the driver’s CameraXSize/CameraYSize win over it at connect.
- class argos.core.alpaca.camera.Camera(host: str, port: int)[source]¶
Bases:
objectASCOM Alpaca camera controller, backed by alpyca.
- Parameters:
host – IP address of the Seestar.
port – Alpaca HTTP port.
- connect() str[source]¶
Connect and return the camera name.
Also reads sensor dimensions and gain range for the UI.
- Raises:
AlpacaError – Connection or device error.
- get_ccd_temperature() float | None[source]¶
Return sensor temperature in °C, or None if unsupported.
Falls back to
HeatSinkTemperatureifCCDTemperatureis absent (the Seestar is air-cooled — no Peltier — but exposes a sensor probe).
- get_electrons_per_adu() float | None[source]¶
Return ElectronsPerADU from the driver, or None if not exposed.
- get_image_array() ndarray[source]¶
Download the last captured image as a 2-D numpy uint16 array.
Tries ImageArrayRaw first (binary ImageBytes, zero-copy numpy path) and falls back to ImageArray (JSON) if the device does not support ImageBytes.
- Returns:
2-D numpy array, shape (height, width), dtype uint16.
- Raises:
AlpacaError – On communication or device error.
- get_readout_mode_name() str | None[source]¶
Return current readout mode name (e.g. ‘Normal’, ‘HCG’), or None.
- get_sensor_metadata() dict[source]¶
Snapshot of static sensor metadata for FITS / diagnostics.
Each field is tolerant — missing values are simply absent from the dict.
- set_binning(value: int) None[source]¶
Set symmetric binning (BinX = BinY, clamped to
max_bin) and retarget the frame size so the next exposure fills the binned sensor.
- set_gain(gain: int) None[source]¶
Set camera gain (clamped to range). No-op if the driver doesn’t implement Gain — some cameras / the ASCOM sim don’t expose it, and that must not break capture.
- set_offset(value: int) None[source]¶
Set the electronic offset (clamped to the driver range). No-op if the driver doesn’t implement Offset — that must not break capture.
- start_exposure(duration: float, light: bool = True) None[source]¶
Start a single exposure.
- Parameters:
duration – Exposure time in seconds.
light – True for light frame, False for dark/bias.
- Raises:
AlpacaError – If the camera rejects the command.
- try_get_gain() int | None[source]¶
Gain as the driver reports it, or None when the driver has no Gain.
Unlike
get_gain()this never substitutes a default — it exists for header truthfulness (P1): a value we cannot read back must not be presented as verified.
ASCOM Alpaca focuser wrapper — backed by alpyca.
The Seestar S30 Pro exposes two focusers via Alpaca (configureddevices discovery): index 0 is the telephoto focuser used by the science camera, index 1 is the wide-angle finder. Argos only drives the telephoto focuser — the wide-angle is for the native Seestar app’s plate-solve loop.
All methods are synchronous and must run inside a QThread worker; never call them from the Qt main thread (network round-trips can take 200+ ms).
- class argos.core.alpaca.focuser.Focuser(host: str, port: int, device_index: int = 0)[source]¶
Bases:
objectASCOM Alpaca focuser controller, backed by alpyca.
- Parameters:
host – IP address of the Seestar.
port – Alpaca HTTP port.
device_index – 0 = telephoto (science), 1 = wide-angle. Default 0.
- get_temperature() float | None[source]¶
Return the focuser-reported temperature, or None if unsupported.
ASCOM Alpaca filter wheel wrapper — backed by alpyca.
- Seestar S30 Pro filter wheel (device 0):
Position 0 — Dark (closed / calibration) Position 1 — IR (infrared pass) Position 2 — LP (light pollution filter)
All methods are synchronous. Run from a QThread worker only.
- class argos.core.alpaca.filterwheel.FilterWheel(host: str, port: int)[source]¶
Bases:
objectASCOM Alpaca filter wheel controller.
- Parameters:
host – IP address of the Seestar.
port – Alpaca HTTP port (32323).
- connect() None[source]¶
Connect to the filter wheel.
- Raises:
AlpacaError – Connection or device error.
- get_position() int[source]¶
Return the current filter position (0–2).
Returns -1 while the wheel is moving.
- Raises:
AlpacaError – Device error.
- set_position(position: int) None[source]¶
Command a filter change.
The move is asynchronous — the wheel returns immediately and
get_position()returns -1 while moving.- Parameters:
position – Target position (0=Dark, 1=IR, 2=LP).
- Raises:
AlpacaError – Invalid position or device error.
- argos.core.alpaca.filterwheel.position_count() int[source]¶
Number of slots on the active profile’s internal wheel (0 = no wheel).
- argos.core.alpaca.filterwheel.position_names() dict[int, str][source]¶
Slot index → filter name, for the active telescope profile.
Was a module constant fixed to the S30 Pro’s Dark/IR/LP wheel. Call it rather than binding it: a model with a different wheel — or none — must be able to change the answer.