Skip to main content

printobserver_types/
action.rs

1//! The closed vocabulary an actor may ask for, and the record of one request.
2//!
3//! [`PrintAction`] has no arbitrary-command variant, no G-code field and no
4//! escape hatch, which is the whole point of the vocabulary being closed: it is
5//! the reason an agent is allowed near a machine that can destroy itself. The
6//! only free-form text any variant carries is the `reason` every action
7//! carries, and the only caller-supplied text that reaches the printer's own
8//! file API is a [`FileName`](crate::FileName), which validates itself.
9//!
10//! Filtration is deliberately not a variant: nobody has yet confirmed that the
11//! target printer exposes filtration through `OctoPrint` at all, and an action
12//! the machine cannot perform is one an agent can call that silently does
13//! nothing.
14
15use serde::{Deserialize, Serialize};
16
17use schemars::JsonSchema;
18
19use crate::file_name::FileName;
20use crate::ids::{ActionId, EventId, PrintId};
21use crate::manifest::JobManifest;
22use crate::policy::PolicyDecision;
23use crate::timestamp::Timestamp;
24
25/// Who asked for something.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
27#[serde(rename_all = "snake_case", deny_unknown_fields)]
28pub enum Actor {
29    /// The supervising agent, naming its session.
30    Agent {
31        /// The supervision session the agent is acting in.
32        session_name: String,
33    },
34    /// A person.
35    Operator,
36    /// The supervisor itself.
37    System,
38}
39
40impl Actor {
41    /// The class this actor belongs to.
42    #[must_use]
43    pub const fn class(&self) -> ActorClass {
44        match self {
45            Self::Agent { .. } => ActorClass::Agent,
46            Self::Operator => ActorClass::Operator,
47            Self::System => ActorClass::System,
48        }
49    }
50}
51
52/// An actor class, which is what a safety envelope grants actions to.
53#[derive(
54    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
55)]
56#[serde(rename_all = "snake_case", deny_unknown_fields)]
57pub enum ActorClass {
58    /// The supervising agent.
59    Agent,
60    /// A person.
61    Operator,
62    /// The supervisor itself.
63    System,
64}
65
66/// What an operator's acknowledgement of a failure event asks for next.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
68#[serde(rename_all = "snake_case", deny_unknown_fields)]
69pub enum AcknowledgementDisposition {
70    /// Carry on printing.
71    Continue,
72    /// Carry on printing, watched more closely.
73    Watch,
74    /// Stop the print.
75    Stop,
76}
77
78/// One action of the closed vocabulary, named without its payload.
79///
80/// This is what a safety envelope grants and what a policy rejection names; the
81/// payload lives on [`PrintAction`] itself.
82#[derive(
83    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
84)]
85#[serde(rename_all = "snake_case", deny_unknown_fields)]
86pub enum ActionKind {
87    /// Pause the print.
88    Pause,
89    /// Resume the print.
90    Resume,
91    /// Cancel the print.
92    Cancel,
93    /// Start a print of a named file.
94    StartPrint,
95    /// Set the feedrate factor.
96    SetFeedrateFactor,
97    /// Set the flowrate factor.
98    SetFlowrateFactor,
99    /// Set a tool's target temperature.
100    SetToolTargetC,
101    /// Set the bed's target temperature.
102    SetBedTargetC,
103    /// Set the fan percentage.
104    SetFanPercent,
105    /// Acknowledge a failure event.
106    AcknowledgeFailure,
107}
108
109/// The whole vocabulary an actor may ask for, and there is no other.
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
111#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
112pub enum PrintAction {
113    /// Pause the print.
114    Pause {
115        /// Why the actor is asking for this.
116        reason: String,
117        /// Who is asking.
118        actor: Actor,
119    },
120    /// Resume the print.
121    Resume {
122        /// Why the actor is asking for this.
123        reason: String,
124        /// Who is asking.
125        actor: Actor,
126    },
127    /// Cancel the print.
128    Cancel {
129        /// Why the actor is asking for this.
130        reason: String,
131        /// Who is asking.
132        actor: Actor,
133    },
134    /// Start a print of a named file, with a manifest.
135    StartPrint {
136        /// The file to print, validated as a name a file API can be asked for.
137        file_name: FileName,
138        /// The manifest this print is bounded by.
139        manifest: JobManifest,
140        /// Why the actor is asking for this.
141        reason: String,
142        /// Who is asking.
143        actor: Actor,
144    },
145    /// Set the feedrate factor, where one means one hundred percent.
146    SetFeedrateFactor {
147        /// The multiplier asked for.
148        factor: f64,
149        /// How long the change stands for, in whole seconds.
150        #[serde(default, skip_serializing_if = "Option::is_none")]
151        duration_s: Option<i64>,
152        /// Why the actor is asking for this.
153        reason: String,
154        /// Who is asking.
155        actor: Actor,
156    },
157    /// Set the flowrate factor, where one means one hundred percent.
158    SetFlowrateFactor {
159        /// The multiplier asked for.
160        factor: f64,
161        /// How long the change stands for, in whole seconds.
162        #[serde(default, skip_serializing_if = "Option::is_none")]
163        duration_s: Option<i64>,
164        /// Why the actor is asking for this.
165        reason: String,
166        /// Who is asking.
167        actor: Actor,
168    },
169    /// Set one tool's target temperature.
170    SetToolTargetC {
171        /// The tool, in the printer's own numbering.
172        tool: i64,
173        /// The target temperature asked for, in degrees Celsius.
174        target_c: f64,
175        /// How long the change stands for, in whole seconds.
176        #[serde(default, skip_serializing_if = "Option::is_none")]
177        duration_s: Option<i64>,
178        /// Why the actor is asking for this.
179        reason: String,
180        /// Who is asking.
181        actor: Actor,
182    },
183    /// Set the bed's target temperature.
184    SetBedTargetC {
185        /// The target temperature asked for, in degrees Celsius.
186        target_c: f64,
187        /// How long the change stands for, in whole seconds.
188        #[serde(default, skip_serializing_if = "Option::is_none")]
189        duration_s: Option<i64>,
190        /// Why the actor is asking for this.
191        reason: String,
192        /// Who is asking.
193        actor: Actor,
194    },
195    /// Set the part-cooling fan percentage.
196    SetFanPercent {
197        /// The percentage asked for.
198        percent: f64,
199        /// How long the change stands for, in whole seconds.
200        #[serde(default, skip_serializing_if = "Option::is_none")]
201        duration_s: Option<i64>,
202        /// Why the actor is asking for this.
203        reason: String,
204        /// Who is asking.
205        actor: Actor,
206    },
207    /// Acknowledge a failure event, with a disposition.
208    AcknowledgeFailure {
209        /// The event being acknowledged.
210        event_id: EventId,
211        /// What to do next.
212        disposition: AcknowledgementDisposition,
213        /// Why the actor is asking for this.
214        reason: String,
215        /// Who is asking.
216        actor: Actor,
217    },
218}
219
220impl PrintAction {
221    /// Which action this is, without its payload.
222    #[must_use]
223    pub const fn kind(&self) -> ActionKind {
224        match self {
225            Self::Pause { .. } => ActionKind::Pause,
226            Self::Resume { .. } => ActionKind::Resume,
227            Self::Cancel { .. } => ActionKind::Cancel,
228            Self::StartPrint { .. } => ActionKind::StartPrint,
229            Self::SetFeedrateFactor { .. } => ActionKind::SetFeedrateFactor,
230            Self::SetFlowrateFactor { .. } => ActionKind::SetFlowrateFactor,
231            Self::SetToolTargetC { .. } => ActionKind::SetToolTargetC,
232            Self::SetBedTargetC { .. } => ActionKind::SetBedTargetC,
233            Self::SetFanPercent { .. } => ActionKind::SetFanPercent,
234            Self::AcknowledgeFailure { .. } => ActionKind::AcknowledgeFailure,
235        }
236    }
237
238    /// Why the actor asked for this.
239    #[must_use]
240    pub fn reason(&self) -> &str {
241        match self {
242            Self::Pause { reason, .. }
243            | Self::Resume { reason, .. }
244            | Self::Cancel { reason, .. }
245            | Self::StartPrint { reason, .. }
246            | Self::SetFeedrateFactor { reason, .. }
247            | Self::SetFlowrateFactor { reason, .. }
248            | Self::SetToolTargetC { reason, .. }
249            | Self::SetBedTargetC { reason, .. }
250            | Self::SetFanPercent { reason, .. }
251            | Self::AcknowledgeFailure { reason, .. } => reason,
252        }
253    }
254
255    /// Who asked for this.
256    #[must_use]
257    pub const fn actor(&self) -> &Actor {
258        match self {
259            Self::Pause { actor, .. }
260            | Self::Resume { actor, .. }
261            | Self::Cancel { actor, .. }
262            | Self::StartPrint { actor, .. }
263            | Self::SetFeedrateFactor { actor, .. }
264            | Self::SetFlowrateFactor { actor, .. }
265            | Self::SetToolTargetC { actor, .. }
266            | Self::SetBedTargetC { actor, .. }
267            | Self::SetFanPercent { actor, .. }
268            | Self::AcknowledgeFailure { actor, .. } => actor,
269        }
270    }
271}
272
273/// What happened when an accepted action reached the printer.
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
275#[serde(rename_all = "snake_case", deny_unknown_fields)]
276pub enum ExecutionOutcome {
277    /// The printer took it.
278    Succeeded,
279    /// The printer did not, for this reason.
280    Failed {
281        /// Why it failed.
282        reason: String,
283    },
284}
285
286/// One request an actor made, at the instant it made it.
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
288#[serde(deny_unknown_fields)]
289pub struct ActionRequest {
290    /// What was asked for.
291    pub action: PrintAction,
292    /// Who asked.
293    pub actor: Actor,
294    /// When they asked.
295    pub requested_at: Timestamp,
296}
297
298/// One request, the decision taken on it, and what became of it.
299///
300/// `executed_at` and `outcome` are both optional and are absent together: a
301/// record is written when the decision is taken, which is before — and, for a
302/// rejected request, instead of — anything reaching the printer.
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
304#[serde(deny_unknown_fields)]
305pub struct ActionRecord {
306    /// This record's identifier, minted by the store.
307    pub id: ActionId,
308    /// The print this request was made against.
309    pub print_id: PrintId,
310    /// The request itself.
311    pub request: ActionRequest,
312    /// The decision policy took on it.
313    pub decision: PolicyDecision,
314    /// When it reached the printer, if it did.
315    #[serde(default, skip_serializing_if = "Option::is_none")]
316    pub executed_at: Option<Timestamp>,
317    /// What the printer made of it, if it reached the printer.
318    #[serde(default, skip_serializing_if = "Option::is_none")]
319    pub outcome: Option<ExecutionOutcome>,
320}