Idea for remote monitoring of 3d prints?

I go on call on Tuesday, so I probably won’t be able to work on this for a couple of weeks. I’m in the fablab today poking around on the API and seeing what data I can get

1 Like

Okay, using the insomnia client and bearer auth I was able to get into the api. I guess at this point I can try building out a client library based off of the documentation? My language of choice is rust, but I am quite comfortable with typescript/javascript as well.

I also guess there’s also the question of what exactly do we want to do with this data? I supposed we could have a client sitting on the MakeICT network that would periodically poll each printer to check in on the status and send that off somewhere? I think someone mentioned earlier about uploading files to the makeICT wiki. I think if we wanted to take that approach, we could try to create an API that will generate a JPEG of the printer statuses and upload that periodically to the wiki. Just spitballing here

3 Likes

Took some time to write a quick and dirty api client in rust. I’m still working out some kinks with serialization since the api is inconsistent in places, but I think it’s a good start.
I’m not married to using rust, but I noticed that there were no crates for octoprint available on crates.io, so I thought I might as well create one
GitHub - ThatNerdUKnow/octoprint

2 Likes

I managed to authenticate and send an http request to octoprint using the quick rust client I built, but i’m still having serialization problems. This is stemming from that the ultimaker is offline, and the documentation from octoprint lies a little bit about the multiplicity of some of the properties on the data models. Pretty much everything is null when the printer is offline. This is a problem because the structs I created are expecting there to be a value there and it’s encountering null

For example,

pub struct JobInfo {
    /// The file that is the target of the current print job
    pub file: FileInfo,
    /// The estimated print time for the file, in seconds
    #[serde(rename = "estimatedPrintTime")]
    pub estimated_print_time: Option<f64>,
    /// The print time of the last print of the file, in seconds
    #[serde(rename = "lastPrintTime")]
    pub last_print_time: Option<f64>,
    /// Information regarding the estimated filament usage of the print job
    pub filament: Option<Filament>,
}

You may notice that some of these types have the Option type around them, like last_print_time. What this tells the compiler is that it’s okay if this particular field is null. I took guidance using the multiplicity property on the data model from the documentation to try and tell what types should be optional while which others are not. I’d still like to preserve this multiplicity of these types by not wrapping every single field in an Option, but I think i’ll have to look more into the documentation for the serialization library to see if there’s anything I can do about it. For example, I’d love to just be able to take this type, returned by one of the REST endpoints…


#[derive(Serialize, Deserialize, Debug)]
pub struct JobInformationResponse {
    /// Information regarding the target of the current print job
    job: JobInfo,
    /// Information regarding the progress of the current print job
    progress: ProgressInfo,
    /// A textual representation of the current state of the job
    /// or connection. e.g. "Operational", "Printing", "Pausing",
    /// "Paused", "Cancelling", "Error", "Offline", "Offline after error",
    /// "Opening serial connection" ... - please note that this list is not exhaustive!
    state: String,
    /// Any error message for the job or connection. Only set if there has been an error
    error: Option<String>,
}

and just wrap JobInfo and ProgressInfo in an option. Unfortunately it doesn’t really work that way. Octoprint is returning something like this

{
"job": {
"foo": null
},
...
}

whereas if I were to wrap these top level objects in Options, only this response would serialize

{
"job": null
}

The difference is subtle, but my serializer is very picky about these things. I imagine there’s some override for this behavior, I just have to find it in the documentation for serde

1 Like

I don’t pretend to know much of anything about Rust or OctoPrint, but this looks relevant:

Provides a Rust type and serialization/deserialization implementations for values that can be represented by 3 states: missing, present but null and present with a value.

pub enum Field<T> {
    Missing,
    Present(Option<T>),
}

This can be useful when using JSON or other formats whereby the default in serde is to treat missing keys and null values as the same, deserializing them to Option::None. A similar problem exists when serializing as you have to create your own 3-state enum and tag each field with skip_serializing_if in order to not serialize a field.

This can be problematic with APIs where partial objects or diffs are provided and you don’t know whether you need to set the value to null or not update value should be left alone.

By using Field you are able to distinguish between null values and missing keys and get serde to behave correctly in these scenarios.

1 Like

That’s not quite what I was looking for. Serde is able to tell that a field is None if it’s either null or missing entirely… I’m pretty sure. The problem is that technically an object with all null properties is still technically an object. I was advised to represent the types exposed by the API as an enum, where each different state is represented. I’ll look more into it but it looks like a good lead

2 Likes

So I’d have to create an enum where one state is online (data exists) the other state is error and the third state is offline

2 Likes

Here’s what I ended up with for the JobInformationResponse enum

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum JobInformationResponse {
    Online {
        /// Information regarding the target of the current print job
        job: JobInfo,
        /// Information regarding the progress of the current print job
        progress: ProgressInfo,
        /// A textual representation of the current state of the job
        /// or connection. e.g. "Operational", "Printing", "Pausing",
        /// "Paused", "Cancelling", "Error", "Offline", "Offline after error",
        /// "Opening serial connection" ... - please note that this list is not exhaustive!
        state: String,
    },
    Offline {
        /// Same as above state, but we'll assume if other props don't deserialize correctly, that the printer is offline
        state: String,
    },
    Error {
        /// Any error message for the job or connection. Only set if there has been an error
        error: Option<String>,
    },
    Unknown,
}

So here i’m using an un-tagged enum, which means that serde will try to discrimminate enum variants depending on which variant serializes correctly first. For sanity’s sake, I included an Unknown variant which should be a good catch-all variant in the event I missed something or made a bad assumption on one of these enum variants, although it might be more correct to allow a serialization attempt to fail rather than throw our hands in the air and use an “any” variant. Now that I think about it Unknown would match pretty much anything that the api spits out which might come back to bite me depending on how serde decides which order to serialize enum variants using the untagged discrimminator mode

2 Likes

Being Valentine’s Day, I won’t be in tonight. Don’t wait on me if you also want to write some code for this. I’m assuming any of the common open-source languages can be used so if you want to build on what @ThatNerdUKnow has written or if you want to try another language - feel free. As a maker-space I think it’s a good opportunity to try multiple versions while building the prototype.

2 Likes

I’d say to consider the rust library a side-project, as there are plenty of other client libraries in other languages that are ready to go. So if we’re geared towards actually delivering an application, using one of those would probably be the best path forward. Also in the spirit of collaboration, I don’t think rust might not be the best choice considering its popularity compared to javascript or python.

2 Likes

I’ll be in the Fab lab today probably after lunch. While I was on-call I was still working on the rust API client and writing integration tests and such. Hopefully I got it right

2 Likes

Yeah, I would agree that Rust probably wouldn’t be my first choice just because it would be harder to find people familiar enough to maintain it. If you take a look at our GitHub we do try to stick to more popular languages. Python is generally my go-to, but I’m not a “real” software developer. :smile:

@chris_w and I were talking about this last week. He’s looking at the possibility of connecting the printers to OctoEverywhere and then using their webhook feature to send printer statuses to a custom website. That would eliminate the need for a local agent connecting to all of the printers, but would be much less flexible.

How did your testing go on Sunday? I I’ll be in the Fab Lab tonight 6:30 - 8:30 if anyone wants to stop by and chat.

1 Like

When I had my 3D printer at home, I had a fairly cheap USB web-cam that I just interfaced with my laptop that I used to drive the printer. It’s easy, but I see that outside access would be a challenge due to internet security of MakeICT - which I fully understand.

However, there are ways… When I have more experience with the group I can probably contribute some ideas.

Randy

2 Likes

@chris_w
Just realized that OctoEverywhere has a pretty harsh limit on webhook calls for the free account:

Standard accounts are limited to 3 webhook notifications a day; all supporter roles get unlimited notifications. Learn More

So if we did end up using that we would have to pay at least $3/month for unlimited notifications. Which isn’t terrible, but is something to consider. Unlimited streaming for webcams would cost $9/month if we wanted to set that up.

3 Likes

octo4a - Run OctoPrint on Android

  • Quick and easy octoprint installation.
  • Printer connection via USB OTG. Thanks to our custom USB driver you can use octoprint even on phones without root access.
  • Built-in camera support. You can use the built-in camera in your phone to see the progress of your 3D prints, instead of buying a separate module. The app also supports octolapse.
  • SSH support. You can easily log-in via ssh and customize your octoprint installation.
1 Like

Thanks for the heads up @Christian. I think for the MVP (v1), we can use the OctoEverywhere webhooks ($3/month) to send data to the endpoint. If there’s no budget for this service, I am happy to cover the monthly cost. For v2 we can write a script to implement the same webhook functionality, and thus bypass using OctoEverywhere anywhere.

I’ll be by for the 3D printer group meeting tomorrow (albeit not until 7:30~8:00). I can show you the endpoint and maybe we can finish setting up OE on the rest of the printers.

1 Like

Yeah, I think that’s a good plan. Hopefully we can meet up with @ThatNerdUKnow at some point and take a look at the API stuff together.

1 Like

@Christian @ThatNerdUKnow @xrunner (and anyone else interested in joining to help)
I’ll be at the 3d printer group meeting tomorrow evening and plan to work on a script that will run locally at makeICT, retrieving printer status info from each of the Octoprint instances and send it to our external endpoint to display. Once we get this working we can cancel the OctoEverywhere subscription.

2 Likes

Sounds good. I will be there.

1 Like

I plan on swinging by tomorrow.
Has anyone mentioned cameras?
I know i have a couple or more.