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"
|
||||
dependencies = [
|
||||
"nalgebra 0.32.6",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
4
TODO.md
4
TODO.md
@@ -48,3 +48,7 @@
|
||||
- [x] Use egui_dock to get a more clean look
|
||||
- [x] Align to bed button
|
||||
- [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]
|
||||
nalgebra.workspace = true
|
||||
rand.workspace = true
|
||||
|
@@ -1,5 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
|
||||
use crate::config::SliceConfig;
|
||||
|
||||
pub struct SliceResult<'a, Layer> {
|
||||
@@ -26,9 +28,27 @@ pub fn human_duration(duration: Duration) -> String {
|
||||
format!("{:}ms", ms)
|
||||
} else if ms < 60_000.0 {
|
||||
format!("{:.2}s", ms / 1000.0)
|
||||
} else {
|
||||
} else if ms < 3_600_000.0 {
|
||||
let minutes = ms / 60_000.0;
|
||||
let seconds = (minutes - minutes.floor()) * 60.0;
|
||||
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 [_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]);
|
||||
surface.split_below(new_node, 0.5, vec![Tab::Workspace]);
|
||||
surface.split_below(new_node, 0.5, vec![Tab::Workspace, Tab::RemotePrint]);
|
||||
|
||||
Self {
|
||||
dock_state,
|
||||
|
@@ -5,12 +5,17 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use common::misc::random_string;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use tracing::info;
|
||||
|
||||
use remote_send::{
|
||||
commands::DisconnectCommand, http_server::HttpServer, mqtt::MqttServer, mqtt_server::Mqtt,
|
||||
status::FullStatusData, Response,
|
||||
commands::{DisconnectCommand, StartPrinting, UploadFile},
|
||||
http_server::HttpServer,
|
||||
mqtt::MqttServer,
|
||||
mqtt_server::Mqtt,
|
||||
status::FullStatusData,
|
||||
Response,
|
||||
};
|
||||
|
||||
pub struct RemotePrint {
|
||||
@@ -125,4 +130,38 @@ impl RemotePrint {
|
||||
|
||||
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 common::misc::human_duration;
|
||||
use egui::{vec2, Align, Context, Grid, Layout, ProgressBar, Separator, TextEdit, Ui};
|
||||
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))
|
||||
.num_columns(2)
|
||||
.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());
|
||||
});
|
||||
|
||||
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() {
|
||||
ui.separator();
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::{fs::File, io::Write};
|
||||
use std::{fs::File, io::Write, sync::Arc};
|
||||
|
||||
use egui::{
|
||||
style::HandleShape, Align, Context, DragValue, Layout, ProgressBar, RichText, Sense, Slider,
|
||||
Vec2, Window,
|
||||
style::HandleShape, text::LayoutJob, Align, Context, DragValue, FontId, FontSelection, Layout,
|
||||
ProgressBar, RichText, Sense, Slider, Style, TextFormat, Vec2, WidgetInfo, WidgetText, Window,
|
||||
};
|
||||
use egui_wgpu::Callback;
|
||||
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.horizontal(|ui| {
|
||||
if ui.button("Send to Printer").clicked() {
|
||||
let result = app.slice_operation.as_ref().unwrap().result();
|
||||
let result = result.as_ref().unwrap();
|
||||
ui.add_enabled_ui(app.remote_print.is_initialized(), |ui| {
|
||||
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 serializer = DynamicSerializer::new();
|
||||
result.goo.serialize(&mut serializer);
|
||||
let data = serializer.into_inner();
|
||||
save_complete = true;
|
||||
}
|
||||
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 mut serializer = DynamicSerializer::new();
|
||||
result.goo.serialize(&mut serializer);
|
||||
let data = Arc::new(serializer.into_inner());
|
||||
save_complete = true;
|
||||
|
||||
app.remote_print
|
||||
.upload(&printer.mainboard_id, data)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
let result = app.slice_operation.as_ref().unwrap().result();
|
||||
|
@@ -9,6 +9,7 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard};
|
||||
use serde_json::Value;
|
||||
use soon::Soon;
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
@@ -111,16 +112,15 @@ impl MqttHandler for Mqtt {
|
||||
|
||||
if let Some(board_id) = packet.topic.strip_prefix("/sdcp/status/") {
|
||||
let status = serde_json::from_slice::<Response<StatusData>>(&packet.data)?;
|
||||
trace!("Got status from `{board_id}`: {status:?}");
|
||||
|
||||
let clients = self.clients.write();
|
||||
let client = clients.get(board_id).unwrap();
|
||||
*client.status.lock() = status.data.status;
|
||||
client.last_update.store(epoch(), Ordering::Relaxed);
|
||||
} else if let Some(board_id) = packet.topic.strip_prefix("/sdcp/response/") {
|
||||
trace!(
|
||||
"Got command response from `{board_id}`: {}",
|
||||
String::from_utf8_lossy(&packet.data)
|
||||
);
|
||||
let json = serde_json::from_slice::<Value>(&packet.data)?;
|
||||
trace!("Got command response from `{board_id}`: {json}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@@ -84,15 +84,17 @@ pub struct FileTransferInfo {
|
||||
pub enum CurrentStatus {
|
||||
Ready = 0,
|
||||
Busy = 1,
|
||||
TransferringFile = 2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize_repr)]
|
||||
pub enum PrintInfoStatus {
|
||||
None = 0,
|
||||
Exposure = 2,
|
||||
Retracting = 3,
|
||||
Lowering = 4,
|
||||
Unknown = 1,
|
||||
Lowering = 2,
|
||||
Exposure = 3,
|
||||
Retracting = 4,
|
||||
Complete = 16,
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user