ascom_alpaca/api/
mod.rs

1/*!
2ASCOM Alpaca Device API v1
3
4The Alpaca API uses RESTful techniques and TCP/IP to enable ASCOM applications and devices to communicate across modern network environments.
5
6## Interface Versions
7These interface definitions include the updates introduced in **ASCOM Platform 7**.
8
9## Interface Behaviour
10The ASCOM Interface behavioural requirements for Alpaca drivers are the same as for COM based drivers and are documented in the <a href="https://ascom-standards.org/Help/Developer/html/N_ASCOM_DeviceInterface.htm">API Interface Definitions</a> e.g. the <a href="https://ascom-standards.org/Help/Developer/html/M_ASCOM_DeviceInterface_ITelescopeV3_SlewToCoordinates.htm">Telescope.SlewToCoordinates</a> method.       This document focuses on how to use the ASCOM Interface standards in their RESTful Alpaca form.
11*/
12
13#![expect(clippy::doc_markdown)]
14
15mod server_info;
16pub use server_info::*;
17
18#[cfg(any(feature = "camera", feature = "telescope"))]
19mod camera_telescope_shared;
20
21mod device_state;
22pub use device_state::TimestampedDeviceState;
23
24mod time_repr;
25
26use std::fmt::Debug;
27use std::sync::Arc;
28
29#[macro_use]
30mod macros;
31
32/// Types related to the general [`Device`] trait.
33pub mod device;
34pub use device::Device;
35
36/// A helper alias for the common type of futures returned by device traits.
37///
38/// You normally don't need to use it as long as you use `#[async_trait]` - it's mostly here for documentation purposes.
39pub type ASCOMResultFuture<'async_trait, T> =
40    futures::future::BoxFuture<'async_trait, crate::ASCOMResult<T>>;
41
42rpc_mod! {
43    #[cfg(feature = "camera")]
44    Camera = "camera",
45
46    #[cfg(feature = "cover_calibrator")]
47    CoverCalibrator = "covercalibrator",
48
49    #[cfg(feature = "dome")]
50    Dome = "dome",
51
52    #[cfg(feature = "filter_wheel")]
53    FilterWheel = "filterwheel",
54
55    #[cfg(feature = "focuser")]
56    Focuser = "focuser",
57
58    #[cfg(feature = "observing_conditions")]
59    ObservingConditions = "observingconditions",
60
61    #[cfg(feature = "rotator")]
62    Rotator = "rotator",
63
64    #[cfg(feature = "safety_monitor")]
65    SafetyMonitor = "safetymonitor",
66
67    #[cfg(feature = "switch")]
68    Switch = "switch",
69
70    #[cfg(feature = "telescope")]
71    Telescope = "telescope",
72}
73
74pub(crate) trait RetrieavableDevice: 'static + Device {
75    #[allow(unused)]
76    const TYPE: DeviceType;
77
78    fn get_storage(storage: &Devices) -> &[Arc<Self>];
79
80    #[cfg(feature = "server")]
81    fn to_configured_device(&self, as_number: usize) -> ConfiguredDevice<DeviceType> {
82        ConfiguredDevice {
83            name: self.static_name().to_owned(),
84            ty: Self::TYPE,
85            number: as_number,
86            unique_id: self.unique_id().to_owned(),
87        }
88    }
89}
90
91/// A trait for devices that can be registered in a `Devices` storage.
92///
93/// DynTrait is unused here, it's only necessary to cheat the type system
94/// and allow "overlapping" blanket impls of RegistrableDevice for different
95/// kinds of devices so that `devices.register(device)` "just works".
96pub(crate) trait RegistrableDevice<DynTrait: ?Sized>: Debug {
97    fn add_to(self, storage: &mut Devices);
98}
99
100impl Default for Devices {
101    fn default() -> Self {
102        // Invoke the inherent const implementation.
103        Self::default()
104    }
105}
106
107// we use internal interfaces to get type inference magic to work with polymorphic device types
108#[expect(private_bounds)]
109impl Devices {
110    /// Register a device in the storage.
111    ///
112    /// `device` can be an instance of any of the category traits (`Camera`, `Telescope`, etc.).
113    ///
114    /// Note that you don't need to provide the generic type parameter - it's here only for type
115    /// inference purposes to allow "overloads" that automatically register device into the correct
116    /// storage.
117    #[tracing::instrument(level = "debug", skip(self))]
118    pub fn register<DynTrait: ?Sized>(&mut self, device: impl RegistrableDevice<DynTrait>) {
119        device.add_to(self);
120    }
121
122    /// Iterate over all devices of a given type.
123    pub fn iter<DynTrait: ?Sized + RetrieavableDevice>(
124        &self,
125    ) -> impl ExactSizeIterator<Item = Arc<DynTrait>> {
126        DynTrait::get_storage(self).iter().map(Arc::clone)
127    }
128
129    /// Retrieve a device by its category trait and an index within that category.
130    ///
131    /// Example: `devices.get::<dyn Camera>(0)` returns the first camera in the storage.
132    pub fn get<DynTrait: ?Sized + RetrieavableDevice>(
133        &self,
134        device_number: usize,
135    ) -> Option<Arc<DynTrait>> {
136        DynTrait::get_storage(self).get(device_number).cloned()
137    }
138
139    #[cfg(feature = "server")]
140    pub(crate) fn get_for_server<DynTrait: ?Sized + RetrieavableDevice>(
141        &self,
142        device_number: usize,
143    ) -> crate::server::Result<Arc<DynTrait>> {
144        DynTrait::get_storage(self)
145            .get(device_number)
146            .map(Arc::clone)
147            .ok_or(crate::server::Error::UnknownDeviceNumber {
148                ty: DynTrait::TYPE,
149                device_number,
150            })
151    }
152}
153
154impl Extend<TypedDevice> for Devices {
155    fn extend<T: IntoIterator<Item = TypedDevice>>(&mut self, iter: T) {
156        for client in iter {
157            self.register(client);
158        }
159    }
160}
161
162impl FromIterator<TypedDevice> for Devices {
163    fn from_iter<T: IntoIterator<Item = TypedDevice>>(iter: T) -> Self {
164        let mut devices = Self::default();
165        devices.extend(iter);
166        devices
167    }
168}