Skip to main content

ados_sdk/client/
mavlink.rs

1//! MAVLink, MSP, telemetry and flight-setpoint calls.
2
3use super::*;
4
5impl PluginIpcClient {
6    /// Send a MAVLink frame, optionally from a registered component id.
7    /// Mirrors `mavlink_send`.
8    pub async fn mavlink_send(
9        &self,
10        msg_bytes: &[u8],
11        component_id: Option<i64>,
12    ) -> Result<Value, ClientError> {
13        let mut entries = vec![(Value::from("msg_bytes"), Value::Binary(msg_bytes.to_vec()))];
14        if let Some(comp) = component_id {
15            entries.push((Value::from("component_id"), Value::from(comp)));
16        }
17        Ok(self
18            .send_request("mavlink.send", "mavlink.write", Value::Map(entries))
19            .await?
20            .args)
21    }
22
23    /// Register a MAVLink component of the given kind (camera, gimbal, payload,
24    /// peripheral, vio). The required cap is `mavlink.component.<kind>`. Mirrors
25    /// `mavlink_register_component`.
26    pub async fn mavlink_register_component(
27        &self,
28        comp_id: i64,
29        kind: &str,
30    ) -> Result<Value, ClientError> {
31        let args = Value::Map(vec![
32            (Value::from("component_id"), Value::from(comp_id)),
33            (Value::from("kind"), Value::from(kind)),
34        ]);
35        let cap = format!("mavlink.component.{kind}");
36        Ok(self
37            .send_request("mavlink.register_component", &cap, args)
38            .await?
39            .args)
40    }
41
42    /// Subscribe to a MAVLink message name and register a callback. MAVLink
43    /// deliveries arrive as `mavlink.deliver` envelopes and route by
44    /// `msg_name`. Mirrors `mavlink_subscribe`.
45    pub async fn mavlink_subscribe(
46        &self,
47        msg_name: &str,
48        callback: EventCallback,
49    ) -> Result<(), ClientError> {
50        register_callback(&self.mavlink_callbacks, msg_name, callback);
51        let args = Value::Map(vec![(Value::from("msg_name"), Value::from(msg_name))]);
52        self.send_request("mavlink.subscribe", "mavlink.read", args)
53            .await?;
54        Ok(())
55    }
56
57    /// Write raw MSP bytes (already framed by `ados_protocol::msp`) to the FC.
58    /// Gated on `msp.write`. The reply is `{sent, len}` or a `not_available`
59    /// map when no MSP FC is wired.
60    pub async fn msp_send(&self, msg_bytes: &[u8]) -> Result<Value, ClientError> {
61        let args = Value::Map(vec![(
62            Value::from("msg_bytes"),
63            Value::Binary(msg_bytes.to_vec()),
64        )]);
65        Ok(self.send_request("msp.send", "msp.write", args).await?.args)
66    }
67
68    /// Subscribe to the raw FC->host MSP byte stream and register a callback.
69    /// Deliveries arrive as `msp.deliver` events carrying `{bytes, timestamp_ms}`;
70    /// MSP has no per-message topic, so the callback sees every chunk and its own
71    /// codec parses it. Gated on `msp.read`. Mirrors the button subscribe shape.
72    pub async fn msp_subscribe(&self, callback: EventCallback) -> Result<(), ClientError> {
73        register_callback(&self.msp_callbacks, VISION_ANY_CAMERA, callback);
74        self.send_request("msp.subscribe", "msp.read", Value::Map(vec![]))
75            .await?;
76        Ok(())
77    }
78
79    /// Extend the heartbeat schema with a new channel. Mirrors
80    /// `telemetry_extend`.
81    pub async fn telemetry_extend(
82        &self,
83        channel: &str,
84        payload: Value,
85    ) -> Result<Value, ClientError> {
86        let args = Value::Map(vec![
87            (Value::from("channel"), Value::from(channel)),
88            (Value::from("payload"), payload),
89        ]);
90        Ok(self
91            .send_request("telemetry.extend", "telemetry.extend", args)
92            .await?
93            .args)
94    }
95
96    /// Subscribe to the vehicle-state snapshot and register a callback. The
97    /// host pushes the state object as an event on [`TELEMETRY_STATE_TOPIC`],
98    /// at most [`TELEMETRY_STATE_MAX_HZ`](ados_protocol::plugin::TELEMETRY_STATE_MAX_HZ)
99    /// times a second and always the newest; the callback receives the event's
100    /// args map (`topic`, `payload`, `publisher`, `timestamp_ms`) like any event.
101    /// Gated on `telemetry.read`. A node with no flight controller never fires
102    /// it.
103    pub async fn telemetry_subscribe(&self, callback: EventCallback) -> Result<(), ClientError> {
104        register_callback(&self.event_callbacks, TELEMETRY_STATE_TOPIC, callback);
105        self.send_request("telemetry.subscribe", "telemetry.read", Value::Map(vec![]))
106            .await?;
107        Ok(())
108    }
109}
110
111impl PluginIpcClient {
112    /// Send one application payload over a MAVLink TUNNEL frame.
113    ///
114    /// `payload_type` must be a private type (> 32767); the host validates it
115    /// before anything is sent.
116    pub async fn mavlink_tunnel_send(
117        &self,
118        payload_type: u16,
119        payload: &[u8],
120    ) -> Result<Value, ClientError> {
121        let args = Value::Map(vec![
122            (
123                Value::from("payload_type"),
124                Value::Integer(payload_type.into()),
125            ),
126            (Value::from("payload"), Value::Binary(payload.to_vec())),
127        ]);
128        Ok(self
129            .send_request("mavlink.tunnel.send", "mavlink.tunnel", args)
130            .await?
131            .args)
132    }
133
134    /// Send one guided-mode setpoint through the scoped sender.
135    ///
136    /// `args` is the setpoint map the host validates (`kind`, `coordinate_frame`,
137    /// `type_mask` and the axis fields). Single-shot by design: the host owns no
138    /// flight mode and no schedule, so a caller holding a velocity re-sends above
139    /// the autopilot's setpoint timeout or the vehicle brakes.
140    pub async fn flight_guided_setpoint(&self, args: Value) -> Result<Value, ClientError> {
141        Ok(self
142            .send_request(
143                "flight.guided_setpoint.send",
144                "flight.guided_setpoint",
145                args,
146            )
147            .await?
148            .args)
149    }
150
151    /// Send one attitude/body-rate + thrust setpoint (SET_ATTITUDE_TARGET)
152    /// through the scoped sender.
153    ///
154    /// `args` is the setpoint map the host validates (`type_mask`, `qw`/`qx`/
155    /// `qy`/`qz`, `body_*_rate`, `thrust`, optional `target_system`/
156    /// `target_component`). Single-shot by design, like the guided sender: the
157    /// host owns no flight mode and no schedule, so a caller holding an attitude
158    /// re-sends above the autopilot's setpoint timeout or the vehicle reverts.
159    pub async fn flight_rate_setpoint(&self, args: Value) -> Result<Value, ClientError> {
160        Ok(self
161            .send_request("flight.rate_setpoint.send", "flight.rate_setpoint", args)
162            .await?
163            .args)
164    }
165}