Skip to main content

ados_sdk/client/
cloud.rs

1//! Cloud publish, records and offload advertisement calls.
2
3use super::*;
4
5impl PluginIpcClient {
6    /// Publish one message on this plugin's own cloud stream.
7    ///
8    /// The host forwards `{stream, payload}` to the cloud relay under the
9    /// caller's verified plugin id, which publishes it at QoS 0 on
10    /// `ados/{device_id}/plugin/{plugin_id}/{stream}`. `stream` must match
11    /// `[a-z0-9][a-z0-9._-]{0,63}` and `payload` is opaque bytes of at most
12    /// [`ados_protocol::cloud_publish::MAX_PAYLOAD`]. The stream
13    /// [`ados_protocol::cloud_publish::VISION_DETECTIONS_STREAM`] instead lands
14    /// on the core detection topic and additionally needs
15    /// `vision.detection.publish`. Gated on `cloud.publish`.
16    ///
17    /// The reply is `{ok: true}` once the relay queued the message (the lane is
18    /// lossy, so this is not broker delivery), or a `not_available` map while
19    /// the relay is down.
20    pub async fn cloud_publish(&self, stream: &str, payload: &[u8]) -> Result<Value, ClientError> {
21        let args = Value::Map(vec![
22            (Value::from("stream"), Value::from(stream)),
23            (Value::from("payload"), Value::Binary(payload.to_vec())),
24        ]);
25        Ok(self
26            .send_request("cloud.publish", "cloud.publish", args)
27            .await?
28            .args)
29    }
30
31    /// Upsert one record `{collection, key, data}` into this plugin's own cloud
32    /// records in the operator's account.
33    ///
34    /// `collection` must match `[a-z0-9_.-]{1,64}`, `key` is 1..=256 UTF-8
35    /// bytes, and `data` must be JSON-representable (the host re-encodes it as
36    /// JSON, at most [`ados_protocol::cloud_publish::MAX_PAYLOAD`]). The record's
37    /// subject is `device_id` when given (e.g. the drone a workstation wrote a
38    /// job for), otherwise this node. Gated on `cloud.records`.
39    ///
40    /// The reply is `{ok: true}` only once the cloud accepted the write, so this
41    /// call waits past the relay's record bound rather than the default request
42    /// timeout; a `not_available` map comes back while the relay is down.
43    pub async fn cloud_records_put(
44        &self,
45        collection: &str,
46        key: &str,
47        data: Value,
48        device_id: Option<&str>,
49    ) -> Result<Value, ClientError> {
50        let mut args = vec![
51            (Value::from("collection"), Value::from(collection)),
52            (Value::from("key"), Value::from(key)),
53            (Value::from("data"), data),
54        ];
55        if let Some(device_id) = device_id {
56            args.push((Value::from("device_id"), Value::from(device_id)));
57        }
58        let args = Value::Map(args);
59        let timeout = self
60            .request_timeout
61            .max(ados_protocol::cloud_publish::RECORD_REPLY_TIMEOUT + RECORD_REPLY_SLACK);
62        Ok(self
63            .send_request_within("cloud.records.put", "cloud.records", args, timeout)
64            .await?
65            .args)
66    }
67
68    /// Report the perception-offload link this plugin holds.
69    ///
70    /// The host writes it as the offload-link sidecar, which goes stale
71    /// [`ados_protocol::offload_link::OFFLOAD_LINK_STALE_MS`] (20 s) after the
72    /// last write, so a plugin keeps an active link counted by re-advertising at
73    /// least every ~10 s, and advertises `paired: false` as soon as the link
74    /// drops rather than waiting out the window. The optional strings must be
75    /// 1..=256 printable bytes. The host stamps `paired` only when `target`
76    /// accepts a TCP connection, and refuses the call while another plugin's
77    /// link is fresh. Gated on `vision.offload.advertise`. The reply is
78    /// `{ok: true, paired: <as stamped>}`.
79    pub async fn offload_advertise(
80        &self,
81        advert: &OffloadAdvertisement,
82    ) -> Result<Value, ClientError> {
83        let opt = |v: &Option<String>| v.as_deref().map_or(Value::Nil, Value::from);
84        let args = Value::Map(vec![
85            (Value::from("paired"), Value::Boolean(advert.paired)),
86            (
87                Value::from("bearer_acceptable"),
88                Value::Boolean(advert.bearer_acceptable),
89            ),
90            (Value::from("target"), opt(&advert.target)),
91            (Value::from("device_id"), opt(&advert.device_id)),
92            (Value::from("model_id"), opt(&advert.model_id)),
93        ]);
94        Ok(self
95            .send_request("offload.advertise", "vision.offload.advertise", args)
96            .await?
97            .args)
98    }
99}