ascom_alpaca/api/
switch.rs

1use super::Device;
2use crate::{ASCOMError, ASCOMResult};
3use macro_rules_attribute::apply;
4
5/// Switch Specific Methods.
6#[apply(rpc_trait)]
7pub trait Switch: Device + Send + Sync {
8    /// Returns the number of switch devices managed by this driver.
9    ///
10    /// Devices are numbered from 0 to MaxSwitch - 1.
11    #[http("maxswitch", method = Get)]
12    async fn max_switch(&self) -> ASCOMResult<i32>;
13
14    /// This endpoint must be implemented and indicates whether the given switch can operate asynchronously.
15    ///
16    /// _ISwitchV3 and later._
17    #[http("canasync", method = Get)]
18    async fn can_async(&self, #[http("Id")] id: i32) -> ASCOMResult<bool> {
19        Ok(false)
20    }
21
22    /// Reports if the specified switch device can be written to, default true.
23    ///
24    /// This is false if the device cannot be written to, for example a limit switch or a sensor.  Devices are numbered from 0 to MaxSwitch - 1.
25    #[http("canwrite", method = Get)]
26    async fn can_write(&self, #[http("Id")] id: i32) -> ASCOMResult<bool> {
27        Ok(false)
28    }
29
30    /// Return the state of switch device id as a boolean.  Devices are numbered from 0 to MaxSwitch - 1.
31    #[http("getswitch", method = Get /* TODO: , device_state = GetSwitch */)]
32    async fn get_switch(&self, #[http("Id")] id: i32) -> ASCOMResult<bool>;
33
34    /// Gets the description of the specified switch device.
35    ///
36    /// This is to allow a fuller description of the device to be returned, for example for a tool tip. Devices are numbered from 0 to MaxSwitch - 1.
37    #[http("getswitchdescription", method = Get)]
38    async fn get_switch_description(&self, #[http("Id")] id: i32) -> ASCOMResult<String>;
39
40    /// Gets the name of the specified switch device.
41    ///
42    /// Devices are numbered from 0 to MaxSwitch - 1.
43    #[http("getswitchname", method = Get)]
44    async fn get_switch_name(&self, #[http("Id")] id: i32) -> ASCOMResult<String>;
45
46    /// Gets the value of the specified switch device as a double.
47    ///
48    /// Devices are numbered from 0 to MaxSwitch - 1, The value of this switch is expected to be between MinSwitchValue and MaxSwitchValue.
49    #[http("getswitchvalue", method = Get /* TODO: , device_state = GetSwitchValue */)]
50    async fn get_switch_value(&self, #[http("Id")] id: i32) -> ASCOMResult<f64>;
51
52    /// Gets the minimum value of the specified switch device as a double.
53    ///
54    /// Devices are numbered from 0 to MaxSwitch - 1.
55    #[http("minswitchvalue", method = Get)]
56    async fn min_switch_value(&self, #[http("Id")] id: i32) -> ASCOMResult<f64>;
57
58    /// Gets the maximum value of the specified switch device as a double.
59    ///
60    /// Devices are numbered from 0 to MaxSwitch - 1.
61    #[http("maxswitchvalue", method = Get)]
62    async fn max_switch_value(&self, #[http("Id")] id: i32) -> ASCOMResult<f64>;
63
64    /// This is an asynchronous method that must return as soon as the state change operation has been successfully started,  with StateChangeComplete(Int16) for the given switch Id = False.  After the state change has completed StateChangeComplete(Int16) becomes True.
65    ///
66    /// _ISwitchV3 and later._
67    #[http("setasync", method = Put)]
68    async fn set_async(
69        &self,
70        #[http("Id")] id: i32,
71        #[http("State")] state: bool,
72    ) -> ASCOMResult<()> {
73        Err(ASCOMError::NOT_IMPLEMENTED)
74    }
75
76    /// This is an asynchronous method that must return as soon as the state change operation has been successfully started,  with StateChangeComplete(Int16) for the given switch Id = False.  After the state change has completed StateChangeComplete(Int16) becomes True.
77    ///
78    /// _ISwitchV3 and later._
79    #[http("setasyncvalue", method = Put)]
80    async fn set_async_value(
81        &self,
82
83        #[http("Id")] id: i32,
84
85        #[http("Value")] value: f64,
86    ) -> ASCOMResult<()> {
87        Err(ASCOMError::NOT_IMPLEMENTED)
88    }
89
90    /// Sets a switch controller device to the specified state, true or false.
91    #[http("setswitch", method = Put)]
92    async fn set_switch(
93        &self,
94        #[http("Id")] id: i32,
95        #[http("State")] state: bool,
96    ) -> ASCOMResult<()> {
97        Err(ASCOMError::NOT_IMPLEMENTED)
98    }
99
100    /// Sets a switch device name to the specified value.
101    #[http("setswitchname", method = Put)]
102    async fn set_switch_name(
103        &self,
104
105        #[http("Id")] id: i32,
106
107        #[http("Name")] name: String,
108    ) -> ASCOMResult<()> {
109        Err(ASCOMError::NOT_IMPLEMENTED)
110    }
111
112    /// Sets a switch device value to the specified value.
113    #[http("setswitchvalue", method = Put)]
114    async fn set_switch_value(
115        &self,
116
117        #[http("Id")] id: i32,
118
119        #[http("Value")] value: f64,
120    ) -> ASCOMResult<()> {
121        Err(ASCOMError::NOT_IMPLEMENTED)
122    }
123
124    /// True if the state of the specified switch is changing, otherwise false.
125    ///
126    /// _ISwitchV3 and later._
127    #[http("statechangecomplete", method = Get /* TODO:, device_state = StateChangeComplete */)]
128    async fn state_change_complete(&self, #[http("Id")] id: i32) -> ASCOMResult<bool>;
129
130    /// Returns the step size that this device supports (the difference between successive values of the device).
131    ///
132    /// Devices are numbered from 0 to MaxSwitch - 1.
133    #[http("switchstep", method = Get)]
134    async fn switch_step(&self, #[http("Id")] id: i32) -> ASCOMResult<f64>;
135
136    /// This method returns the version of the ASCOM device interface contract to which this device complies.
137    ///
138    /// Only one interface version is current at a moment in time and all new devices should be built to the latest interface version. Applications can choose which device interface versions they support and it is in their interest to support  previous versions as well as the current version to ensure thay can use the largest number of devices.
139    #[http("interfaceversion", method = Get)]
140    async fn interface_version(&self) -> ASCOMResult<i32> {
141        Ok(3_i32)
142    }
143}
144
145/// An object representing operational properties of a specific device connected to the switch.
146#[derive(Default, Debug, Clone, Copy)]
147pub struct SwitchDeviceState {
148    /// Result of [`Switch::get_switch`].
149    pub get_switch: Option<bool>,
150    /// Result of [`Switch::get_switch_value`].
151    pub get_switch_value: Option<f64>,
152    /// Result of [`Switch::state_change_complete`].
153    pub state_change_complete: Option<bool>,
154}
155
156impl SwitchDeviceState {
157    async fn new(switch: &(impl ?Sized + Switch), id: i32) -> Self {
158        Self {
159            get_switch: switch.get_switch(id).await.ok(),
160            get_switch_value: switch.get_switch_value(id).await.ok(),
161            state_change_complete: switch.state_change_complete(id).await.ok(),
162        }
163    }
164}
165
166/// An object representing all operational properties of the device.
167#[derive(Default, Debug, Clone)]
168pub struct DeviceState {
169    /// States of individual switch devices, indexed by their ID.
170    pub switch_devices: Vec<SwitchDeviceState>,
171}
172
173impl DeviceState {
174    async fn new(switch: &(impl ?Sized + Switch)) -> Self {
175        Self {
176            switch_devices: match switch.max_switch().await {
177                Ok(n) => {
178                    futures::future::join_all(
179                        (0_i32..n).map(|id| SwitchDeviceState::new(switch, id)),
180                    )
181                    .await
182                }
183                Err(err) => {
184                    tracing::error!(%err, "Failed to get max switch");
185                    Vec::new()
186                }
187            },
188        }
189    }
190}
191
192#[cfg(feature = "server")]
193impl serde::Serialize for DeviceState {
194    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
195        use serde::ser::SerializeMap;
196
197        let mut map = serializer.serialize_map(None)?;
198        for (i, device) in self.switch_devices.iter().enumerate() {
199            if let Some(value) = &device.get_switch {
200                map.serialize_entry(&format!("GetSwitch{i}"), value)?;
201            }
202            if let Some(value) = &device.get_switch_value {
203                map.serialize_entry(&format!("GetSwitchValue{i}"), value)?;
204            }
205            if let Some(value) = &device.state_change_complete {
206                map.serialize_entry(&format!("StateChangeComplete{i}"), value)?;
207            }
208        }
209        map.end()
210    }
211}
212
213#[cfg(feature = "client")]
214impl<'de> serde::Deserialize<'de> for DeviceState {
215    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
216        use serde::de;
217
218        struct Visitor;
219
220        impl<'de> de::Visitor<'de> for Visitor {
221            type Value = DeviceState;
222
223            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224                formatter.write_str("device state object")
225            }
226
227            fn visit_map<A: de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
228                let mut state = DeviceState::default();
229
230                while let Some(name) = map.next_key::<&'de str>()? {
231                    // This is pretty complicated because we want to transform shape like `{Name: "GetSwitch2", Value}` into `switch_devices[2].get_switch = Value`.
232                    let index_start = name.find(|c: char| c.is_ascii_digit()).ok_or_else(|| {
233                        de::Error::custom(format!("could not find switch device index in {name:?}"))
234                    })?;
235                    let (name, index) = name.split_at(index_start);
236                    let index = index.parse::<usize>().map_err(|err| {
237                        de::Error::custom(format_args!(
238                            "could not parse switch device index {index:?}: {err}"
239                        ))
240                    })?;
241                    // Auto-extend the vec to accommodate the new index. We don't have access to total number of devices here without another async call,
242                    // so we have to make guesses based on the returned data.
243                    if index >= state.switch_devices.len() {
244                        state
245                            .switch_devices
246                            .resize_with(index + 1, SwitchDeviceState::default);
247                    }
248                    let switch_device = &mut state.switch_devices[index];
249                    match name {
250                        "GetSwitch" => {
251                            switch_device.get_switch = Some(map.next_value()?);
252                        }
253                        "GetSwitchValue" => {
254                            switch_device.get_switch_value = Some(map.next_value()?);
255                        }
256                        "StateChangeComplete" => {
257                            switch_device.state_change_complete = Some(map.next_value()?);
258                        }
259                        other => {
260                            return Err(de::Error::unknown_field(
261                                other,
262                                &["GetSwitch", "GetSwitchValue", "StateChangeComplete"],
263                            ));
264                        }
265                    }
266                }
267
268                Ok(state)
269            }
270        }
271
272        deserializer.deserialize_map(Visitor)
273    }
274}