Working remote print integration
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1146,6 +1146,7 @@ name = "common"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nalgebra 0.32.6",
|
"nalgebra 0.32.6",
|
||||||
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
4
TODO.md
4
TODO.md
@@ -48,3 +48,7 @@
|
|||||||
- [x] Use egui_dock to get a more clean look
|
- [x] Use egui_dock to get a more clean look
|
||||||
- [x] Align to bed button
|
- [x] Align to bed button
|
||||||
- [x] Define all crate versions in workspace toml
|
- [x] Define all crate versions in workspace toml
|
||||||
|
- [ ] MQTT commands use refs to strings
|
||||||
|
- [ ] Ask for filename when sending to printer
|
||||||
|
- [ ] Printer scanning (UDP broadcast)
|
||||||
|
- [ ] Verify printer capabilities before uploading / printing
|
||||||
|
@@ -5,3 +5,4 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra.workspace = true
|
nalgebra.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
|
||||||
use crate::config::SliceConfig;
|
use crate::config::SliceConfig;
|
||||||
|
|
||||||
pub struct SliceResult<'a, Layer> {
|
pub struct SliceResult<'a, Layer> {
|
||||||
@@ -26,9 +28,27 @@ pub fn human_duration(duration: Duration) -> String {
|
|||||||
format!("{:}ms", ms)
|
format!("{:}ms", ms)
|
||||||
} else if ms < 60_000.0 {
|
} else if ms < 60_000.0 {
|
||||||
format!("{:.2}s", ms / 1000.0)
|
format!("{:.2}s", ms / 1000.0)
|
||||||
} else {
|
} else if ms < 3_600_000.0 {
|
||||||
let minutes = ms / 60_000.0;
|
let minutes = ms / 60_000.0;
|
||||||
let seconds = (minutes - minutes.floor()) * 60.0;
|
let seconds = (minutes - minutes.floor()) * 60.0;
|
||||||
format!("{:.0}m {:.2}s", minutes.floor(), seconds)
|
format!("{:.0}m {:.2}s", minutes.floor(), seconds)
|
||||||
|
} else {
|
||||||
|
let hours = ms / 3_600_000.0;
|
||||||
|
let minutes = (hours - hours.floor()) * 60.0;
|
||||||
|
let seconds = (minutes - minutes.floor()) * 60.0;
|
||||||
|
format!(
|
||||||
|
"{:.0}h {:.0}m {:.2}s",
|
||||||
|
hours.floor(),
|
||||||
|
minutes.floor(),
|
||||||
|
seconds
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn random_string(len: usize) -> String {
|
||||||
|
rand::thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(len)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
@@ -169,7 +169,7 @@ impl Default for App {
|
|||||||
let surface = dock_state.main_surface_mut();
|
let surface = dock_state.main_surface_mut();
|
||||||
let [_old_node, new_node] = surface.split_left(NodeIndex::root(), 0.20, vec![Tab::Models]);
|
let [_old_node, new_node] = surface.split_left(NodeIndex::root(), 0.20, vec![Tab::Models]);
|
||||||
let [_old_node, new_node] = surface.split_below(new_node, 0.5, vec![Tab::SliceConfig]);
|
let [_old_node, new_node] = surface.split_below(new_node, 0.5, vec![Tab::SliceConfig]);
|
||||||
surface.split_below(new_node, 0.5, vec![Tab::Workspace]);
|
surface.split_below(new_node, 0.5, vec![Tab::Workspace, Tab::RemotePrint]);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
dock_state,
|
dock_state,
|
||||||
|
@@ -5,12 +5,17 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use common::misc::random_string;
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use remote_send::{
|
use remote_send::{
|
||||||
commands::DisconnectCommand, http_server::HttpServer, mqtt::MqttServer, mqtt_server::Mqtt,
|
commands::{DisconnectCommand, StartPrinting, UploadFile},
|
||||||
status::FullStatusData, Response,
|
http_server::HttpServer,
|
||||||
|
mqtt::MqttServer,
|
||||||
|
mqtt_server::Mqtt,
|
||||||
|
status::FullStatusData,
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RemotePrint {
|
pub struct RemotePrint {
|
||||||
@@ -125,4 +130,38 @@ impl RemotePrint {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn upload(&self, mainboard_id: &str, data: Arc<Vec<u8>>) -> Result<()> {
|
||||||
|
let services = self.services.as_ref().unwrap();
|
||||||
|
|
||||||
|
let filename = format!("{}.goo", random_string(8));
|
||||||
|
services.http.add_file(&filename, data.clone());
|
||||||
|
|
||||||
|
services
|
||||||
|
.mqtt
|
||||||
|
.send_command(
|
||||||
|
&mainboard_id,
|
||||||
|
UploadFile::new(filename, services.http_port, &data),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(&self, mainboard_id: &str, filename: &str) -> Result<()> {
|
||||||
|
let services = self.services.as_ref().unwrap();
|
||||||
|
|
||||||
|
services
|
||||||
|
.mqtt
|
||||||
|
.send_command(
|
||||||
|
&mainboard_id,
|
||||||
|
StartPrinting {
|
||||||
|
filename: filename.to_owned(),
|
||||||
|
start_layer: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::atomic::Ordering;
|
use std::{sync::atomic::Ordering, time::Duration};
|
||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
use common::misc::human_duration;
|
||||||
use egui::{vec2, Align, Context, Grid, Layout, ProgressBar, Separator, TextEdit, Ui};
|
use egui::{vec2, Align, Context, Grid, Layout, ProgressBar, Separator, TextEdit, Ui};
|
||||||
use remote_send::status::{FileTransferStatus, PrintInfoStatus};
|
use remote_send::status::{FileTransferStatus, PrintInfoStatus};
|
||||||
|
|
||||||
@@ -58,6 +59,59 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let status = client.status.lock();
|
||||||
|
|
||||||
|
let print_info = &status.print_info;
|
||||||
|
let printing = !matches!(
|
||||||
|
print_info.status,
|
||||||
|
PrintInfoStatus::None | PrintInfoStatus::Complete
|
||||||
|
);
|
||||||
|
if printing {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Printing ");
|
||||||
|
ui.monospace(&print_info.filename);
|
||||||
|
ui.label(format!("({:?})", print_info.status));
|
||||||
|
});
|
||||||
|
|
||||||
|
let eta = human_duration(Duration::from_millis(
|
||||||
|
(print_info.total_ticks - print_info.current_ticks) as u64,
|
||||||
|
));
|
||||||
|
ui.label(format!("ETA: {eta}"));
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
ProgressBar::new(print_info.current_layer as f32 / print_info.total_layer as f32)
|
||||||
|
.text(format!(
|
||||||
|
"{}/{}",
|
||||||
|
print_info.current_layer, print_info.total_layer
|
||||||
|
))
|
||||||
|
.desired_width(ui.available_width()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_transfer = &status.file_transfer_info;
|
||||||
|
if file_transfer.status == FileTransferStatus::None && file_transfer.file_total_size != 0 {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Transferring ");
|
||||||
|
ui.monospace(&file_transfer.filename);
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
ui.add(
|
||||||
|
ProgressBar::new(
|
||||||
|
file_transfer.download_offset as f32 / file_transfer.file_total_size as f32,
|
||||||
|
)
|
||||||
|
.desired_width(ui.available_width()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if file_transfer.status == FileTransferStatus::Done && !printing {
|
||||||
|
ui.label("File transfer complete.");
|
||||||
|
if ui.button("Print").clicked() {
|
||||||
|
app.remote_print
|
||||||
|
.print(&attributes.mainboard_id, &file_transfer.filename)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Grid::new(format!("printer_{}", attributes.mainboard_id))
|
Grid::new(format!("printer_{}", attributes.mainboard_id))
|
||||||
.num_columns(2)
|
.num_columns(2)
|
||||||
.striped(true)
|
.striped(true)
|
||||||
@@ -94,39 +148,6 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
|||||||
ui.monospace(&last_update.format("%Y-%m-%d %H:%M:%S").to_string());
|
ui.monospace(&last_update.format("%Y-%m-%d %H:%M:%S").to_string());
|
||||||
});
|
});
|
||||||
|
|
||||||
let status = client.status.lock();
|
|
||||||
|
|
||||||
let print_info = &status.print_info;
|
|
||||||
if !matches!(
|
|
||||||
print_info.status,
|
|
||||||
PrintInfoStatus::None | PrintInfoStatus::Complete
|
|
||||||
) {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Printing ");
|
|
||||||
ui.monospace(&print_info.filename);
|
|
||||||
ui.label(format!(". ({:?})", print_info.status));
|
|
||||||
});
|
|
||||||
ui.add(
|
|
||||||
ProgressBar::new(print_info.current_layer as f32 / print_info.total_layer as f32)
|
|
||||||
.desired_width(ui.available_width()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_transfer = &status.file_transfer_info;
|
|
||||||
if file_transfer.status == FileTransferStatus::None && file_transfer.file_total_size != 0 {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Transferring ");
|
|
||||||
ui.monospace(&file_transfer.filename);
|
|
||||||
ui.label(".");
|
|
||||||
});
|
|
||||||
ui.add(
|
|
||||||
ProgressBar::new(
|
|
||||||
file_transfer.download_offset as f32 / file_transfer.file_total_size as f32,
|
|
||||||
)
|
|
||||||
.desired_width(ui.available_width()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if i + 1 != printers.len() {
|
if i + 1 != printers.len() {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
use std::{fs::File, io::Write};
|
use std::{fs::File, io::Write, sync::Arc};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
style::HandleShape, Align, Context, DragValue, Layout, ProgressBar, RichText, Sense, Slider,
|
style::HandleShape, text::LayoutJob, Align, Context, DragValue, FontId, FontSelection, Layout,
|
||||||
Vec2, Window,
|
ProgressBar, RichText, Sense, Slider, Style, TextFormat, Vec2, WidgetInfo, WidgetText, Window,
|
||||||
};
|
};
|
||||||
use egui_wgpu::Callback;
|
use egui_wgpu::Callback;
|
||||||
use goo_format::LayerDecoder;
|
use goo_format::LayerDecoder;
|
||||||
@@ -47,15 +47,46 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
|
|
||||||
ui.with_layout(Layout::default().with_cross_align(Align::Max), |ui| {
|
ui.with_layout(Layout::default().with_cross_align(Align::Max), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Send to Printer").clicked() {
|
ui.add_enabled_ui(app.remote_print.is_initialized(), |ui| {
|
||||||
let result = app.slice_operation.as_ref().unwrap().result();
|
ui.menu_button("Send to Printer", |ui| {
|
||||||
|
let mqtt = app.remote_print.mqtt();
|
||||||
|
for printer in app.remote_print.printers().iter() {
|
||||||
|
let client = mqtt.get_client(&printer.mainboard_id);
|
||||||
|
|
||||||
|
let mut layout_job = LayoutJob::default();
|
||||||
|
RichText::new(&format!("{} ", client.attributes.name))
|
||||||
|
.append_to(
|
||||||
|
&mut layout_job,
|
||||||
|
&Style::default(),
|
||||||
|
FontSelection::Default,
|
||||||
|
Align::LEFT,
|
||||||
|
);
|
||||||
|
RichText::new(&client.attributes.mainboard_id)
|
||||||
|
.monospace()
|
||||||
|
.append_to(
|
||||||
|
&mut layout_job,
|
||||||
|
&Style::default(),
|
||||||
|
FontSelection::Default,
|
||||||
|
Align::LEFT,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ui.button(layout_job).clicked() {
|
||||||
|
let result =
|
||||||
|
app.slice_operation.as_ref().unwrap().result();
|
||||||
let result = result.as_ref().unwrap();
|
let result = result.as_ref().unwrap();
|
||||||
|
|
||||||
let mut serializer = DynamicSerializer::new();
|
let mut serializer = DynamicSerializer::new();
|
||||||
result.goo.serialize(&mut serializer);
|
result.goo.serialize(&mut serializer);
|
||||||
let data = serializer.into_inner();
|
let data = Arc::new(serializer.into_inner());
|
||||||
save_complete = true;
|
save_complete = true;
|
||||||
|
|
||||||
|
app.remote_print
|
||||||
|
.upload(&printer.mainboard_id, data)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if ui.button("Save").clicked() {
|
if ui.button("Save").clicked() {
|
||||||
let result = app.slice_operation.as_ref().unwrap().result();
|
let result = app.slice_operation.as_ref().unwrap().result();
|
||||||
|
@@ -9,6 +9,7 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard};
|
use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard};
|
||||||
|
use serde_json::Value;
|
||||||
use soon::Soon;
|
use soon::Soon;
|
||||||
use tracing::{info, trace, warn};
|
use tracing::{info, trace, warn};
|
||||||
|
|
||||||
@@ -111,16 +112,15 @@ impl MqttHandler for Mqtt {
|
|||||||
|
|
||||||
if let Some(board_id) = packet.topic.strip_prefix("/sdcp/status/") {
|
if let Some(board_id) = packet.topic.strip_prefix("/sdcp/status/") {
|
||||||
let status = serde_json::from_slice::<Response<StatusData>>(&packet.data)?;
|
let status = serde_json::from_slice::<Response<StatusData>>(&packet.data)?;
|
||||||
|
trace!("Got status from `{board_id}`: {status:?}");
|
||||||
|
|
||||||
let clients = self.clients.write();
|
let clients = self.clients.write();
|
||||||
let client = clients.get(board_id).unwrap();
|
let client = clients.get(board_id).unwrap();
|
||||||
*client.status.lock() = status.data.status;
|
*client.status.lock() = status.data.status;
|
||||||
client.last_update.store(epoch(), Ordering::Relaxed);
|
client.last_update.store(epoch(), Ordering::Relaxed);
|
||||||
} else if let Some(board_id) = packet.topic.strip_prefix("/sdcp/response/") {
|
} else if let Some(board_id) = packet.topic.strip_prefix("/sdcp/response/") {
|
||||||
trace!(
|
let json = serde_json::from_slice::<Value>(&packet.data)?;
|
||||||
"Got command response from `{board_id}`: {}",
|
trace!("Got command response from `{board_id}`: {json}");
|
||||||
String::from_utf8_lossy(&packet.data)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -84,15 +84,17 @@ pub struct FileTransferInfo {
|
|||||||
pub enum CurrentStatus {
|
pub enum CurrentStatus {
|
||||||
Ready = 0,
|
Ready = 0,
|
||||||
Busy = 1,
|
Busy = 1,
|
||||||
|
TransferringFile = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize_repr)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize_repr)]
|
||||||
pub enum PrintInfoStatus {
|
pub enum PrintInfoStatus {
|
||||||
None = 0,
|
None = 0,
|
||||||
Exposure = 2,
|
Unknown = 1,
|
||||||
Retracting = 3,
|
Lowering = 2,
|
||||||
Lowering = 4,
|
Exposure = 3,
|
||||||
|
Retracting = 4,
|
||||||
Complete = 16,
|
Complete = 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user