Skip to content

Commit 65ffc9a

Browse files
authored
Add in MachineState to the Control trait, HTTP Endpoint (#118)
This is a first step as we start to chip away on #68 slash #49 and others. This will add a standard part of our "Control" trait to check on the state of a printer -- is the printer running? idle? paused mid-print? Broken? I implemented this for Moonraker (since I was able to test it), and stubbed in Unknown elsewhere. USB is doable here -- and I think we can do Bambu, but that may wait until I get into the office to play with it myself.
1 parent 67f6825 commit 65ffc9a

File tree

12 files changed

+222
-13
lines changed

12 files changed

+222
-13
lines changed

moonraker/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
mod metrics;
1414
mod print;
15+
mod status;
1516
mod upload;
1617

1718
use anyhow::Result;

moonraker/src/status.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use anyhow::Result;
2+
use serde::{Deserialize, Serialize};
3+
4+
use super::Client;
5+
6+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
7+
pub struct VirtualSdcard {
8+
pub progress: f64,
9+
pub file_position: f64,
10+
pub is_active: bool,
11+
pub file_path: Option<String>,
12+
pub file_size: f64,
13+
}
14+
15+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
16+
pub struct Webhooks {
17+
pub state: String,
18+
pub state_message: String,
19+
}
20+
21+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
22+
pub struct PrintStats {
23+
pub print_duration: f64,
24+
pub total_duration: f64,
25+
pub filament_used: f64,
26+
pub filename: String,
27+
pub state: String,
28+
pub message: String,
29+
}
30+
31+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
32+
pub struct Status {
33+
pub virtual_sdcard: VirtualSdcard,
34+
pub webhooks: Webhooks,
35+
pub print_stats: PrintStats,
36+
}
37+
38+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
39+
struct QueryResponse {
40+
status: Status,
41+
eventtime: f64,
42+
}
43+
44+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
45+
struct QueryResponseWrapper {
46+
result: QueryResponse,
47+
}
48+
49+
impl Client {
50+
/// Print an uploaded file.
51+
pub async fn status(&self) -> Result<Status> {
52+
tracing::debug!(base = self.url_base, "requesting status");
53+
let client = reqwest::Client::new();
54+
55+
let resp: QueryResponseWrapper = client
56+
.get(format!(
57+
"{}/printer/objects/query?webhooks&virtual_sdcard&print_stats",
58+
self.url_base
59+
))
60+
.send()
61+
.await?
62+
.json()
63+
.await?;
64+
65+
Ok(resp.result.status)
66+
}
67+
}

openapi/api.json

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,21 @@
113113
],
114114
"description": "Maximum part size that can be manufactured by this device. This may be some sort of theoretical upper bound, getting close to this limit seems like maybe a bad idea.\n\nThis may be `None` if the maximum size is not knowable by the Machine API.\n\nWhat \"close\" means is up to you!",
115115
"nullable": true
116+
},
117+
"state": {
118+
"allOf": [
119+
{
120+
"$ref": "#/components/schemas/MachineState"
121+
}
122+
],
123+
"description": "Status of the printer -- be it printing, idle, or unreachable. This may dictate if a machine is capable of taking a new job."
116124
}
117125
},
118126
"required": [
119127
"id",
120128
"machine_type",
121-
"make_model"
129+
"make_model",
130+
"state"
122131
],
123132
"type": "object"
124133
},
@@ -143,6 +152,67 @@
143152
},
144153
"type": "object"
145154
},
155+
"MachineState": {
156+
"description": "Current state of the machine -- be it printing, idle or offline. This can be used to determine if a printer is in the correct state to take a new job.",
157+
"oneOf": [
158+
{
159+
"description": "If a print state can not be resolved at this time, an Unknown may be returned.",
160+
"enum": [
161+
"Unknown"
162+
],
163+
"type": "string"
164+
},
165+
{
166+
"description": "Idle, and ready for another job.",
167+
"enum": [
168+
"Idle"
169+
],
170+
"type": "string"
171+
},
172+
{
173+
"description": "Running a job -- 3D printing or CNC-ing a part.",
174+
"enum": [
175+
"Running"
176+
],
177+
"type": "string"
178+
},
179+
{
180+
"description": "Machine is currently offline or unreachable.",
181+
"enum": [
182+
"Offline"
183+
],
184+
"type": "string"
185+
},
186+
{
187+
"description": "Job is underway but halted, waiting for some action to take place.",
188+
"enum": [
189+
"Paused"
190+
],
191+
"type": "string"
192+
},
193+
{
194+
"description": "Job is finished, but waiting manual action to move back to Idle.",
195+
"enum": [
196+
"Complete"
197+
],
198+
"type": "string"
199+
},
200+
{
201+
"additionalProperties": false,
202+
"description": "The printer has failed and is in an unknown state that may require manual attention to resolve. The inner value is a human readable description of what specifically has failed.",
203+
"properties": {
204+
"Failed": {
205+
"nullable": true,
206+
"type": "string"
207+
}
208+
},
209+
"required": [
210+
"Failed"
211+
],
212+
"type": "object"
213+
}
214+
]
215+
},
146216
"MachineType": {
147217
"description": "Specific technique by which this Machine takes a design, and produces a real-world 3D object.",
148218
"oneOf": [

src/any_machine.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::Result;
22

3-
use crate::{Control as ControlTrait, MachineInfo, MachineMakeModel, MachineType, Volume};
3+
use crate::{Control as ControlTrait, MachineInfo, MachineMakeModel, MachineState, MachineType, Volume};
44

55
/// AnyMachine is any supported machine.
66
#[non_exhaustive]
@@ -128,4 +128,8 @@ impl ControlTrait for AnyMachine {
128128
async fn healthy(&self) -> bool {
129129
for_all!(|self, machine| { machine.healthy().await })
130130
}
131+
132+
async fn state(&self) -> Result<MachineState> {
133+
for_all!(|self, machine| { machine.state().await })
134+
}
131135
}

src/bambu/control.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bambulabs::{client::Client, command::Command};
33

44
use super::{PrinterInfo, X1Carbon};
55
use crate::{
6-
Control as ControlTrait, MachineInfo as MachineInfoTrait, MachineMakeModel, MachineType,
6+
Control as ControlTrait, MachineInfo as MachineInfoTrait, MachineMakeModel, MachineState, MachineType,
77
SuspendControl as SuspendControlTrait, ThreeMfControl as ThreeMfControlTrait, ThreeMfTemporaryFile, Volume,
88
};
99

@@ -74,6 +74,10 @@ impl ControlTrait for X1Carbon {
7474
// TODO: fix this
7575
true
7676
}
77+
78+
async fn state(&self) -> Result<MachineState> {
79+
Ok(MachineState::Unknown)
80+
}
7781
}
7882

7983
impl SuspendControlTrait for X1Carbon {

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use serde::{Deserialize, Serialize};
4343
pub use slicer::AnySlicer;
4444
pub use sync::SharedMachine;
4545
pub use traits::{
46-
Control, GcodeControl, GcodeSlicer, GcodeTemporaryFile, MachineInfo, MachineMakeModel, MachineType, SuspendControl,
47-
ThreeMfControl, ThreeMfSlicer, ThreeMfTemporaryFile,
46+
Control, GcodeControl, GcodeSlicer, GcodeTemporaryFile, MachineInfo, MachineMakeModel, MachineState, MachineType,
47+
SuspendControl, ThreeMfControl, ThreeMfSlicer, ThreeMfTemporaryFile,
4848
};
4949

5050
/// A specific file containing a design to be manufactured.

src/moonraker/control.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use moonraker::InfoResponse;
66
use super::Client;
77
use crate::{
88
Control as ControlTrait, GcodeControl as GcodeControlTrait, GcodeTemporaryFile, MachineInfo as MachineInfoTrait,
9-
MachineMakeModel, MachineType, SuspendControl as SuspendControlTrait, Volume,
9+
MachineMakeModel, MachineState, MachineType, SuspendControl as SuspendControlTrait, Volume,
1010
};
1111

1212
/// Information about the connected Moonraker-based printer.
@@ -64,6 +64,20 @@ impl ControlTrait for Client {
6464
async fn healthy(&self) -> bool {
6565
self.client.info().await.is_ok()
6666
}
67+
68+
async fn state(&self) -> Result<MachineState> {
69+
let status = self.client.status().await?;
70+
71+
Ok(match status.print_stats.state.as_str() {
72+
"printing" => MachineState::Running,
73+
"standby" => MachineState::Idle,
74+
"paused" => MachineState::Paused,
75+
"complete" => MachineState::Complete,
76+
"cancelled" => MachineState::Complete,
77+
"error" => MachineState::Failed(Some(status.print_stats.message.to_owned())),
78+
_ => MachineState::Unknown,
79+
})
80+
}
6781
}
6882

6983
impl SuspendControlTrait for Client {

src/noop.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use anyhow::Result;
55

66
use crate::{
77
Control as ControlTrait, GcodeControl as GcodeControlTrait, GcodeTemporaryFile, MachineInfo as MachineInfoTrait,
8-
MachineMakeModel, MachineType, SuspendControl as SuspendControlTrait, ThreeMfControl as ThreeMfControlTrait,
9-
ThreeMfTemporaryFile, Volume,
8+
MachineMakeModel, MachineState, MachineType, SuspendControl as SuspendControlTrait,
9+
ThreeMfControl as ThreeMfControlTrait, ThreeMfTemporaryFile, Volume,
1010
};
1111

1212
/// Noop-machine will no-op, well, everything.
@@ -70,6 +70,10 @@ impl ControlTrait for Noop {
7070
async fn healthy(&self) -> bool {
7171
true
7272
}
73+
74+
async fn state(&self) -> Result<MachineState> {
75+
Ok(MachineState::Unknown)
76+
}
7377
}
7478

7579
impl SuspendControlTrait for Noop {

src/server/endpoints.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66

77
use super::{Context, CorsResponseOk};
8-
use crate::{AnyMachine, Control, DesignFile, MachineInfo, MachineMakeModel, MachineType, TemporaryFile, Volume};
8+
use crate::{
9+
AnyMachine, Control, DesignFile, MachineInfo, MachineMakeModel, MachineState, MachineType, TemporaryFile, Volume,
10+
};
911

1012
/// Return the OpenAPI schema in JSON format.
1113
#[endpoint {
@@ -68,6 +70,10 @@ pub struct MachineInfoResponse {
6870
/// What "close" means is up to you!
6971
pub max_part_volume: Option<Volume>,
7072

73+
/// Status of the printer -- be it printing, idle, or unreachable. This
74+
/// may dictate if a machine is capable of taking a new job.
75+
pub state: MachineState,
76+
7177
/// Additional, per-machine information which is specific to the
7278
/// underlying machine type.
7379
pub extra: Option<ExtraMachineInfoResponse>,
@@ -83,6 +89,7 @@ impl MachineInfoResponse {
8389
make_model: machine_info.make_model(),
8490
machine_type: machine_info.machine_type(),
8591
max_part_volume: machine_info.max_part_volume(),
92+
state: machine.state().await?,
8693
extra: match machine {
8794
AnyMachine::Moonraker(_) => Some(ExtraMachineInfoResponse::Moonraker {}),
8895
AnyMachine::Usb(_) => Some(ExtraMachineInfoResponse::Usb {}),

src/sync.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1+
use crate::{Control, MachineState};
12
use std::sync::Arc;
2-
33
use tokio::sync::Mutex;
44

5-
use crate::Control;
6-
75
/// Wrapper around an `Arc<Mutex<Control>>`, which helpfully will handle
86
/// the locking to expose a [Control] without the caller having to care
97
/// that this is a shared handle.
@@ -42,4 +40,7 @@ where
4240
async fn healthy(&self) -> bool {
4341
self.0.lock().await.healthy().await
4442
}
43+
async fn state(&self) -> Result<MachineState, Self::Error> {
44+
self.0.lock().await.state().await
45+
}
4546
}

0 commit comments

Comments
 (0)