initial commit after modification of prusatool-rs to make libraryish
This commit is contained in:
commit
ea6fd69abc
4 changed files with 291 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "prusalib-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.23", features = ["blocking"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.143"
|
||||
thiserror = "2.0.16"
|
||||
toml = "0.9.5"
|
||||
uuid = { version = "1.18.1", features = ["v4"] }
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
mod prusa;
|
||||
277
src/prusa.rs
Normal file
277
src/prusa.rs
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
use core::time::Duration;
|
||||
use std::fs;
|
||||
use thiserror::Error;
|
||||
use serde::Deserialize;
|
||||
use reqwest::blocking::Client;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Telemetry {
|
||||
#[serde(rename = "temp-bed")]
|
||||
temp_bed: f32,
|
||||
#[serde(rename = "temp-nozzle")]
|
||||
temp_nozzle: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct State {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Info {
|
||||
telemetry: Telemetry,
|
||||
// temperature: Temperature,
|
||||
state: State,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct StorageInfo {
|
||||
storage_list: Vec<Storage>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Storage {
|
||||
path: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PrusaConfig {
|
||||
api_key: String,
|
||||
ip_addr: String,
|
||||
}
|
||||
|
||||
pub struct Prusa {
|
||||
config: PrusaConfig,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl Prusa {
|
||||
pub fn new(config: PrusaConfig) -> Self {
|
||||
Self {
|
||||
config,
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn new_from_file(filepath: &str) -> Result<Self, Error> {
|
||||
let file_data = fs::read_to_string(filepath)?;
|
||||
let config: PrusaConfig = toml::from_str(&file_data)?;
|
||||
Ok(Self::new(config))
|
||||
}
|
||||
|
||||
|
||||
pub fn get_info(&self) -> Result<Info, Error> {
|
||||
let api_target = "/api/printer";
|
||||
let resp = self.client.get(format!("http://{}{api_target}", self.config.ip_addr))
|
||||
.header("X-Api-Key", &self.config.api_key)
|
||||
.send()?;
|
||||
let text = resp.text()?;
|
||||
// println!("{text}");
|
||||
let info: Info = serde_json::from_str(&text)?;
|
||||
// println!("{info:?}");
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
// TODO return this into a specific type
|
||||
pub fn get_storage_info(&self) -> Result<StorageInfo, Error> {
|
||||
let api_target = "/api/v1/storage";
|
||||
let resp = self.client.get(format!("http://{}{api_target}", self.config.ip_addr))
|
||||
.header("X-Api-Key", &self.config.api_key)
|
||||
.send()?;
|
||||
let text = resp.text()?;
|
||||
// println!("{text}");
|
||||
let info: StorageInfo = serde_json::from_str(&text)?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
// Prusalink load file schema:
|
||||
// content:
|
||||
// application/octet-stream:
|
||||
// schema:
|
||||
// type: string
|
||||
// format: binary
|
||||
|
||||
|
||||
// Need parameters
|
||||
/*
|
||||
parameters:
|
||||
- in: header
|
||||
name: Content-Length
|
||||
description: Length of file to upload
|
||||
schema:
|
||||
type: integer
|
||||
example: 101342
|
||||
- in: header
|
||||
name: Content-Type
|
||||
description: Type of uploaded media
|
||||
schema:
|
||||
type: string
|
||||
default: application/octet-stream
|
||||
- in: header
|
||||
name: Print-After-Upload
|
||||
description: Whether to start printing the file after upload
|
||||
schema:
|
||||
type: string
|
||||
description: ?0=False, ?1=True, according RFC8941/3.3.6
|
||||
enum: [ "?0", "?1" ]
|
||||
default: "?0"
|
||||
- in: header
|
||||
name: Overwrite
|
||||
description: Whether to overwrite already existing files
|
||||
schema:
|
||||
type: string
|
||||
description: ?0=False, ?1=True, according RFC8941/3.3.6
|
||||
enum: ["?0", "?1"]
|
||||
default: "?0"
|
||||
*/
|
||||
|
||||
// TODO: async
|
||||
pub fn try_load_file(&self, filepath: &str) -> Result<Uuid, Error> {
|
||||
let data = fs::read(filepath)?;
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
// TODO: Allow storage selection
|
||||
// For now, assume that we're using only USB:
|
||||
let api_target = format!("/api/v1/files/usb/{uuid}.bgcode");
|
||||
let resp = self.client.put(format!("http://{}{api_target}", self.config.ip_addr))
|
||||
.header("X-Api-Key", &self.config.api_key)
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.header("Content-Length", data.len())
|
||||
.header("Print-After-Upload", "0")
|
||||
.header("Overwrite", "0")
|
||||
.body(data)
|
||||
.timeout(Duration::from_secs(60))
|
||||
.send()?;
|
||||
let text = resp.text()?;
|
||||
println!("{text}");
|
||||
|
||||
// todo!("Implement UUID Handling");
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
// /api/v1/files/{storage}/{path}:
|
||||
//
|
||||
// post:
|
||||
/* summary: Start print of file if there's no print job running
|
||||
description: Body is ignored
|
||||
requestBody:
|
||||
required: false
|
||||
content: {}
|
||||
responses:
|
||||
204:
|
||||
description: No Content
|
||||
401:
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
404:
|
||||
$ref: "#/components/responses/NotFound"
|
||||
409:
|
||||
$ref: "#/components/responses/Conflict"
|
||||
head:
|
||||
summary: file presence and state check
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
headers:
|
||||
Read-Only:
|
||||
description: Whether the file or storage is read-only
|
||||
required: true
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
Currently-Printed:
|
||||
description: Whether this file is currently being printed
|
||||
required: true
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
*/
|
||||
|
||||
// TODO: async
|
||||
pub fn try_print_file(&self, id: &Uuid) -> Result<(), Error> {
|
||||
// /api/v1/files/{storage}/{path}
|
||||
let api_target = format!("/api/v1/files/usb/{id}.bgcode");
|
||||
let resp = self.client.post(format!("http://{}{api_target}", self.config.ip_addr))
|
||||
.header("X-Api-Key", &self.config.api_key)
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.send()?;
|
||||
let text = resp.text()?;
|
||||
println!("{text}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// TODO: poorly named, should be something else so as not to confuse with print operations
|
||||
pub fn print_info(&self) -> Result<(), Error> {
|
||||
let info = self.get_info()?;
|
||||
println!("\n--------------------");
|
||||
println!("STATE:\t{}", info.state.text);
|
||||
println!("NOZZLE:\t{}", info.telemetry.temp_nozzle);
|
||||
println!("BED:\t{}", info.telemetry.temp_bed);
|
||||
println!("--------------------\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_storage_info(&self) -> Result<(), Error> {
|
||||
let info = self.get_storage_info()?;
|
||||
for (index, storage) in info.storage_list.iter().enumerate() {
|
||||
println!("\n------------------");
|
||||
println!("STORAGE: {index}");
|
||||
println!(" PATH:\t{}", storage.path);
|
||||
println!(" NAME:\t{}", storage.name);
|
||||
println!("--------------------\n");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("File error")]
|
||||
FileError(#[from] std::io::Error),
|
||||
#[error("Toml Parse Error")]
|
||||
TomlError(#[from] toml::de::Error),
|
||||
#[error("HTTP(S) Error")]
|
||||
HttpError(#[from] reqwest::Error),
|
||||
#[error("Serde JSON Error")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
|
||||
// fn main() -> Result<(), Error> {
|
||||
// let cli = Cli::parse();
|
||||
// let file_data = fs::read_to_string(cli.printer_config)?;
|
||||
// let config: Config = toml::from_str(&file_data)?;
|
||||
// // println!("{:?}", config);
|
||||
//
|
||||
// match config.printer.model.as_str() {
|
||||
// "prusa-mk4" => {
|
||||
// let prusa = Prusa::new(&config.printer.api_key, &config.printer.ip_addr);
|
||||
// match cli.command {
|
||||
// Command::Info => {
|
||||
// prusa.print_info()?;
|
||||
// prusa.print_storage_info()?;
|
||||
// },
|
||||
// // Should generate UUID for the filename:
|
||||
// Command::Load {filepath, print_immediately} => {
|
||||
// let uuid = prusa.try_load_file(&filepath)?;
|
||||
// if print_immediately {
|
||||
// prusa.try_print_file(&uuid)?;
|
||||
// }
|
||||
// println!("Loaded as UUID:\n{uuid}");
|
||||
// },
|
||||
// Command::Print {file_id} => prusa.try_print_file(&file_id)?,
|
||||
// }
|
||||
// }
|
||||
// _ => println!("Unrecognized printer type")
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
Loading…
Add table
Add a link
Reference in a new issue