fundamentum_sdk_mqtt/models/
action.rs

1use cloud_proto::{action_request::Action as CloudAction, action_response::Status as CloudStatus};
2use fundamentum_iot_mqtt_proto::com::fundamentum::actions::v1 as cloud_proto;
3use prost::{Message as _, bytes::Bytes};
4
5use crate::{
6    Device, PublishOptions, Publishable,
7    error::Error,
8    models::actions::{ClientAction, FileTransferAction},
9};
10
11/// A request from the cloud for a specific device to perform an action/command.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct ActionRequest {
14    /// Unique ID of the action.
15    pub id: u64,
16    /// A list of serial numbers for devices targeted by this request.
17    /// Specifies the devices on which the action should be applied, allowing for batch operations across multiple devices.
18    /// Defaults to the current device if empty.
19    pub target_devices: Vec<String>,
20    /// Types of the action
21    pub action: Action,
22}
23
24/// Enum defining all types of actions
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub enum Action {
27    /// Custom *action* defined by the client.
28    ClientAction(ClientAction),
29    /// Action to transfer one file to and from the Cloud.
30    FileTransferAction(FileTransferAction),
31}
32
33impl TryFrom<Bytes> for ActionRequest {
34    type Error = Error;
35
36    fn try_from(data: Bytes) -> Result<Self, Self::Error> {
37        cloud_proto::ActionRequest::decode(data)?.try_into()
38    }
39}
40
41impl TryFrom<cloud_proto::ActionRequest> for ActionRequest {
42    type Error = Error;
43
44    fn try_from(action_request: cloud_proto::ActionRequest) -> Result<Self, Self::Error> {
45        let action = match action_request.action {
46            Some(CloudAction::ClientAction(a)) => Action::ClientAction(a.into()),
47            Some(CloudAction::FileTransferAction(a)) => Action::FileTransferAction(a.try_into()?),
48            _ => {
49                return Err(Error::InvalidRequest(format!(
50                    "Action is not supported: {action_request:?}"
51                )));
52            }
53        };
54
55        Ok(Self {
56            id: action_request.id,
57            target_devices: action_request.target_devices,
58            action,
59        })
60    }
61}
62
63/// The various ways a device may respond to an action request.
64/// Might be returned multiple times for some statuses (ongoing, deferred).
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct ActionResponse {
67    /// Action id
68    pub id: u64,
69    /// A list of serial numbers representing the devices that share the same response.
70    /// This field allows to send a collective response to Fundamentum for multiple devices simultaneously.
71    /// Defaults to the current device if empty.
72    pub serial_numbers: Vec<String>,
73    /// Response message to display in Fundamentum's UI.
74    pub message: String,
75    /// Optional action user response payload.
76    /// Left empty when unspecified.
77    pub payload: Vec<u8>,
78    /// Status of the action.
79    pub status: Status,
80}
81
82impl Publishable for ActionResponse {
83    type Error = Error;
84
85    fn topic(&self, device: &Device) -> impl Into<String> + Send {
86        format!(
87            "registries/{}/devices/{}/actions/{}/response",
88            device.registry_id(),
89            device.serial(),
90            self.id
91        )
92    }
93
94    fn payload(&self) -> Result<impl Into<Bytes> + Send, Error> {
95        Ok(cloud_proto::ActionResponse::from(self.clone()).encode_to_vec())
96    }
97
98    fn publish_overrides(&self) -> PublishOptions {
99        PublishOptions::default()
100    }
101}
102
103/// Status of the action.
104#[derive(Debug, Clone, PartialEq, Eq, Hash)]
105pub enum Status {
106    /// The action completed successfully.
107    Success,
108    /// The action failed to complete.
109    Failure,
110    /// The action takes some time to complete and is ongoing.
111    /// Another status shall be sent to update the progress or signal its completion.
112    Ongoing {
113        /// Progress of the action from 0-100% in increment of 1%.
114        progress: u32,
115    },
116    /// The action has been received but can't be processed right now.
117    /// Another status shall be sent to signal its progress/completion.
118    Deferred,
119}
120
121impl From<ActionResponse> for cloud_proto::ActionResponse {
122    fn from(action_response: ActionResponse) -> Self {
123        Self {
124            serial_numbers: action_response.serial_numbers,
125            message: action_response.message,
126            payload: action_response.payload,
127            status: Some(action_response.status.into()),
128        }
129    }
130}
131
132impl From<Status> for CloudStatus {
133    fn from(status: Status) -> Self {
134        match status {
135            Status::Success => Self::Success(cloud_proto::SuccessStatus {}),
136            Status::Failure => Self::Failure(cloud_proto::FailureStatus {}),
137            Status::Ongoing { progress } => Self::Ongoing(cloud_proto::OngoingStatus { progress }),
138            Status::Deferred => Self::Deferred(cloud_proto::DeferredStatus {}),
139        }
140    }
141}