initial commit after modification of prusatool-rs to make libraryish

This commit is contained in:
sigil-03 2025-09-11 17:31:53 -06:00
commit ea6fd69abc
4 changed files with 291 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

12
Cargo.toml Normal file
View 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
View file

@ -0,0 +1 @@
mod prusa;

277
src/prusa.rs Normal file
View 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(())
// }