From 057afb0b2e61ba5809f846c7635ba7d6c9611470 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Fri, 9 Jan 2026 20:29:13 +0100 Subject: [PATCH 1/6] List machines in rest api --- machines/src/laser/act.rs | 4 +- machines/src/lib.rs | 11 ++- machines/src/machine_identification.rs | 35 ++++++++ server/src/app_state.rs | 21 ++++- server/src/rest/init.rs | 4 +- server/src/rest/mod.rs | 1 + server/src/rest/rest_api.rs | 107 +++++++++++++++++++++++++ 7 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 server/src/rest/rest_api.rs diff --git a/machines/src/laser/act.rs b/machines/src/laser/act.rs index 501dafc20..95e1d2663 100644 --- a/machines/src/laser/act.rs +++ b/machines/src/laser/act.rs @@ -41,7 +41,6 @@ impl MachineAct for LaserMachine { } fn act_machine_message(&mut self, msg: MachineMessage) { - tracing::info!("{:?}", msg); match msg { MachineMessage::SubscribeNamespace(namespace) => { self.namespace.namespace = Some(namespace); @@ -69,7 +68,8 @@ impl MachineAct for LaserMachine { /*Doesnt connect to any Machine so do nothing*/ { () - } + }, + MachineMessage::RequestValues(sender) => sender.send_blocking(crate::MachineValues { state: serde_json::Value::Null, live_values: serde_json::Value::Null }) } } } diff --git a/machines/src/lib.rs b/machines/src/lib.rs index 29d39a5eb..3961941c2 100644 --- a/machines/src/lib.rs +++ b/machines/src/lib.rs @@ -13,9 +13,8 @@ use machine_identification::{ use serde::Serialize; use smol::channel::{Receiver, Sender}; use socketioxide::extract::SocketRef; +use std::any::Any; use std::fmt::Debug; -use std::{any::Any, sync::Arc, time::Instant}; -pub mod analog_input_test_machine; pub mod aquapath1; #[cfg(not(feature = "mock-machine"))] pub mod buffer1; @@ -36,7 +35,6 @@ pub const MACHINE_WINDER_V1: u16 = 0x0002; pub const MACHINE_EXTRUDER_V1: u16 = 0x0004; pub const MACHINE_LASER_V1: u16 = 0x0006; pub const MACHINE_MOCK: u16 = 0x0007; -#[cfg(not(feature = "mock-machine"))] pub const MACHINE_BUFFER_V1: u16 = 0x0008; pub const MACHINE_AQUAPATH_V1: u16 = 0x0009; pub const MACHINE_WAGO_POWER_V1: u16 = 0x000A; @@ -276,6 +274,12 @@ pub trait MachineAct { fn act(&mut self, now: Instant); } +#[derive(Serialize, Debug, Clone)] +pub struct MachineValues { + pub state: serde_json::Value, + pub live_values: serde_json::Value, +} + // generic MachineMessage allows us to implement actions // to manage or mutate machines with simple messages sent to the Recv Channel of the given Machine, // which the machine itself will handle to avoid locking @@ -287,6 +291,7 @@ pub enum MachineMessage { HttpApiJsonRequest(serde_json::Value), ConnectToMachine(MachineConnection), DisconnectMachine(MachineConnection), + RequestValues(Sender), } pub trait MachineApi { diff --git a/machines/src/machine_identification.rs b/machines/src/machine_identification.rs index 7895ca7ec..8bc7f690e 100644 --- a/machines/src/machine_identification.rs +++ b/machines/src/machine_identification.rs @@ -41,6 +41,29 @@ impl MachineIdentification { pub const fn is_valid(&self) -> bool { self.vendor != 0 && self.machine != 0 } + + pub fn vendor_str(&self) -> String { + match self.vendor { + x if x == VENDOR_QITECH => "QiTech".to_string(), + _ => "N/A".to_string() + } + } + + pub fn slug(&self) -> String { + match self.machine { + x if x == MACHINE_WINDER_V1 => "winder_v1".to_string(), + x if x == MACHINE_EXTRUDER_V1 => "extruder_v1".to_string(), + x if x == MACHINE_LASER_V1 => "laser_v1".to_string(), + x if x == MACHINE_MOCK => "mock".to_string(), + x if x == MACHINE_AQUAPATH_V1 => "aquapath_v1".to_string(), + x if x == MACHINE_BUFFER_V1 => "buffer_v1".to_string(), + x if x == MACHINE_EXTRUDER_V2 => "extruder_v2".to_string(), + x if x == TEST_MACHINE => "test_machine".to_string(), + x if x == IP20_TEST_MACHINE => "ip20_test_machine".to_string(), + x if x == ANALOG_INPUT_TEST_MACHINE => "analog_input_test_machine".to_string(), + _ => "N/A".to_string() + } + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -149,6 +172,18 @@ use ethercat_hal::helpers::ethercrab_types::{ use ethercrab::MainDevice; use ethercrab::SubDeviceIdentity; +use crate::ANALOG_INPUT_TEST_MACHINE; +use crate::IP20_TEST_MACHINE; +use crate::MACHINE_AQUAPATH_V1; +use crate::MACHINE_BUFFER_V1; +use crate::MACHINE_EXTRUDER_V1; +use crate::MACHINE_EXTRUDER_V2; +use crate::MACHINE_LASER_V1; +use crate::MACHINE_MOCK; +use crate::MACHINE_WINDER_V1; +use crate::TEST_MACHINE; +use crate::VENDOR_QITECH; + #[derive(Debug)] pub struct MachineIdentificationAddresses { pub vendor_word: u16, diff --git a/server/src/app_state.rs b/server/src/app_state.rs index c6820a2f6..37b1d4671 100644 --- a/server/src/app_state.rs +++ b/server/src/app_state.rs @@ -3,11 +3,12 @@ use crate::rest::handlers::write_machine_device_identification::MachineDeviceInf use crate::socketio::main_namespace::MainNamespaceEvents; use crate::socketio::main_namespace::machines_event::{MachineObj, MachinesEventBuilder}; use crate::socketio::namespaces::Namespaces; +use anyhow::{Result, bail}; use control_core::socketio::event::GenericEvent; use ethercat_hal::devices::EthercatDevice; use ethercrab::SubDeviceRef; use ethercrab::{MainDevice, SubDeviceGroup, subdevice_group::Op}; -use machines::machine_identification::{DeviceIdentification, MachineIdentificationUnique}; +use machines::machine_identification::{self, DeviceIdentification, MachineIdentificationUnique}; use machines::serial::registry::SERIAL_DEVICE_REGISTRY; use machines::{Machine, MachineMessage}; use serde::{Deserialize, Serialize}; @@ -123,11 +124,27 @@ impl EthercatSetup { impl SharedState { pub async fn send_machines_event(&self) { - let event = MachinesEventBuilder().build(self.current_machines_meta.lock().await.clone()); + let event = MachinesEventBuilder().build(self.get_machines_meta().await); let main_namespace = &mut self.socketio_setup.namespaces.write().await.main_namespace; main_namespace.emit(MainNamespaceEvents::MachinesEvent(event)); } + pub async fn get_machines_meta(&self) -> Vec { + self.current_machines_meta.lock().await.clone() + } + + pub async fn message_machine(&self, machine_identification_unique: MachineIdentificationUnique, message: MachineMessage) -> Result<()> { + let machines = self.api_machines.lock().await; + let sender = machines.get(&machine_identification_unique); + + if let Some(sender) = sender { + sender.send(message).await?; + return Ok(()); + } + + bail!("Unknown machine!") + } + /// Removes a machine by its unique identifier pub async fn remove_machine(&self, machine_id: &MachineIdentificationUnique) { let mut current_machines = self.current_machines_meta.lock().await; diff --git a/server/src/rest/init.rs b/server/src/rest/init.rs index ba039b4b8..0359f1756 100644 --- a/server/src/rest/init.rs +++ b/server/src/rest/init.rs @@ -9,6 +9,7 @@ use tracing::Level; use super::handlers::machine_mutation::post_machine_mutate; use super::handlers::write_machine_device_identification::post_write_machine_device_identification; use crate::app_state::SharedState; +use crate::rest::rest_api::rest_api_router; use crate::socketio::init::init_socketio; use crate::rest::handlers::metrics::metrics_router; @@ -29,6 +30,7 @@ async fn init_api(app_state: Arc) -> Result<()> { ) .route("/api/v1/machine/mutate", post(post_machine_mutate)) .nest("/api/v1/metrics", metrics_router()) + .nest("/api/v2", rest_api_router()) .layer(socketio_layer) .layer(cors) .layer(trace_layer) @@ -53,7 +55,7 @@ pub fn start_api_thread(app_state: Arc) -> std::thread::JoinHandle< .build() .expect("Failed to create Tokio runtime"); - if let Err(err) = rt.block_on(init_api(app_state.clone())) { + if let Err(err) = rt.block_on(init_api(app_state)) { eprintln!("API server exited with error: {err:?}"); } }) diff --git a/server/src/rest/mod.rs b/server/src/rest/mod.rs index 735c4dc3b..9002a98ad 100644 --- a/server/src/rest/mod.rs +++ b/server/src/rest/mod.rs @@ -1,3 +1,4 @@ pub mod handlers; pub mod init; pub mod util; +pub mod rest_api; diff --git a/server/src/rest/rest_api.rs b/server/src/rest/rest_api.rs new file mode 100644 index 000000000..d300b77a9 --- /dev/null +++ b/server/src/rest/rest_api.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use axum::{Router, debug_handler, Json}; +use axum::extract::{Path, State}; +use axum::routing::get; +use machines::MachineMessage; +use machines::machine_identification::MachineIdentificationUnique; +use machines::winder2::Winder2; +use machines::winder2::api::{LiveValuesEvent, StateEvent, Winder2Events}; +use serde::{Deserialize, Serialize}; + +use crate::app_state::SharedState; +use crate::rest::util::ResponseUtil; + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +struct MachineResponce { + legacy_id: MachineIdentificationUnique, + serial: u16, + vendor: String, + slug: String, + error: Option, +} + +impl From for MachineResponce { + + fn from(machine_identification_unique: MachineIdentificationUnique) -> Self { + let vendor = machine_identification_unique.machine_identification.vendor_str(); + let slug = machine_identification_unique.machine_identification.slug(); + let serial = machine_identification_unique.serial; + + MachineResponce { + legacy_id: machine_identification_unique, + serial, + vendor, + slug, + error: None, + } + } +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +struct GetMachinesResponce { + machines: Vec +} + +#[debug_handler] +async fn get_machines_handler( + State(shared_state): State>, +) -> Json { + + let machines = shared_state.get_machines_meta().await.into_iter().map(|m| { + let vendor = m.machine_identification_unique.machine_identification.vendor_str(); + let slug = m.machine_identification_unique.machine_identification.slug(); + let serial = m.machine_identification_unique.serial; + + MachineResponce { + legacy_id: m.machine_identification_unique, + serial, + vendor, + slug, + error: m.error, + } + }) + .collect(); + + Json(GetMachinesResponce { + machines + }) +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +struct GetMachineResponce { + machine: MachineResponce, + state: serde_json::Value, + live_values: serde_json::Value, +} + +#[debug_handler] +async fn get_winder_v1_handler( + Path(serial): Path, + State(shared_state): State>, +) -> Json { + + let id = MachineIdentificationUnique { + machine_identification: Winder2::MACHINE_IDENTIFICATION, + serial, + }; + + let (sender, receiver) = smol::channel::unbounded(); + shared_state.message_machine(machine_identification_unique, MachineMessage::RequestValues(sender)).await?; + + if let Ok(values) = receiver.recv().await { + return Json(GetMachineResponce { + machine: MachineResponce::from(id), + state: values.state, + live_values: values.live_values, + }); + } + + bail!("Could not get values from machine") +} + +pub fn rest_api_router() -> Router> { + Router::new() + .route("/machine", get(get_machines_handler)) + .route("/machine/winder_v1/{serial}", get(get_winder_v1_handler)) +} From 703bbfc9e6d52a1001e94739e50b9c4b0d569be6 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Sat, 10 Jan 2026 17:36:41 +0100 Subject: [PATCH 2/6] Rest API: Get mock winder v1 values --- machines/src/registry.rs | 2 + machines/src/winder2/mock/act.rs | 11 ++++++ machines/src/winder2/mock/mock_emit.rs | 13 +++--- machines/src/winder2/mod.rs | 3 ++ server/src/app_state.rs | 4 +- server/src/rest/mod.rs | 1 + server/src/rest/response.rs | 55 ++++++++++++++++++++++++++ server/src/rest/rest_api.rs | 28 ++++++------- 8 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 server/src/rest/response.rs diff --git a/machines/src/registry.rs b/machines/src/registry.rs index e9c391ed2..5245ed69d 100644 --- a/machines/src/registry.rs +++ b/machines/src/registry.rs @@ -1,3 +1,4 @@ +#[cfg(not(feature = "mock-machine"))] use crate::analog_input_test_machine::AnalogInputTestMachine; use crate::ip20_test_machine::IP20TestMachine; #[cfg(feature = "mock-machine")] @@ -126,6 +127,7 @@ lazy_static! { mc.register::(IP20TestMachine::MACHINE_IDENTIFICATION); + #[cfg(not(feature = "mock-machine"))] mc.register::(AnalogInputTestMachine::MACHINE_IDENTIFICATION); mc diff --git a/machines/src/winder2/mock/act.rs b/machines/src/winder2/mock/act.rs index bf020e531..aa73edc67 100644 --- a/machines/src/winder2/mock/act.rs +++ b/machines/src/winder2/mock/act.rs @@ -1,6 +1,7 @@ use super::Winder2; use crate::MachineAct; use crate::MachineMessage; +use crate::MachineValues; use std::time::{Duration, Instant}; impl MachineAct for Winder2 { @@ -36,6 +37,16 @@ impl MachineAct for Winder2 { MachineMessage::DisconnectMachine(_machine_connection) => /*Doesnt connec to any Machine do nothing*/ { + () + }, + MachineMessage::RequestValues(sender) => { + sender.send_blocking(MachineValues { + state: serde_json::to_value(self.build_state_event()).expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()).expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + () } } diff --git a/machines/src/winder2/mock/mock_emit.rs b/machines/src/winder2/mock/mock_emit.rs index a869abd19..375e29c3b 100644 --- a/machines/src/winder2/mock/mock_emit.rs +++ b/machines/src/winder2/mock/mock_emit.rs @@ -69,17 +69,18 @@ impl Winder2 { } pub fn emit_live_values(&mut self) { - let event = LiveValuesEvent { + let event = self.get_live_values().build(); + self.namespace.emit(Winder2Events::LiveValues(event)); + } + + pub fn get_live_values(&self) -> LiveValuesEvent { + LiveValuesEvent { traverse_position: Some(0.0), puller_speed: 0.0, spool_rpm: 0.0, tension_arm_angle: 0.0, spool_progress: 0.0, - }; - - let event = event.build(); - - self.namespace.emit(Winder2Events::LiveValues(event)); + } } pub fn build_state_event(&mut self) -> StateEvent { diff --git a/machines/src/winder2/mod.rs b/machines/src/winder2/mod.rs index 893ad190d..08b09de64 100644 --- a/machines/src/winder2/mod.rs +++ b/machines/src/winder2/mod.rs @@ -336,6 +336,9 @@ impl Winder2 { } } +#[cfg(feature = "mock-machine")] +pub use mock::Winder2; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Winder2Mode { Standby, diff --git a/server/src/app_state.rs b/server/src/app_state.rs index 37b1d4671..48f0e9892 100644 --- a/server/src/app_state.rs +++ b/server/src/app_state.rs @@ -133,9 +133,9 @@ impl SharedState { self.current_machines_meta.lock().await.clone() } - pub async fn message_machine(&self, machine_identification_unique: MachineIdentificationUnique, message: MachineMessage) -> Result<()> { + pub async fn message_machine(&self, machine_identification_unique: &MachineIdentificationUnique, message: MachineMessage) -> Result<()> { let machines = self.api_machines.lock().await; - let sender = machines.get(&machine_identification_unique); + let sender = machines.get(machine_identification_unique); if let Some(sender) = sender { sender.send(message).await?; diff --git a/server/src/rest/mod.rs b/server/src/rest/mod.rs index 9002a98ad..d5d15ada2 100644 --- a/server/src/rest/mod.rs +++ b/server/src/rest/mod.rs @@ -1,4 +1,5 @@ pub mod handlers; pub mod init; pub mod util; +pub mod response; pub mod rest_api; diff --git a/server/src/rest/response.rs b/server/src/rest/response.rs new file mode 100644 index 000000000..b5469a30f --- /dev/null +++ b/server/src/rest/response.rs @@ -0,0 +1,55 @@ +use axum::{Json, body::Body, http::StatusCode}; +use serde::Serialize; +use serde_json::json; + +pub enum ApiError { + ErrBadRequest(String), + ErrNotFound(String), + ErrInternal(String), +} + +impl axum::response::IntoResponse for ApiError +{ + fn into_response(self) -> axum::response::Response { + let json = match self { + Self::ErrBadRequest(ref e) => serde_json::to_string(&json!({ "error_bad_request": e })), + Self::ErrNotFound(ref e) => serde_json::to_string(&json!({ "error_not_found": e })), + Self::ErrInternal(ref e) => serde_json::to_string(&json!({ "error_internal": e })), + }; + + let body = match json { + Ok(s) => Body::from(s), + Err(_) => Body::empty(), + }; + + let status = match self { + Self::ErrBadRequest(_) => StatusCode::BAD_REQUEST, + Self::ErrNotFound(_) => StatusCode::NOT_FOUND, + Self::ErrInternal(_) => StatusCode::INTERNAL_SERVER_ERROR, + }; + + axum::response::Response::builder() + .status(status) + .header("Content-Type", "application/json") + .body(body) + .expect("Failed to build error response") + } +} + +pub type Result = axum::response::Result, ApiError>; + +pub fn json(t: T) -> Result { + Result::Ok(Json(t)) +} + +pub fn bad_request(e: E) -> ApiError { + ApiError::ErrBadRequest(e.to_string()) +} + +pub fn not_found(e: E) -> ApiError { + ApiError::ErrNotFound(e.to_string()) +} + +pub fn internal_error(e: E) -> ApiError { + ApiError::ErrInternal(e.to_string()) +} diff --git a/server/src/rest/rest_api.rs b/server/src/rest/rest_api.rs index d300b77a9..22ac22618 100644 --- a/server/src/rest/rest_api.rs +++ b/server/src/rest/rest_api.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use axum::{Router, debug_handler, Json}; +use axum::{Router, debug_handler}; use axum::extract::{Path, State}; use axum::routing::get; use machines::MachineMessage; @@ -10,7 +10,7 @@ use machines::winder2::api::{LiveValuesEvent, StateEvent, Winder2Events}; use serde::{Deserialize, Serialize}; use crate::app_state::SharedState; -use crate::rest::util::ResponseUtil; +use crate::rest::response::*; #[derive(Deserialize, Serialize, Debug, PartialEq)] struct MachineResponce { @@ -46,7 +46,7 @@ struct GetMachinesResponce { #[debug_handler] async fn get_machines_handler( State(shared_state): State>, -) -> Json { +) -> Result { let machines = shared_state.get_machines_meta().await.into_iter().map(|m| { let vendor = m.machine_identification_unique.machine_identification.vendor_str(); @@ -63,7 +63,7 @@ async fn get_machines_handler( }) .collect(); - Json(GetMachinesResponce { + json(GetMachinesResponce { machines }) } @@ -79,7 +79,7 @@ struct GetMachineResponce { async fn get_winder_v1_handler( Path(serial): Path, State(shared_state): State>, -) -> Json { +) -> Result { let id = MachineIdentificationUnique { machine_identification: Winder2::MACHINE_IDENTIFICATION, @@ -87,17 +87,15 @@ async fn get_winder_v1_handler( }; let (sender, receiver) = smol::channel::unbounded(); - shared_state.message_machine(machine_identification_unique, MachineMessage::RequestValues(sender)).await?; - - if let Ok(values) = receiver.recv().await { - return Json(GetMachineResponce { - machine: MachineResponce::from(id), - state: values.state, - live_values: values.live_values, - }); - } + shared_state.message_machine(&id, MachineMessage::RequestValues(sender)).await.map_err(not_found)?; + + let values = receiver.recv().await.map_err(internal_error)?; - bail!("Could not get values from machine") + json(GetMachineResponce { + machine: MachineResponce::from(id), + state: values.state, + live_values: values.live_values, + }) } pub fn rest_api_router() -> Router> { From 1ffd7eade60e101dc29ffc6c075193155f5dffa0 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Sat, 10 Jan 2026 22:26:17 +0100 Subject: [PATCH 3/6] Rest API: Getting values from all mock machines --- machines/src/analog_input_test_machine/act.rs | 23 ++- machines/src/aquapath1/act.rs | 23 +-- machines/src/aquapath1/mod.rs | 21 +-- machines/src/buffer1/act.rs | 23 +-- machines/src/buffer1/mod.rs | 20 ++- machines/src/extruder1/mock/act.rs | 13 +- machines/src/extruder1/mock/mock_emit.rs | 10 +- machines/src/extruder1/mod.rs | 3 + machines/src/extruder2/act.rs | 19 ++- machines/src/extruder2/emit.rs | 17 ++- machines/src/extruder2/mock/act.rs | 20 ++- machines/src/extruder2/mock/mock_emit.rs | 20 +-- machines/src/ip20_test_machine/act.rs | 13 +- machines/src/ip20_test_machine/mod.rs | 16 ++- machines/src/laser/act.rs | 25 ++-- machines/src/laser/mod.rs | 28 ++-- machines/src/lib.rs | 22 +++ machines/src/machine_identification.rs | 22 +-- machines/src/mock/act.rs | 13 +- machines/src/mock/mod.rs | 34 +++-- machines/src/registry.rs | 2 - machines/src/test_machine/act.rs | 12 +- machines/src/test_machine/mod.rs | 8 +- machines/src/wago_power/mod.rs | 134 ++++++++++-------- machines/src/winder2/emit.rs | 10 +- machines/src/winder2/mock/act.rs | 15 +- server/src/app_state.rs | 8 +- server/src/mock_init.rs | 2 - server/src/modbus_tcp/mod.rs | 15 +- server/src/rest/mod.rs | 2 +- server/src/rest/response.rs | 4 +- server/src/rest/rest_api.rs | 108 +++++++++----- 32 files changed, 461 insertions(+), 244 deletions(-) diff --git a/machines/src/analog_input_test_machine/act.rs b/machines/src/analog_input_test_machine/act.rs index 2637f147c..7e69b2b78 100644 --- a/machines/src/analog_input_test_machine/act.rs +++ b/machines/src/analog_input_test_machine/act.rs @@ -1,21 +1,32 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use crate::{MachineAct, analog_input_test_machine::AnalogInputTestMachine}; +use crate::{ + MachineAct, MachineMessage, MachineValues, analog_input_test_machine::AnalogInputTestMachine, +}; impl MachineAct for AnalogInputTestMachine { - fn act_machine_message(&mut self, msg: crate::MachineMessage) { + fn act_machine_message(&mut self, msg: MachineMessage) { match msg { - crate::MachineMessage::SubscribeNamespace(namespace) => { + MachineMessage::SubscribeNamespace(namespace) => { self.namespace.namespace = Some(namespace); self.emit_measurement_rate(); } - crate::MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None, - crate::MachineMessage::HttpApiJsonRequest(value) => { + MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None, + MachineMessage::HttpApiJsonRequest(value) => { use crate::MachineApi; let _res = self.api_mutate(value); } crate::MachineMessage::ConnectToMachine(_machine_connection) => {} - crate::MachineMessage::DisconnectMachine(_machine_connection) => {} + MachineMessage::DisconnectMachine(_machine_connection) => {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::Value::Null, + live_values: serde_json::Value::Null, + }) + .expect("Failed to send values"); + sender.close(); + } } } diff --git a/machines/src/aquapath1/act.rs b/machines/src/aquapath1/act.rs index 78f157ce7..fb7711b7a 100644 --- a/machines/src/aquapath1/act.rs +++ b/machines/src/aquapath1/act.rs @@ -1,5 +1,5 @@ use super::{AquaPathV1, AquaPathV1Mode}; -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for AquaPathV1 { @@ -45,14 +45,21 @@ impl MachineAct for AquaPathV1 { let _res = self.api_mutate(value); } MachineMessage::ConnectToMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () - } + /*Doesnt connect to any Machine so do nothing*/ + {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () + /*Doesnt connect to any Machine so do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); } } } diff --git a/machines/src/aquapath1/mod.rs b/machines/src/aquapath1/mod.rs index 204367b92..6ef6cfad4 100644 --- a/machines/src/aquapath1/mod.rs +++ b/machines/src/aquapath1/mod.rs @@ -109,8 +109,8 @@ impl std::fmt::Display for AquaPathV1 { } impl AquaPathV1 { - pub fn emit_live_values(&mut self) { - let live_values = LiveValuesEvent { + pub fn get_live_values(&self) -> LiveValuesEvent { + LiveValuesEvent { front_temperature: self .front_controller .current_temperature @@ -123,13 +123,16 @@ impl AquaPathV1 { back_flow: self.back_controller.current_flow.get::(), front_temp_reservoir: self.front_controller.temp_reservoir.get::(), back_temp_reservoir: self.back_controller.temp_reservoir.get::(), - }; - let event = live_values.build(); + } + } + + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(AquaPathV1Events::LiveValues(event)); } - pub fn emit_state(&mut self) { - let state = StateEvent { + pub fn get_state(&self) -> StateEvent { + StateEvent { is_default_state: false, mode_state: ModeState { mode: self.mode.clone(), @@ -167,9 +170,11 @@ impl AquaPathV1 { should_flow: self.back_controller.should_pump, }, }, - }; + } + } - let event = state.build(); + pub fn emit_state(&mut self) { + let event = self.get_state().build(); self.namespace.emit(AquaPathV1Events::State(event)); } } diff --git a/machines/src/buffer1/act.rs b/machines/src/buffer1/act.rs index 273a34df4..835fd3637 100644 --- a/machines/src/buffer1/act.rs +++ b/machines/src/buffer1/act.rs @@ -1,5 +1,5 @@ use super::BufferV1; -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for BufferV1 { @@ -33,14 +33,21 @@ impl MachineAct for BufferV1 { let _res = self.api_mutate(value); } MachineMessage::ConnectToMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () - } + /*Doesnt connect to any Machine so do nothing*/ + {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () + /*Doesnt connect to any Machine so do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); } } } diff --git a/machines/src/buffer1/mod.rs b/machines/src/buffer1/mod.rs index cd58387e4..61b0cf030 100644 --- a/machines/src/buffer1/mod.rs +++ b/machines/src/buffer1/mod.rs @@ -48,21 +48,27 @@ impl BufferV1 { vendor: VENDOR_QITECH, machine: MACHINE_BUFFER_V1, }; - pub fn emit_live_values(&mut self) { - let live_values = LiveValuesEvent {}; - let event = live_values.build(); + pub fn get_live_values(&mut self) -> LiveValuesEvent { + LiveValuesEvent {} + } + + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(BufferV1Events::LiveValues(event)); } - pub fn emit_state(&mut self) { - let state = StateEvent { + + pub fn get_state(&self) -> StateEvent { + StateEvent { mode_state: ModeState { mode: self.mode.clone(), }, // connected_machine_state: self.connected_winder.to_state(), - }; + } + } - let event = state.build(); + pub fn emit_state(&mut self) { + let event = self.get_state().build(); self.namespace.emit(BufferV1Events::State(event)); } diff --git a/machines/src/extruder1/mock/act.rs b/machines/src/extruder1/mock/act.rs index fcb0298cd..cd4ab27cc 100644 --- a/machines/src/extruder1/mock/act.rs +++ b/machines/src/extruder1/mock/act.rs @@ -1,6 +1,6 @@ use super::ExtruderV2; use crate::MachineAct; -use crate::MachineMessage; +use crate::{MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for ExtruderV2 { @@ -40,6 +40,17 @@ impl MachineAct for ExtruderV2 { { () } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.build_state_event()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + } } } } diff --git a/machines/src/extruder1/mock/mock_emit.rs b/machines/src/extruder1/mock/mock_emit.rs index ffbfd8622..a7249aca2 100644 --- a/machines/src/extruder1/mock/mock_emit.rs +++ b/machines/src/extruder1/mock/mock_emit.rs @@ -51,8 +51,8 @@ impl ExtruderV2 { } } - pub fn emit_live_values(&mut self) { - let live_values = LiveValuesEvent { + pub fn get_live_values(&mut self) -> LiveValuesEvent { + LiveValuesEvent { motor_status: self.motor_status.clone(), pressure: self.pressure, nozzle_temperature: self.nozzle_temperature, @@ -65,9 +65,11 @@ impl ExtruderV2 { middle_power: self.middle_power, combined_power: self.combined_power, total_energy_kwh: self.total_energy_kwh, - }; + } + } - let event = live_values.build(); + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(ExtruderV2Events::LiveValues(event)); } diff --git a/machines/src/extruder1/mod.rs b/machines/src/extruder1/mod.rs index 3f0c37c05..d85815d75 100644 --- a/machines/src/extruder1/mod.rs +++ b/machines/src/extruder1/mod.rs @@ -100,6 +100,9 @@ pub struct ExtruderV2 { emitted_default_state: bool, } +#[cfg(feature = "mock-machine")] +pub use mock::ExtruderV2; + #[cfg(not(feature = "mock-machine"))] impl Machine for ExtruderV2 { fn get_machine_identification_unique(&self) -> MachineIdentificationUnique { diff --git a/machines/src/extruder2/act.rs b/machines/src/extruder2/act.rs index 71f2c9934..a38e511d8 100644 --- a/machines/src/extruder2/act.rs +++ b/machines/src/extruder2/act.rs @@ -2,7 +2,7 @@ use std::time::Instant; #[cfg(not(feature = "mock-machine"))] -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; #[cfg(not(feature = "mock-machine"))] use super::ExtruderV3; @@ -65,11 +65,20 @@ impl MachineAct for ExtruderV3 { let _res = self.api_mutate(value); } - MachineMessage::ConnectToMachine(_machine_connection) => (), + MachineMessage::ConnectToMachine(_machine_connection) => {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () + /*Doesnt connect to any Machine so do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.build_state_event()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); } } } diff --git a/machines/src/extruder2/emit.rs b/machines/src/extruder2/emit.rs index a062efba4..d409e36b9 100644 --- a/machines/src/extruder2/emit.rs +++ b/machines/src/extruder2/emit.rs @@ -9,6 +9,8 @@ use crate::extruder1::{ }, }; #[cfg(not(feature = "mock-machine"))] +use crate::extruder2::api::{LiveValuesEvent, StateEvent}; +#[cfg(not(feature = "mock-machine"))] use control_core::helpers::hasher_serializer::hash_with_serde_model; #[cfg(not(feature = "mock-machine"))] use control_core::socketio::event::BuildEvent; @@ -24,7 +26,7 @@ use units::thermodynamic_temperature::ThermodynamicTemperature; use units::{angular_velocity::revolution_per_minute, thermodynamic_temperature::degree_celsius}; #[cfg(not(feature = "mock-machine"))] -use super::{ExtruderV3, ExtruderV3Mode, api::StateEvent}; +use super::{ExtruderV3, ExtruderV3Mode}; #[cfg(not(feature = "mock-machine"))] impl ExtruderV3 { @@ -176,15 +178,14 @@ impl ExtruderV3 { } } - pub fn emit_live_values(&mut self) { + pub fn get_live_values(&mut self) -> LiveValuesEvent { use std::time::Instant; - use crate::extruder2::api::{ExtruderV3Events, LiveValuesEvent}; let now = Instant::now(); let combined_power = self.calculate_combined_power(); self.update_total_energy(combined_power, now); - let live_values = LiveValuesEvent { + LiveValuesEvent { motor_status: self.screw_speed_controller.get_motor_status().into(), pressure: self.screw_speed_controller.get_pressure().get::(), nozzle_temperature: self @@ -221,9 +222,13 @@ impl ExtruderV3 { .get_heating_element_wattage(), combined_power, total_energy_kwh: self.total_energy_kwh, - }; + } + } + + pub fn emit_live_values(&mut self) { + use crate::extruder2::api::ExtruderV3Events; - let event = live_values.build(); + let event = self.get_live_values().build(); self.namespace.emit(ExtruderV3Events::LiveValues(event)); } diff --git a/machines/src/extruder2/mock/act.rs b/machines/src/extruder2/mock/act.rs index fcb0298cd..ea79c88c5 100644 --- a/machines/src/extruder2/mock/act.rs +++ b/machines/src/extruder2/mock/act.rs @@ -1,6 +1,5 @@ use super::ExtruderV2; -use crate::MachineAct; -use crate::MachineMessage; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for ExtruderV2 { @@ -34,11 +33,20 @@ impl MachineAct for ExtruderV2 { let _res = self.api_mutate(value); } - MachineMessage::ConnectToMachine(_machine_connection) => (), + MachineMessage::ConnectToMachine(_machine_connection) => {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connec to any Machine do nothing*/ - { - () + /*Doesnt connec to any Machine do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); } } } diff --git a/machines/src/extruder2/mock/mock_emit.rs b/machines/src/extruder2/mock/mock_emit.rs index 24f36c1a1..1bd50b2bb 100644 --- a/machines/src/extruder2/mock/mock_emit.rs +++ b/machines/src/extruder2/mock/mock_emit.rs @@ -10,10 +10,9 @@ use control_core::{ }; impl ExtruderV2 { - pub fn build_state_event(&mut self) -> StateEvent { - // bad performance wise, but doesnt matter its only a mock machine + pub fn get_state(&self) -> StateEvent { StateEvent { - is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), + is_default_state: !self.emitted_default_state, rotation_state: self.rotation_state.clone(), mode_state: self.mode_state.clone(), regulation_state: self.regulation_state.clone(), @@ -25,13 +24,12 @@ impl ExtruderV2 { pid_settings: self.pid_settings.clone(), } } -} -impl ExtruderV2 { pub fn emit_state(&mut self) { - let state = self.build_state_event(); + let state = self.get_state(); let hash = hash_with_serde_model(self.inverter_status_state.clone()); self.last_status_hash = Some(hash); + self.emitted_default_state = true; let event = state.build(); self.namespace.emit(ExtruderV2Events::State(event)); } @@ -51,8 +49,8 @@ impl ExtruderV2 { } } - pub fn emit_live_values(&mut self) { - let live_values = LiveValuesEvent { + pub fn get_live_values(&self) -> LiveValuesEvent { + LiveValuesEvent { motor_status: self.motor_status.clone(), pressure: self.pressure, nozzle_temperature: self.nozzle_temperature, @@ -65,9 +63,11 @@ impl ExtruderV2 { middle_power: self.middle_power, combined_power: self.combined_power, total_energy_kwh: self.total_energy_kwh, - }; + } + } - let event = live_values.build(); + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(ExtruderV2Events::LiveValues(event)); } diff --git a/machines/src/ip20_test_machine/act.rs b/machines/src/ip20_test_machine/act.rs index ac87b37d0..86af7c69f 100644 --- a/machines/src/ip20_test_machine/act.rs +++ b/machines/src/ip20_test_machine/act.rs @@ -1,5 +1,5 @@ use super::IP20TestMachine; -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for IP20TestMachine { @@ -42,6 +42,17 @@ impl MachineAct for IP20TestMachine { MachineMessage::DisconnectMachine(_machine_connection) => { // Does not connect to any Machine; do nothing } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + } } } } diff --git a/machines/src/ip20_test_machine/mod.rs b/machines/src/ip20_test_machine/mod.rs index 53cefd61a..33bc99af8 100644 --- a/machines/src/ip20_test_machine/mod.rs +++ b/machines/src/ip20_test_machine/mod.rs @@ -45,21 +45,25 @@ impl IP20TestMachine { } impl IP20TestMachine { - pub fn emit_state(&mut self) { - let event = StateEvent { + pub fn get_state(&self) -> StateEvent { + StateEvent { outputs: self.outputs, } - .build(); + } + pub fn emit_state(&mut self) { + let event = self.get_state().build(); self.namespace.emit(IP20TestMachineEvents::State(event)); } - pub fn emit_live_values(&mut self) { - let event = LiveValuesEvent { + pub fn get_live_values(&self) -> LiveValuesEvent { + LiveValuesEvent { inputs: self.inputs, } - .build(); + } + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace .emit(IP20TestMachineEvents::LiveValues(event)); } diff --git a/machines/src/laser/act.rs b/machines/src/laser/act.rs index 95e1d2663..2dbb5ca6d 100644 --- a/machines/src/laser/act.rs +++ b/machines/src/laser/act.rs @@ -1,6 +1,7 @@ use super::LaserMachine; use crate::MachineAct; use crate::MachineMessage; +use crate::MachineValues; use std::time::{Duration, Instant}; /// Implements the `MachineAct` trait for the `LaserMachine`. @@ -60,16 +61,24 @@ impl MachineAct for LaserMachine { let _res = self.api_mutate(value); } MachineMessage::ConnectToMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () - } + /*Doesnt connect to any Machine so do nothing*/ + {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { + /*Doesnt connect to any Machine so do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + () - }, - MachineMessage::RequestValues(sender) => sender.send_blocking(crate::MachineValues { state: serde_json::Value::Null, live_values: serde_json::Value::Null }) + } } } } diff --git a/machines/src/laser/mod.rs b/machines/src/laser/mod.rs index 26ffc0a0d..5613e86b0 100644 --- a/machines/src/laser/mod.rs +++ b/machines/src/laser/mod.rs @@ -97,22 +97,24 @@ impl LaserMachine { machine: MACHINE_LASER_V1, }; - ///diameter in mm - pub fn emit_live_values(&mut self) { + pub fn get_live_values(&self) -> LiveValuesEvent { let diameter = self.diameter.get::(); let x_diameter = self.x_diameter.map(|x| x.get::()); let y_diameter = self.y_diameter.map(|y| y.get::()); let roundness = self.roundness; - let live_values = LiveValuesEvent { + LiveValuesEvent { diameter, x_diameter, y_diameter, roundness, - }; + } + } - self.namespace - .emit(LaserEvents::LiveValues(live_values.build())); + ///diameter in mm + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); + self.namespace.emit(LaserEvents::LiveValues(event)); } pub fn build_state_event(&self) -> StateEvent { @@ -129,19 +131,23 @@ impl LaserMachine { } } - pub fn emit_state(&mut self) { - let state = StateEvent { - is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), + pub fn get_state(&self) -> StateEvent { + StateEvent { + is_default_state: !self.emitted_default_state, laser_state: LaserState { higher_tolerance: self.laser_target.higher_tolerance.get::(), lower_tolerance: self.laser_target.lower_tolerance.get::(), target_diameter: self.laser_target.diameter.get::(), in_tolerance: self.in_tolerance, }, - }; + } + } - self.namespace.emit(LaserEvents::State(state.build())); + pub fn emit_state(&mut self) { + let event = self.get_state().build(); + self.namespace.emit(LaserEvents::State(event)); self.did_change_state = false; + self.emitted_default_state = true; } pub fn set_higher_tolerance(&mut self, higher_tolerance: f64) { diff --git a/machines/src/lib.rs b/machines/src/lib.rs index 3961941c2..2fd00c8e6 100644 --- a/machines/src/lib.rs +++ b/machines/src/lib.rs @@ -15,6 +15,9 @@ use smol::channel::{Receiver, Sender}; use socketioxide::extract::SocketRef; use std::any::Any; use std::fmt::Debug; +use std::sync::Arc; +use std::time::Instant; +pub mod analog_input_test_machine; pub mod aquapath1; #[cfg(not(feature = "mock-machine"))] pub mod buffer1; @@ -452,6 +455,9 @@ where } pub trait MachineWithChannel: Send + Debug + Sync { + type State: serde::Serialize; + type LiveValues: serde::Serialize; + fn get_machine_channel(&self) -> &MachineChannel; fn get_machine_channel_mut(&mut self) -> &mut MachineChannel; @@ -459,6 +465,11 @@ pub trait MachineWithChannel: Send + Debug + Sync { fn update(&mut self, now: std::time::Instant) -> Result<()>; fn mutate(&mut self, value: Value) -> Result<()>; + + fn get_state(&self) -> Self::State; + fn get_live_values(&self) -> Option { + None + } } impl MachineApi for C @@ -518,6 +529,17 @@ where MachineMessage::DisconnectMachine(_machine_connection) => { todo!(); } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + } } } } diff --git a/machines/src/machine_identification.rs b/machines/src/machine_identification.rs index 8bc7f690e..017a9e645 100644 --- a/machines/src/machine_identification.rs +++ b/machines/src/machine_identification.rs @@ -45,23 +45,23 @@ impl MachineIdentification { pub fn vendor_str(&self) -> String { match self.vendor { x if x == VENDOR_QITECH => "QiTech".to_string(), - _ => "N/A".to_string() + _ => "N/A".to_string(), } } pub fn slug(&self) -> String { match self.machine { - x if x == MACHINE_WINDER_V1 => "winder_v1".to_string(), - x if x == MACHINE_EXTRUDER_V1 => "extruder_v1".to_string(), - x if x == MACHINE_LASER_V1 => "laser_v1".to_string(), - x if x == MACHINE_MOCK => "mock".to_string(), - x if x == MACHINE_AQUAPATH_V1 => "aquapath_v1".to_string(), - x if x == MACHINE_BUFFER_V1 => "buffer_v1".to_string(), - x if x == MACHINE_EXTRUDER_V2 => "extruder_v2".to_string(), - x if x == TEST_MACHINE => "test_machine".to_string(), - x if x == IP20_TEST_MACHINE => "ip20_test_machine".to_string(), + x if x == MACHINE_WINDER_V1 => "winder_v1".to_string(), + x if x == MACHINE_EXTRUDER_V1 => "extruder_v1".to_string(), + x if x == MACHINE_LASER_V1 => "laser_v1".to_string(), + x if x == MACHINE_MOCK => "mock".to_string(), + x if x == MACHINE_AQUAPATH_V1 => "aquapath_v1".to_string(), + x if x == MACHINE_BUFFER_V1 => "buffer_v1".to_string(), + x if x == MACHINE_EXTRUDER_V2 => "extruder_v2".to_string(), + x if x == TEST_MACHINE => "test_machine".to_string(), + x if x == IP20_TEST_MACHINE => "ip20_test_machine".to_string(), x if x == ANALOG_INPUT_TEST_MACHINE => "analog_input_test_machine".to_string(), - _ => "N/A".to_string() + _ => "N/A".to_string(), } } } diff --git a/machines/src/mock/act.rs b/machines/src/mock/act.rs index b6fdd9b93..13abf25f0 100644 --- a/machines/src/mock/act.rs +++ b/machines/src/mock/act.rs @@ -1,5 +1,5 @@ use super::MockMachine; -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; /// Implements the `MachineAct` trait for the `MockMachine`. @@ -57,6 +57,17 @@ impl MachineAct for MockMachine { { () } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + } } } } diff --git a/machines/src/mock/mod.rs b/machines/src/mock/mod.rs index 7ec03c951..a3f4ce0fa 100644 --- a/machines/src/mock/mod.rs +++ b/machines/src/mock/mod.rs @@ -58,8 +58,7 @@ impl MockMachine { vendor: VENDOR_QITECH, }; - /// Emit live values data event with the current sine wave amplitude - pub fn emit_live_values(&mut self) { + pub fn get_live_values(&self) -> LiveValuesEvent { let now = Instant::now(); let elapsed = now.duration_since(self.t_0).as_secs_f64(); let freq1_hz = self.frequency1.get::(); @@ -76,37 +75,44 @@ impl MockMachine { let amplitude2 = (t * freq2_hz).sin(); let amplitude3 = (t * freq3_hz).sin(); - let live_values = LiveValuesEvent { + LiveValuesEvent { amplitude_sum: amplitude1 + amplitude2 + amplitude3, amplitude1, amplitude2, amplitude3, - }; + } + } - self.namespace - .emit(MockEvents::LiveValues(live_values.build())); + /// Emit live values data event with the current sine wave amplitude + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); + self.namespace.emit(MockEvents::LiveValues(event)); } - /// Emit the current state of the mock machine only if values have changed - pub fn emit_state(&mut self) { + pub fn get_state(&self) -> StateEvent { info!( "Emitting state for MockMachine, is default state: {}", !self.emitted_default_state ); - let current_state = StateEvent { - is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), + StateEvent { + is_default_state: !self.emitted_default_state, frequency1: self.frequency1.get::(), frequency2: self.frequency2.get::(), frequency3: self.frequency3.get::(), mode_state: ModeState { mode: self.mode.clone(), }, - }; + } + } - self.namespace - .emit(MockEvents::State(current_state.build())); - self.last_emitted_event = Some(current_state); + /// Emit the current state of the mock machine only if values have changed + pub fn emit_state(&mut self) { + let state = self.get_state(); + let event = state.build(); + self.namespace.emit(MockEvents::State(event)); + self.emitted_default_state = true; + self.last_emitted_event = Some(state); } /// Set the frequencies of the sine waves diff --git a/machines/src/registry.rs b/machines/src/registry.rs index 5245ed69d..e9c391ed2 100644 --- a/machines/src/registry.rs +++ b/machines/src/registry.rs @@ -1,4 +1,3 @@ -#[cfg(not(feature = "mock-machine"))] use crate::analog_input_test_machine::AnalogInputTestMachine; use crate::ip20_test_machine::IP20TestMachine; #[cfg(feature = "mock-machine")] @@ -127,7 +126,6 @@ lazy_static! { mc.register::(IP20TestMachine::MACHINE_IDENTIFICATION); - #[cfg(not(feature = "mock-machine"))] mc.register::(AnalogInputTestMachine::MACHINE_IDENTIFICATION); mc diff --git a/machines/src/test_machine/act.rs b/machines/src/test_machine/act.rs index 946a49000..d366b7acf 100644 --- a/machines/src/test_machine/act.rs +++ b/machines/src/test_machine/act.rs @@ -1,5 +1,5 @@ use super::TestMachine; -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; use std::time::{Duration, Instant}; impl MachineAct for TestMachine { @@ -31,6 +31,16 @@ impl MachineAct for TestMachine { MachineMessage::DisconnectMachine(_machine_connection) => { // Does not connect to any Machine; do nothing } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::Value::Null, + }) + .expect("Failed to send values"); + sender.close(); + } } } } diff --git a/machines/src/test_machine/mod.rs b/machines/src/test_machine/mod.rs index 03490a955..e0ce74f47 100644 --- a/machines/src/test_machine/mod.rs +++ b/machines/src/test_machine/mod.rs @@ -40,12 +40,14 @@ impl TestMachine { } impl TestMachine { - pub fn emit_state(&mut self) { - let event = StateEvent { + pub fn get_state(&self) -> StateEvent { + StateEvent { led_on: self.led_on, } - .build(); + } + pub fn emit_state(&mut self) { + let event = self.get_state().build(); self.namespace.emit(TestMachineEvents::State(event)); } diff --git a/machines/src/wago_power/mod.rs b/machines/src/wago_power/mod.rs index 3bb25c639..6212677ee 100644 --- a/machines/src/wago_power/mod.rs +++ b/machines/src/wago_power/mod.rs @@ -1,39 +1,42 @@ use crate::{MachineChannel, MachineWithChannel}; use anyhow::Result; -use control_core::{ - modbus::tcp::ModbusTcpDevice, - socketio::{ - event::{BuildEvent, GenericEvent}, - namespace::{ - CacheFn, CacheableEvents, NamespaceCacheingLogic, cache_duration, - cache_first_and_last_event, - }, +use control_core::socketio::{ + event::{BuildEvent, GenericEvent}, + namespace::{ + CacheFn, CacheableEvents, NamespaceCacheingLogic, cache_duration, + cache_first_and_last_event, }, }; use control_core_derive::BuildEvent; use serde::*; -use smol::lock::Mutex; -use std::{ - net::SocketAddr, - time::{Duration, Instant}, -}; -use units::{ - electric_current::milliampere, - electric_potential::{millivolt, volt}, - *, -}; +use std::time::{Duration, Instant}; + +#[cfg(not(feature = "mock-machine"))] +mod imports { + pub use control_core::modbus::tcp::ModbusTcpDevice; + pub use smol::lock::Mutex; + pub use std::net::SocketAddr; + pub use units::{ + electric_current::milliampere, + electric_potential::{millivolt, volt}, + *, + }; +} + +#[cfg(not(feature = "mock-machine"))] +use imports::*; const MODBUS_DC_OFF: u16 = 0; const MODBUS_DC_ON: u16 = 1; const MODBUS_HICCUP_POWER: u16 = 1 << 8; #[derive(Serialize, Debug, Clone, BuildEvent)] -pub struct LiveValuesEvent { +pub struct LiveValues { voltage: f64, current: f64, } -impl CacheableEvents for LiveValuesEvent { +impl CacheableEvents for LiveValues { fn event_value(&self) -> GenericEvent { self.build().into() } @@ -59,12 +62,12 @@ impl Mode { } #[derive(Serialize, Debug, Clone, BuildEvent)] -pub struct StateEvent { +pub struct State { mode: Mode, is_default_state: bool, } -impl CacheableEvents for StateEvent { +impl CacheableEvents for State { fn event_value(&self) -> GenericEvent { self.build().into() } @@ -87,6 +90,7 @@ pub struct WagoPower { device: Mutex, last_emit: Instant, emitted_default_state: bool, + last_live_values: Option, } impl WagoPower { @@ -101,44 +105,12 @@ impl WagoPower { device: Mutex::new(ModbusTcpDevice::new(addr).await?), last_emit: Instant::now(), emitted_default_state: false, - }) - } - - #[cfg(feature = "mock-machine")] - fn get_live_values(&mut self) -> Result { - match self.mode { - Mode::Off => Ok(LiveValuesEvent { - voltage: 0.0, - current: 0.0, - }), - Mode::On24V => Ok(LiveValuesEvent { - voltage: 24.0, - current: 5000.0, - }), - } - } - - #[cfg(not(feature = "mock-machine"))] - fn get_live_values(&mut self) -> Result { - let electric = smol::block_on(async { - let mut dev = self.device.lock().await; - dev.get_holding_registers(0x0500, 2).await - })?; - - let voltage = ElectricPotential::new::(f64::from(electric[0])); - let current = ElectricCurrent::new::(f64::from(electric[1])); - - Ok(LiveValuesEvent { - voltage: voltage.get::(), - current: current.get::(), + last_live_values: None, }) } fn emit_state(&mut self) { - let event = StateEvent { - mode: self.mode.clone(), - is_default_state: !self.emitted_default_state, - }; + let event = self.get_state(); self.channel.emit(event); } @@ -180,9 +152,42 @@ impl WagoPower { let mut dev = self.device.lock().await; dev.get_u16(0x000B).await } + + #[cfg(feature = "mock-machine")] + fn read_live_values(&mut self) -> Result { + match self.mode { + Mode::Off => Ok(LiveValues { + voltage: 0.0, + current: 0.0, + }), + Mode::On24V => Ok(LiveValues { + voltage: 24.0, + current: 5000.0, + }), + } + } + + #[cfg(not(feature = "mock-machine"))] + fn read_live_values(&mut self) -> Result { + let electric = smol::block_on(async { + let mut dev = self.device.lock().await; + dev.get_holding_registers(0x0500, 2).await + })?; + + let voltage = ElectricPotential::new::(f64::from(electric[0])); + let current = ElectricCurrent::new::(f64::from(electric[1])); + + Ok(LiveValues { + voltage: voltage.get::(), + current: current.get::(), + }) + } } impl MachineWithChannel for WagoPower { + type State = State; + type LiveValues = LiveValues; + fn get_machine_channel(&self) -> &MachineChannel { &self.channel } @@ -214,13 +219,24 @@ impl MachineWithChannel for WagoPower { } if now.duration_since(self.last_emit) > Duration::from_secs_f64(1.0 / 30.0) { - if let Ok(event) = self.get_live_values() { - self.channel.emit(event); - } + let live_values = self.read_live_values()?; + self.channel.emit(live_values.clone()); + self.last_live_values = Some(live_values); self.last_emit = now; } Ok(()) } + + fn get_state(&self) -> Self::State { + State { + mode: self.mode.clone(), + is_default_state: !self.emitted_default_state, + } + } + + fn get_live_values(&self) -> Option { + self.last_live_values.clone() + } } diff --git a/machines/src/winder2/emit.rs b/machines/src/winder2/emit.rs index f557fc680..b33888cfc 100644 --- a/machines/src/winder2/emit.rs +++ b/machines/src/winder2/emit.rs @@ -161,7 +161,7 @@ impl Winder2 { self.emit_state(); } - pub fn emit_live_values(&mut self) { + pub fn get_live_values(&self) -> LiveValuesEvent { let angle_deg = self.tension_arm.get_angle().get::(); // Wrap [270;<360] to [-90; 0] @@ -192,7 +192,7 @@ impl Winder2 { .get::() .abs(); - let live_values = LiveValuesEvent { + LiveValuesEvent { traverse_position: self .traverse_controller .get_current_position() @@ -201,9 +201,11 @@ impl Winder2 { spool_rpm, tension_arm_angle: angle_deg, spool_progress: self.spool_automatic_action.progress.get::(), - }; + } + } - let event = live_values.build(); + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(Winder2Events::LiveValues(event)); } diff --git a/machines/src/winder2/mock/act.rs b/machines/src/winder2/mock/act.rs index aa73edc67..c1b33f7e2 100644 --- a/machines/src/winder2/mock/act.rs +++ b/machines/src/winder2/mock/act.rs @@ -38,13 +38,16 @@ impl MachineAct for Winder2 { /*Doesnt connec to any Machine do nothing*/ { () - }, + } MachineMessage::RequestValues(sender) => { - sender.send_blocking(MachineValues { - state: serde_json::to_value(self.build_state_event()).expect("Failed to serialize state"), - live_values: serde_json::to_value(self.get_live_values()).expect("Failed to serialize live values"), - }) - .expect("Failed to send values"); + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.build_state_event()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); sender.close(); () diff --git a/server/src/app_state.rs b/server/src/app_state.rs index 48f0e9892..12b954fa1 100644 --- a/server/src/app_state.rs +++ b/server/src/app_state.rs @@ -8,7 +8,7 @@ use control_core::socketio::event::GenericEvent; use ethercat_hal::devices::EthercatDevice; use ethercrab::SubDeviceRef; use ethercrab::{MainDevice, SubDeviceGroup, subdevice_group::Op}; -use machines::machine_identification::{self, DeviceIdentification, MachineIdentificationUnique}; +use machines::machine_identification::{DeviceIdentification, MachineIdentificationUnique}; use machines::serial::registry::SERIAL_DEVICE_REGISTRY; use machines::{Machine, MachineMessage}; use serde::{Deserialize, Serialize}; @@ -133,7 +133,11 @@ impl SharedState { self.current_machines_meta.lock().await.clone() } - pub async fn message_machine(&self, machine_identification_unique: &MachineIdentificationUnique, message: MachineMessage) -> Result<()> { + pub async fn message_machine( + &self, + machine_identification_unique: &MachineIdentificationUnique, + message: MachineMessage, + ) -> Result<()> { let machines = self.api_machines.lock().await; let sender = machines.get(machine_identification_unique); diff --git a/server/src/mock_init.rs b/server/src/mock_init.rs index f861a2c7a..1487e33c9 100644 --- a/server/src/mock_init.rs +++ b/server/src/mock_init.rs @@ -1,7 +1,5 @@ use crate::add_serial_device; use crate::app_state::SharedState; -use crate::socketio::main_namespace::MainNamespaceEvents; -use crate::socketio::main_namespace::machines_event::MachinesEventBuilder; use machines::registry::MACHINE_REGISTRY; use std::sync::Arc; diff --git a/server/src/modbus_tcp/mod.rs b/server/src/modbus_tcp/mod.rs index 5c69d30a7..40f6dbd1a 100644 --- a/server/src/modbus_tcp/mod.rs +++ b/server/src/modbus_tcp/mod.rs @@ -1,14 +1,21 @@ use crate::app_state::SharedState; -use control_core::ethernet::modbus_tcp_discovery::probe_modbus_tcp; -use control_core::futures::FutureIteratorExt; use machines::{ MACHINE_WAGO_POWER_V1, Machine, MachineChannel, VENDOR_QITECH, machine_identification::{MachineIdentification, MachineIdentificationUnique}, wago_power::WagoPower, }; -use smol::Timer; use std::sync::Arc; -use std::time::Duration; + +#[cfg(not(feature = "mock-machine"))] +mod imports { + pub use control_core::ethernet::modbus_tcp_discovery::probe_modbus_tcp; + pub use control_core::futures::FutureIteratorExt; + pub use smol::Timer; + pub use std::time::Duration; +} + +#[cfg(not(feature = "mock-machine"))] +use imports::*; #[cfg(not(feature = "mock-machine"))] pub async fn start_modbus_tcp_discovery(shared_state: Arc) { diff --git a/server/src/rest/mod.rs b/server/src/rest/mod.rs index d5d15ada2..2ff968614 100644 --- a/server/src/rest/mod.rs +++ b/server/src/rest/mod.rs @@ -1,5 +1,5 @@ pub mod handlers; pub mod init; -pub mod util; pub mod response; pub mod rest_api; +pub mod util; diff --git a/server/src/rest/response.rs b/server/src/rest/response.rs index b5469a30f..163ca67ca 100644 --- a/server/src/rest/response.rs +++ b/server/src/rest/response.rs @@ -1,5 +1,4 @@ use axum::{Json, body::Body, http::StatusCode}; -use serde::Serialize; use serde_json::json; pub enum ApiError { @@ -8,8 +7,7 @@ pub enum ApiError { ErrInternal(String), } -impl axum::response::IntoResponse for ApiError -{ +impl axum::response::IntoResponse for ApiError { fn into_response(self) -> axum::response::Response { let json = match self { Self::ErrBadRequest(ref e) => serde_json::to_string(&json!({ "error_bad_request": e })), diff --git a/server/src/rest/rest_api.rs b/server/src/rest/rest_api.rs index 22ac22618..628b72772 100644 --- a/server/src/rest/rest_api.rs +++ b/server/src/rest/rest_api.rs @@ -1,18 +1,24 @@ use std::sync::Arc; -use axum::{Router, debug_handler}; use axum::extract::{Path, State}; use axum::routing::get; +use axum::{Extension, Router, debug_handler}; use machines::MachineMessage; -use machines::machine_identification::MachineIdentificationUnique; +use machines::analog_input_test_machine::AnalogInputTestMachine; +use machines::aquapath1::AquaPathV1; +use machines::extruder1::ExtruderV2; +use machines::ip20_test_machine::IP20TestMachine; +use machines::laser::LaserMachine; +use machines::machine_identification::{MachineIdentification, MachineIdentificationUnique}; +use machines::mock::MockMachine; +use machines::test_machine::TestMachine; use machines::winder2::Winder2; -use machines::winder2::api::{LiveValuesEvent, StateEvent, Winder2Events}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use crate::app_state::SharedState; use crate::rest::response::*; -#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[derive(Serialize, Debug, PartialEq)] struct MachineResponce { legacy_id: MachineIdentificationUnique, serial: u16, @@ -22,9 +28,10 @@ struct MachineResponce { } impl From for MachineResponce { - fn from(machine_identification_unique: MachineIdentificationUnique) -> Self { - let vendor = machine_identification_unique.machine_identification.vendor_str(); + let vendor = machine_identification_unique + .machine_identification + .vendor_str(); let slug = machine_identification_unique.machine_identification.slug(); let serial = machine_identification_unique.serial; @@ -38,37 +45,44 @@ impl From for MachineResponce { } } -#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[derive(Serialize, Debug, PartialEq)] struct GetMachinesResponce { - machines: Vec + machines: Vec, } #[debug_handler] async fn get_machines_handler( State(shared_state): State>, ) -> Result { - - let machines = shared_state.get_machines_meta().await.into_iter().map(|m| { - let vendor = m.machine_identification_unique.machine_identification.vendor_str(); - let slug = m.machine_identification_unique.machine_identification.slug(); - let serial = m.machine_identification_unique.serial; - - MachineResponce { - legacy_id: m.machine_identification_unique, - serial, - vendor, - slug, - error: m.error, - } - }) - .collect(); - - json(GetMachinesResponce { - machines - }) + let machines = shared_state + .get_machines_meta() + .await + .into_iter() + .map(|m| { + let vendor = m + .machine_identification_unique + .machine_identification + .vendor_str(); + let slug = m + .machine_identification_unique + .machine_identification + .slug(); + let serial = m.machine_identification_unique.serial; + + MachineResponce { + legacy_id: m.machine_identification_unique, + serial, + vendor, + slug, + error: m.error, + } + }) + .collect(); + + json(GetMachinesResponce { machines }) } -#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[derive(Serialize, Debug, PartialEq)] struct GetMachineResponce { machine: MachineResponce, state: serde_json::Value, @@ -76,18 +90,21 @@ struct GetMachineResponce { } #[debug_handler] -async fn get_winder_v1_handler( - Path(serial): Path, +async fn get_machine_handler( + Extension(id): Extension, State(shared_state): State>, + Path(serial): Path, ) -> Result { - let id = MachineIdentificationUnique { - machine_identification: Winder2::MACHINE_IDENTIFICATION, + machine_identification: id, serial, }; - let (sender, receiver) = smol::channel::unbounded(); - shared_state.message_machine(&id, MachineMessage::RequestValues(sender)).await.map_err(not_found)?; + let (sender, receiver) = smol::channel::bounded(1); + shared_state + .message_machine(&id, MachineMessage::RequestValues(sender)) + .await + .map_err(not_found)?; let values = receiver.recv().await.map_err(internal_error)?; @@ -98,8 +115,27 @@ async fn get_winder_v1_handler( }) } +fn make_machine_router(id: MachineIdentification) -> Router> { + let slug = id.slug(); + Router::new() + .route( + format!("/machine/{slug}/{{serial}}").as_ref(), + get(get_machine_handler), + ) + .layer(Extension(id)) +} + pub fn rest_api_router() -> Router> { Router::new() .route("/machine", get(get_machines_handler)) - .route("/machine/winder_v1/{serial}", get(get_winder_v1_handler)) + .merge(make_machine_router(LaserMachine::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(Winder2::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(MockMachine::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(ExtruderV2::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(AquaPathV1::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(TestMachine::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(IP20TestMachine::MACHINE_IDENTIFICATION)) + .merge(make_machine_router( + AnalogInputTestMachine::MACHINE_IDENTIFICATION, + )) } From 671e36fa0fabf543e4e3bcf1eb680dcab7aec6c1 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Sat, 10 Jan 2026 22:52:46 +0100 Subject: [PATCH 4/6] Rest API: Getting values from all machines --- machines/src/extruder1/act.rs | 20 +++++++++---- machines/src/extruder1/emit.rs | 29 ++++++++----------- machines/src/extruder1/mod.rs | 7 +++-- .../src/extruder1/screw_speed_controller.rs | 14 ++++----- .../src/extruder1/temperature_controller.rs | 2 +- machines/src/winder2/act.rs | 13 ++++++++- 6 files changed, 51 insertions(+), 34 deletions(-) diff --git a/machines/src/extruder1/act.rs b/machines/src/extruder1/act.rs index 1fcf141cb..ed62784d7 100644 --- a/machines/src/extruder1/act.rs +++ b/machines/src/extruder1/act.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "mock-machine"))] use crate::extruder1::ExtruderV2; #[cfg(not(feature = "mock-machine"))] -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; #[cfg(not(feature = "mock-machine"))] use std::time::{Duration, Instant}; @@ -41,6 +41,7 @@ impl MachineAct for ExtruderV2 { // more than 33ms have passed since last emit (30 "fps" target) if now.duration_since(self.last_measurement_emit) > Duration::from_secs_f64(1.0 / 30.0) { + self.update_total_energy(now); self.maybe_emit_state_event(); // Emit live values at 30 FPS self.emit_live_values(); @@ -61,11 +62,20 @@ impl MachineAct for ExtruderV2 { let _res = self.api_mutate(value); } - MachineMessage::ConnectToMachine(_machine_connection) => (), + MachineMessage::ConnectToMachine(_machine_connection) => {} MachineMessage::DisconnectMachine(_machine_connection) => - /*Doesnt connect to any Machine so do nothing*/ - { - () + /*Doesnt connect to any Machine so do nothing*/ + {} + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.get_state()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); } } } diff --git a/machines/src/extruder1/emit.rs b/machines/src/extruder1/emit.rs index 2e673eda3..7246c08b3 100644 --- a/machines/src/extruder1/emit.rs +++ b/machines/src/extruder1/emit.rs @@ -25,11 +25,11 @@ use units::{angular_velocity::revolution_per_minute, thermodynamic_temperature:: #[cfg(not(feature = "mock-machine"))] impl ExtruderV2 { - pub fn build_state_event(&mut self) -> StateEvent { - use crate::extruder1::api::{TemperaturePid, TemperaturePidStates}; + pub fn get_state(&self) -> StateEvent { + use crate::extruder1::api::TemperaturePidStates; StateEvent { - is_default_state: !std::mem::replace(&mut self.emitted_default_state, true), + is_default_state: !self.emitted_default_state, rotation_state: RotationState { forward: self.screw_speed_controller.get_rotation_direction(), }, @@ -141,16 +141,14 @@ impl ExtruderV2 { }, } } -} -#[cfg(not(feature = "mock-machine"))] -impl ExtruderV2 { pub fn emit_state(&mut self) { - let state = self.build_state_event(); + let state = self.get_state(); let hash = hash_with_serde_model(self.screw_speed_controller.get_inverter_status()); self.last_status_hash = Some(hash); let event = state.build(); self.namespace.emit(ExtruderV2Events::State(event)); + self.emitted_default_state = true; } pub fn maybe_emit_state_event(&mut self) { @@ -168,13 +166,8 @@ impl ExtruderV2 { } } - pub fn emit_live_values(&mut self) { - use std::time::Instant; - let now = Instant::now(); - let combined_power = self.calculate_combined_power(); - self.update_total_energy(combined_power, now); - - let live_values = LiveValuesEvent { + pub fn get_live_values(&self) -> LiveValuesEvent { + LiveValuesEvent { motor_status: self.screw_speed_controller.get_motor_status().into(), pressure: self.screw_speed_controller.get_pressure().get::(), nozzle_temperature: self @@ -209,11 +202,13 @@ impl ExtruderV2 { middle_power: self .temperature_controller_middle .get_heating_element_wattage(), - combined_power, + combined_power: self.calculate_combined_power(), total_energy_kwh: self.total_energy_kwh, - }; + } + } - let event = live_values.build(); + pub fn emit_live_values(&mut self) { + let event = self.get_live_values().build(); self.namespace.emit(ExtruderV2Events::LiveValues(event)); } diff --git a/machines/src/extruder1/mod.rs b/machines/src/extruder1/mod.rs index d85815d75..5b4eb812d 100644 --- a/machines/src/extruder1/mod.rs +++ b/machines/src/extruder1/mod.rs @@ -132,7 +132,7 @@ impl ExtruderV2 { #[cfg(not(feature = "mock-machine"))] impl ExtruderV2 { /// Calculate combined power consumption in watts - fn calculate_combined_power(&mut self) -> f64 { + fn calculate_combined_power(&self) -> f64 { let motor_power = { let motor_status = &self.screw_speed_controller.inverter.motor_status; let voltage = motor_status.voltage.get::(); @@ -156,10 +156,11 @@ impl ExtruderV2 { } /// Update total energy consumption in kWh - fn update_total_energy(&mut self, current_power_watts: f64, now: Instant) { + fn update_total_energy(&mut self, now: Instant) { + let power_watts = self.calculate_combined_power(); if let Some(last_time) = self.last_energy_calculation_time { let time_delta_hours = now.duration_since(last_time).as_secs_f64() / 3600.0; - let energy_delta_kwh = (current_power_watts / 1000.0) * time_delta_hours; + let energy_delta_kwh = (power_watts / 1000.0) * time_delta_hours; self.total_energy_kwh += energy_delta_kwh; } self.last_energy_calculation_time = Some(now); diff --git a/machines/src/extruder1/screw_speed_controller.rs b/machines/src/extruder1/screw_speed_controller.rs index 65f0b4b73..a66cd1a17 100644 --- a/machines/src/extruder1/screw_speed_controller.rs +++ b/machines/src/extruder1/screw_speed_controller.rs @@ -77,11 +77,11 @@ impl ScrewSpeedController { self.nozzle_pressure_limit = pressure; } - pub fn get_nozzle_pressure_limit(&mut self) -> Pressure { + pub fn get_nozzle_pressure_limit(&self) -> Pressure { self.nozzle_pressure_limit } - pub const fn get_nozzle_pressure_limit_enabled(&mut self) -> bool { + pub const fn get_nozzle_pressure_limit_enabled(&self) -> bool { self.nozzle_pressure_limit_enabled } @@ -89,11 +89,11 @@ impl ScrewSpeedController { self.nozzle_pressure_limit_enabled = enabled; } - pub fn get_target_rpm(&mut self) -> AngularVelocity { + pub fn get_target_rpm(&self) -> AngularVelocity { self.target_rpm } - pub const fn get_rotation_direction(&mut self) -> bool { + pub const fn get_rotation_direction(&self) -> bool { self.forward_rotation } @@ -129,7 +129,7 @@ impl ScrewSpeedController { self.inverter.set_frequency_target(target_frequency); } - pub const fn get_uses_rpm(&mut self) -> bool { + pub const fn get_uses_rpm(&self) -> bool { self.uses_rpm } @@ -148,7 +148,7 @@ impl ScrewSpeedController { self.motor_on = true; } - pub fn get_motor_status(&mut self) -> MotorStatus { + pub fn get_motor_status(&self) -> MotorStatus { let frequency = self.inverter.motor_status.frequency; let rpm = AngularVelocity::new::(frequency.get::()); @@ -197,7 +197,7 @@ impl ScrewSpeedController { self.pid.reset() } - pub fn get_pressure(&mut self) -> Pressure { + pub fn get_pressure(&self) -> Pressure { let current_result = self.get_sensor_current(); let current = match current_result { Ok(current) => current.get::(), diff --git a/machines/src/extruder1/temperature_controller.rs b/machines/src/extruder1/temperature_controller.rs index 323e29dcc..976638d1b 100644 --- a/machines/src/extruder1/temperature_controller.rs +++ b/machines/src/extruder1/temperature_controller.rs @@ -70,7 +70,7 @@ impl TemperatureController { self.heating_allowed = true; } - pub fn get_heating_element_wattage(&mut self) -> f64 { + pub fn get_heating_element_wattage(&self) -> f64 { self.temperature_pid_output * self.heating_element_wattage } diff --git a/machines/src/winder2/act.rs b/machines/src/winder2/act.rs index 0033102bc..05e449ffb 100644 --- a/machines/src/winder2/act.rs +++ b/machines/src/winder2/act.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "mock-machine"))] use super::Winder2; #[cfg(not(feature = "mock-machine"))] -use crate::{MachineAct, MachineMessage}; +use crate::{MachineAct, MachineMessage, MachineValues}; #[cfg(not(feature = "mock-machine"))] use std::time::{Duration, Instant}; @@ -61,6 +61,17 @@ impl MachineAct for Winder2 { MachineMessage::DisconnectMachine(_machine_connection) => { self.connected_machines.clear(); } + MachineMessage::RequestValues(sender) => { + sender + .send_blocking(MachineValues { + state: serde_json::to_value(self.build_state_event()) + .expect("Failed to serialize state"), + live_values: serde_json::to_value(self.get_live_values()) + .expect("Failed to serialize live values"), + }) + .expect("Failed to send values"); + sender.close(); + } } } } From 12e7f245cedecf7740f12272c0582f41156cb8e6 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Sun, 11 Jan 2026 21:28:04 +0100 Subject: [PATCH 5/6] Rest API: Allow posting mutations to each machine --- machines/src/machine_identification.rs | 4 ++- machines/src/wago_power/mod.rs | 12 +++++++- server/src/modbus_tcp/mod.rs | 13 ++------- server/src/rest/rest_api.rs | 39 +++++++++++++++++++++----- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/machines/src/machine_identification.rs b/machines/src/machine_identification.rs index 017a9e645..2de7f6e86 100644 --- a/machines/src/machine_identification.rs +++ b/machines/src/machine_identification.rs @@ -58,10 +58,11 @@ impl MachineIdentification { x if x == MACHINE_AQUAPATH_V1 => "aquapath_v1".to_string(), x if x == MACHINE_BUFFER_V1 => "buffer_v1".to_string(), x if x == MACHINE_EXTRUDER_V2 => "extruder_v2".to_string(), + x if x == MACHINE_WAGO_POWER_V1 => "wago_power_v1".to_string(), x if x == TEST_MACHINE => "test_machine".to_string(), x if x == IP20_TEST_MACHINE => "ip20_test_machine".to_string(), x if x == ANALOG_INPUT_TEST_MACHINE => "analog_input_test_machine".to_string(), - _ => "N/A".to_string(), + _ => unreachable!("Unknown machine id"), } } } @@ -180,6 +181,7 @@ use crate::MACHINE_EXTRUDER_V1; use crate::MACHINE_EXTRUDER_V2; use crate::MACHINE_LASER_V1; use crate::MACHINE_MOCK; +use crate::MACHINE_WAGO_POWER_V1; use crate::MACHINE_WINDER_V1; use crate::TEST_MACHINE; use crate::VENDOR_QITECH; diff --git a/machines/src/wago_power/mod.rs b/machines/src/wago_power/mod.rs index 6212677ee..4b5131e2a 100644 --- a/machines/src/wago_power/mod.rs +++ b/machines/src/wago_power/mod.rs @@ -1,4 +1,7 @@ -use crate::{MachineChannel, MachineWithChannel}; +use crate::{ + MACHINE_WAGO_POWER_V1, MachineChannel, MachineWithChannel, VENDOR_QITECH, + machine_identification::MachineIdentification, +}; use anyhow::Result; use control_core::socketio::{ event::{BuildEvent, GenericEvent}, @@ -184,6 +187,13 @@ impl WagoPower { } } +impl WagoPower { + pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification { + vendor: VENDOR_QITECH, + machine: MACHINE_WAGO_POWER_V1, + }; +} + impl MachineWithChannel for WagoPower { type State = State; type LiveValues = LiveValues; diff --git a/server/src/modbus_tcp/mod.rs b/server/src/modbus_tcp/mod.rs index 40f6dbd1a..d56b8db7d 100644 --- a/server/src/modbus_tcp/mod.rs +++ b/server/src/modbus_tcp/mod.rs @@ -1,7 +1,6 @@ use crate::app_state::SharedState; use machines::{ - MACHINE_WAGO_POWER_V1, Machine, MachineChannel, VENDOR_QITECH, - machine_identification::{MachineIdentification, MachineIdentificationUnique}, + Machine, MachineChannel, machine_identification::MachineIdentificationUnique, wago_power::WagoPower, }; use std::sync::Arc; @@ -32,10 +31,7 @@ pub async fn start_modbus_tcp_discovery(shared_state: Arc) { .map(|probe| { smol::spawn(async move { let machine_identification_unique = MachineIdentificationUnique { - machine_identification: MachineIdentification { - vendor: VENDOR_QITECH, - machine: MACHINE_WAGO_POWER_V1, - }, + machine_identification: WagoPower::MACHINE_IDENTIFICATION, serial: probe.serial, }; @@ -58,10 +54,7 @@ pub async fn start_modbus_tcp_discovery(shared_state: Arc) { #[cfg(feature = "mock-machine")] pub async fn start_modbus_tcp_discovery(shared_state: Arc) { let machine_identification_unique = MachineIdentificationUnique { - machine_identification: MachineIdentification { - vendor: VENDOR_QITECH, - machine: MACHINE_WAGO_POWER_V1, - }, + machine_identification: WagoPower::MACHINE_IDENTIFICATION, serial: 0xbeef, }; diff --git a/server/src/rest/rest_api.rs b/server/src/rest/rest_api.rs index 628b72772..a441e5111 100644 --- a/server/src/rest/rest_api.rs +++ b/server/src/rest/rest_api.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use axum::extract::{Path, State}; -use axum::routing::get; -use axum::{Extension, Router, debug_handler}; +use axum::routing::{get, post}; +use axum::{Extension, Json, Router, debug_handler}; use machines::MachineMessage; use machines::analog_input_test_machine::AnalogInputTestMachine; use machines::aquapath1::AquaPathV1; @@ -12,6 +12,7 @@ use machines::laser::LaserMachine; use machines::machine_identification::{MachineIdentification, MachineIdentificationUnique}; use machines::mock::MockMachine; use machines::test_machine::TestMachine; +use machines::wago_power::WagoPower; use machines::winder2::Winder2; use serde::Serialize; @@ -35,7 +36,7 @@ impl From for MachineResponce { let slug = machine_identification_unique.machine_identification.slug(); let serial = machine_identification_unique.serial; - MachineResponce { + Self { legacy_id: machine_identification_unique, serial, vendor, @@ -115,13 +116,36 @@ async fn get_machine_handler( }) } +type PostMachineRequest = Vec; + +#[debug_handler] +async fn post_machine_handler( + Extension(id): Extension, + State(shared_state): State>, + Path(serial): Path, + Json(request): Json, +) -> Result<()> { + let id = MachineIdentificationUnique { + machine_identification: id, + serial, + }; + + for value in request { + shared_state + .message_machine(&id, MachineMessage::HttpApiJsonRequest(value)) + .await + .map_err(not_found)?; + } + + json(()) +} + fn make_machine_router(id: MachineIdentification) -> Router> { let slug = id.slug(); + let path = format!("/machine/{slug}/{{serial}}"); Router::new() - .route( - format!("/machine/{slug}/{{serial}}").as_ref(), - get(get_machine_handler), - ) + .route(&path, get(get_machine_handler)) + .route(&path, post(post_machine_handler)) .layer(Extension(id)) } @@ -134,6 +158,7 @@ pub fn rest_api_router() -> Router> { .merge(make_machine_router(ExtruderV2::MACHINE_IDENTIFICATION)) .merge(make_machine_router(AquaPathV1::MACHINE_IDENTIFICATION)) .merge(make_machine_router(TestMachine::MACHINE_IDENTIFICATION)) + .merge(make_machine_router(WagoPower::MACHINE_IDENTIFICATION)) .merge(make_machine_router(IP20TestMachine::MACHINE_IDENTIFICATION)) .merge(make_machine_router( AnalogInputTestMachine::MACHINE_IDENTIFICATION, From 8c2b20b148b3c338801ff1fbdef429783e3dd8e6 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Sun, 11 Jan 2026 23:17:25 +0100 Subject: [PATCH 6/6] Rest API: Docs --- docs/rest-api.md | 235 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 docs/rest-api.md diff --git a/docs/rest-api.md b/docs/rest-api.md new file mode 100644 index 000000000..b9ea48f27 --- /dev/null +++ b/docs/rest-api.md @@ -0,0 +1,235 @@ +# REST API `/api/v2` + +The Qitech Control Panel exposes a small HTTP interface for discovering machines and reading their current values. All examples below assume you are connected to the panel’s Ethernet subnet and talk directly to the panel at: + +- **Base URL:** `http://10.10.10.1:3001` + +> **Schema note:** This documentation intentionally stays light on field details beyond what is shown in the examples. For the exact datatypes and complete payload shapes, refer to the corresponding Rust types (linked below). + +--- + +## Authentication + +The panel does **not** perform HTTP authentication. The expected security model is **network-level isolation**: anything that can send packets to the panel’s Ethernet interface is treated as trusted. This matches common security assumptions in EtherCAT-style control networks. + +The panel is configured to administer its own subnet `10.10.10.0/24` via DHCP, while Wi-Fi can be used for upstream internet connectivity (if configured). If you need authentication or access from outside the isolated subnet, place a router/device in **client/bridge mode** on the panel network and expose the panel through a **reverse proxy** where you can add authentication, logging, rate limiting, etc. + +If DNS is available on the subnet, you may be able to resolve `qitech.control`; otherwise use the static address `10.10.10.1`. + +--- + +## List machines `GET /api/v2/machine` + +Returns the set of machines currently known/connected to the panel. + +Machines are identified by: + +- `slug`: the machine type / model identifier (string) +- `serial`: the specific machine instance identifier (int) + +Each machine also includes a `legacy_id` to support older **v1** workflows. If a machine reports an issue, the `error` field may be present and non-null (containing an error message). + +### Example request + +```bash +curl -X GET "http://10.10.10.1:3001/api/v2/machine" +``` + +### Example response + +```json +{ + "machines": [ + { + "legacy_id": { + "machine_identification": { + "vendor": 1, + "machine": 7 + }, + "serial": 57922 + }, + "serial": 57922, + "vendor": "QiTech", + "slug": "mock", + "error": null + }, + { + "legacy_id": { + "machine_identification": { + "vendor": 1, + "machine": 4 + }, + "serial": 57922 + }, + "serial": 57922, + "vendor": "QiTech", + "slug": "extruder_v1", + "error": null + }, + { + "legacy_id": { + "machine_identification": { + "vendor": 1, + "machine": 2 + }, + "serial": 57922 + }, + "serial": 57922, + "vendor": "QiTech", + "slug": "winder_v1", + "error": null + }, + { + "legacy_id": { + "machine_identification": { + "vendor": 1, + "machine": 10 + }, + "serial": 48879 + }, + "serial": 48879, + "vendor": "QiTech", + "slug": "wago_power_v1", + "error": null + } + ] +} +``` + +--- + +## Get current values `GET /api/v2/machine//` + +Returns all currently known values for a single machine. + +Values are categorized into two groups: + +- **State**: requested/commanded values (these typically change only after a state-change request, or if another controller updates them) +- **Live Values**: measured/observed values coming from the machine and potentially changing quickly + +This REST endpoint returns **only the current snapshot**, not a stream of live values. +To receive continuous updates (via WebSockets), subscribe to the machine namespace (see **WebSockets** below). + +### Example request (mock machine) + +```bash +curl -X GET "http://10.10.10.1:3001/api/v2/machine/mock/57922" +``` + +### Example response + +```json +{ + "machine": { + "legacy_id": { + "machine_identification": { + "vendor": 1, + "machine": 7 + }, + "serial": 57922 + }, + "serial": 57922, + "vendor": "QiTech", + "slug": "mock", + "error": null + }, + "state": { + "frequency1": 100.0, + "frequency2": 200.0, + "frequency3": 500.0, + "is_default_state": false, + "mode_state": { + "mode": "Running" + } + }, + "live_values": { + "amplitude1": -0.03438523433309566, + "amplitude2": -0.06872980145477608, + "amplitude3": -0.1711138370170743, + "amplitude_sum": -0.27422887280494607 + } +} +``` + +--- + +## Change machine state `POST /api/v1/machine//` + +State changes are submitted as **mutations**. The mutation payload is defined per machine type in Rust. Conceptually, each item in the mutation list represents a setter-style operation that is applied by the real-time control loop. + +The API does **not** return the newly-applied state in the POST response. The panel runs a real-time loop and generally won’t block waiting for the physical system to converge. Instead: + +- Submit the mutation via `POST` +- Poll `GET /api/v2/machine//` to observe the updated state and/or any reported errors + +### Example request (mock machine) + +```bash +curl -X POST \ + -d \ + -H "Content-Type: application/json" \ + "http://10.10.10.1:3001/api/v1/machine/mock/57922" +``` + +### Example response + +```json +null +``` + +--- + +## WebSockets + +For continuous updates, subscribe to a machine-specific namespace derived from its `legacy_id`: + +- Namespace: `/machine///` + +The stream emits events for: + +- state changes (`StateEvent`) +- live value updates (`LiveValuesEvent`) + +Both event payloads use the same machine-specific schema as the `/api/v2` REST responses. + +--- + +## List of all machines + +Below is a template you can fill with links to the relevant Rust types (mutations + state/live structs). +For each machine, link to: + +- **Mutations:** the request payload type used by `POST /api/v1/machine//` +- **State / Live Values:** the response payload types returned by `GET /api/v2/machine//` + +### Machines + +- **winder_v1** + + - Mutations: + - State: + - Live Values: + +- **extruder_v1** + + - Mutations: + - State: + - Live Values: + +- **laser_v1** + + - Mutations: + - State: + - Live Values: + +- **mock** + + - Mutations: + - State: + - Live Values: + +- **extruder_v2** + + - Mutations: + - State: + - Live Values: