Compare commits

..
Author SHA1 Message Date
kraemr e0cd36058c testing debug log impact 2025-09-23 15:45:46 +02:00
Robin Krämer f2f893c917 Merge pull request #741 from qitechgmbh/739-add-roundness-value-to-laser-graph-and-control-page
Added Roundness Value to 2 Axis Laser
2025-09-22 14:27:30 +02:00
Robin KrämerandCopilot bbbca4085d Update server/src/machines/laser/mod.rs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-22 14:24:42 +02:00
Robin KrämerandCopilot 4fdf461520 Update server/src/machines/laser/mod.rs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-22 14:23:37 +02:00
snacj dcfc6728ba Made Roundness unitless 2025-09-22 14:10:25 +02:00
snacj 2673a92f50 Added Roundness Value to 2 Axis Laser 2025-09-22 13:39:59 +02:00
10 changed files with 167 additions and 79 deletions

No files matched your search

@@ -7,8 +7,8 @@ type DiameterVisualisationProps = {
lowTolerance: number;
highTolerance: number;
diameter: TimeSeries;
x_value?: TimeSeries;
y_value?: TimeSeries;
x_diameter?: TimeSeries;
y_diameter?: TimeSeries;
};
// Ring color depending on tolerance
@@ -36,12 +36,12 @@ export function DiameterVisualisation({
lowTolerance,
highTolerance,
diameter,
x_value,
y_value,
x_diameter,
y_diameter,
}: DiameterVisualisationProps) {
const actualDiameter = diameter.current?.value ?? 0.0;
const actualX = x_value?.current?.value ?? actualDiameter;
const actualY = y_value?.current?.value ?? actualDiameter;
const actualX = x_diameter?.current?.value ?? actualDiameter;
const actualY = y_diameter?.current?.value ?? actualDiameter;
const minDia = targetDiameter - lowTolerance;
const maxDia = targetDiameter + highTolerance;
@@ -55,9 +55,15 @@ export function DiameterVisualisation({
inputMax: number,
outputMin: number,
outputMax: number,
) =>
outputMin +
((value - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin);
) => {
if (inputMax === inputMin) {
return (outputMin + outputMax) / 2;
}
return (
outputMin +
((value - inputMin) * (outputMax - outputMin)) / (inputMax - inputMin)
);
};
const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
@@ -14,8 +14,9 @@ import { DiameterVisualisation } from "../DiameterVisualisation";
export function Laser1ControlPage() {
const {
diameter,
x_value,
y_value,
x_diameter,
y_diameter,
roundness,
state,
defaultState,
setTargetDiameter,
@@ -36,8 +37,8 @@ export function Laser1ControlPage() {
lowTolerance={lowerTolerance}
highTolerance={higherTolerance}
diameter={diameter}
x_value={x_value}
y_value={y_value}
x_diameter={x_diameter}
y_diameter={y_diameter}
/>
<div className="flex flex-row items-center gap-6">
<TimeSeriesValueNumeric
@@ -47,22 +48,31 @@ export function Laser1ControlPage() {
renderValue={(value) => value.toFixed(3)}
/>
</div>
{x_value?.current && (
{x_diameter?.current && (
<div className="flex flex-row items-center gap-6">
<TimeSeriesValueNumeric
label="X-Diameter"
unit="mm"
timeseries={x_value}
timeseries={x_diameter}
renderValue={(value) => value.toFixed(3)}
/>
</div>
)}
{y_value?.current && (
{y_diameter?.current && (
<div className="flex flex-row items-center gap-6">
<TimeSeriesValueNumeric
label="Y-Diameter"
unit="mm"
timeseries={y_value}
timeseries={y_diameter}
renderValue={(value) => value.toFixed(3)}
/>
</div>
)}
{roundness?.current && (
<div className="flex flex-row items-center gap-6">
<TimeSeriesValueNumeric
label="Roundness"
timeseries={roundness}
renderValue={(value) => value.toFixed(3)}
/>
</div>
@@ -10,18 +10,19 @@ import React from "react";
import { useLaser1 } from "./useLaser1";
export function Laser1GraphsPage() {
const { diameter, x_value, y_value, state } = useLaser1();
const { diameter, x_diameter, y_diameter, roundness, state } = useLaser1();
const syncHook = useGraphSync("diameter-group");
const targetDiameter = state?.laser_state?.target_diameter ?? 0;
const lowerTolerance = state?.laser_state?.lower_tolerance ?? 0;
const higherTolerance = state?.laser_state?.higher_tolerance ?? 0;
const isTwoAxis = !!x_value?.current || !!y_value?.current;
const isTwoAxis = !!x_diameter?.current || !!y_diameter?.current;
const diameterColor = "#3b82f6";
const xDiameterColor = "#ef4444";
const yDiameterColor = "#22c55e";
const roundnessColor = "#eab308";
const config: GraphConfig = {
title: "Diameter",
@@ -110,15 +111,20 @@ export function Laser1GraphsPage() {
],
},
{
newData: x_value,
newData: x_diameter,
color: xDiameterColor,
title: "X-Diameter",
},
{
newData: y_value,
newData: y_diameter,
color: yDiameterColor,
title: "Y-Diameter",
},
{
newData: roundness,
color: roundnessColor,
title: "Roundness",
},
]}
unit="mm"
renderValue={(value) => value.toFixed(3)}
@@ -28,8 +28,9 @@ import {
*/
export const liveValuesEventDataSchema = z.object({
diameter: z.number(),
x_value: z.number().nullable(),
y_value: z.number().nullable(),
x_diameter: z.number().nullable(),
y_diameter: z.number().nullable(),
roundness: z.number().nullable(),
});
/**
@@ -59,8 +60,9 @@ export type Laser1NamespaceStore = {
// Time series data for live values
diameter: TimeSeries;
x_value: TimeSeries;
y_value: TimeSeries;
x_diameter: TimeSeries;
y_diameter: TimeSeries;
roundness: TimeSeries;
};
// Constants for time durations
@@ -75,19 +77,19 @@ const { initialTimeSeries: diameter, insert: addDiameter } = createTimeSeries(
ONE_HOUR,
);
const { initialTimeSeries: x_value, insert: addXValue } = createTimeSeries(
const { initialTimeSeries: x_diameter, insert: addXDiameter } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
const { initialTimeSeries: y_diameter, insert: addYDiameter } =
createTimeSeries(TWENTY_MILLISECOND, ONE_SECOND, FIVE_SECOND, ONE_HOUR);
const { initialTimeSeries: roundness, insert: addRoundness } = createTimeSeries(
TWENTY_MILLISECOND,
ONE_SECOND,
FIVE_SECOND,
ONE_HOUR,
);
const { initialTimeSeries: y_value, insert: addYValue } = createTimeSeries(
TWENTY_MILLISECOND,
ONE_SECOND,
FIVE_SECOND,
ONE_HOUR,
);
/**
* Factory function to create a new Laser1 namespace store
* @returns A new Zustand store instance for Laser1 namespace
@@ -98,8 +100,9 @@ export const createLaser1NamespaceStore = (): StoreApi<Laser1NamespaceStore> =>
state: null,
defaultState: null,
diameter: diameter,
x_value: x_value,
y_value: y_value,
x_diameter,
y_diameter,
roundness: roundness,
};
});
@@ -143,31 +146,43 @@ export function laser1MessageHandler(
value: liveValuesEvent.data.diameter,
timestamp: event.ts,
};
if (liveValuesEvent.data.x_value !== null) {
const xValue: TimeSeriesValue = {
value: liveValuesEvent.data.x_value,
timestamp: event.ts,
};
updateStore((state) => ({
...state,
x_value: addXValue(state.x_value, xValue),
}));
}
if (liveValuesEvent.data.y_value !== null) {
const yValue: TimeSeriesValue = {
value: liveValuesEvent.data.y_value,
timestamp: event.ts,
};
updateStore((state) => ({
...state,
y_value: addYValue(state.y_value, yValue),
}));
}
updateStore((state) => ({
...state,
diameter: addDiameter(state.diameter, diameterValue),
}));
if (liveValuesEvent.data.x_diameter !== null) {
const xValue: TimeSeriesValue = {
value: liveValuesEvent.data.x_diameter,
timestamp: event.ts,
};
updateStore((state) => ({
...state,
x_diameter: addXDiameter(state.x_diameter, xValue),
}));
}
if (liveValuesEvent.data.y_diameter !== null) {
const yValue: TimeSeriesValue = {
value: liveValuesEvent.data.y_diameter,
timestamp: event.ts,
};
updateStore((state) => ({
...state,
y_diameter: addYDiameter(state.y_diameter, yValue),
}));
}
if (liveValuesEvent.data.roundness !== null) {
const roundnessValue: TimeSeriesValue = {
value: liveValuesEvent.data.roundness,
timestamp: event.ts,
};
updateStore((state) => ({
...state,
roundness: addRoundness(state.roundness, roundnessValue),
}));
}
} else {
handleUnhandledEventError(eventName);
}
@@ -11,7 +11,7 @@ import { produce } from "immer";
function useLaser(machine_identification_unique: MachineIdentificationUnique) {
// Get consolidated state and live values from namespace
const { state, defaultState, diameter, x_value, y_value } =
const { state, defaultState, diameter, x_diameter, y_diameter, roundness } =
useLaser1Namespace(machine_identification_unique);
// Single optimistic state for all state management
@@ -110,8 +110,9 @@ function useLaser(machine_identification_unique: MachineIdentificationUnique) {
// Live values (TimeSeries)
diameter,
x_value,
y_value,
x_diameter,
y_diameter,
roundness,
// Loading states
isLoading: stateOptimistic.isOptimistic,
+1 -1
View File
@@ -107,7 +107,7 @@ serviceConfig = {
environment = {
RUST_BACKTRACE = "full";
RUST_LOG = "info";
RUST_LOG = "debug";
};
};
+1
View File
@@ -19,6 +19,7 @@ use std::time::{Duration, Instant};
///
impl MachineAct for LaserMachine {
fn act(&mut self, now: Instant) {
self.update();
// 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.emit_live_values();
+3 -2
View File
@@ -21,8 +21,9 @@ use tracing::instrument;
pub struct LiveValuesEvent {
/// diameter measurement in mm
pub diameter: f64,
pub x_value: Option<f64>,
pub y_value: Option<f64>,
pub x_diameter: Option<f64>,
pub y_diameter: Option<f64>,
pub roundness: Option<f64>,
}
impl LiveValuesEvent {
+58 -17
View File
@@ -24,6 +24,12 @@ pub struct LaserMachine {
namespace: LaserMachineNamespace,
last_measurement_emit: Instant,
// laser values
diameter: Length,
x_diameter: Option<Length>,
y_diameter: Option<Length>,
roundness: Option<f64>,
//laser target configuration
laser_target: LaserTarget,
@@ -42,26 +48,16 @@ impl LaserMachine {
impl LaserMachine {
///diameter in mm
pub fn emit_live_values(&mut self) {
let laser_data = smol::block_on(async { self.laser.read().await.get_data().await });
let diameter = laser_data
.as_ref()
.map(|data| data.diameter.get::<millimeter>())
.unwrap_or(0.0);
let x_value = laser_data
.as_ref()
.and_then(|data| data.x_axis.as_ref())
.map(|x| x.get::<millimeter>());
let y_value = laser_data
.as_ref()
.and_then(|data| data.y_axis.as_ref())
.map(|y| y.get::<millimeter>());
let diameter = self.diameter.get::<millimeter>();
let x_diameter = self.x_diameter.map(|x| x.get::<millimeter>());
let y_diameter = self.y_diameter.map(|y| y.get::<millimeter>());
let roundness = self.roundness;
let live_values = LiveValuesEvent {
diameter,
x_value,
y_value,
x_diameter,
y_diameter,
roundness,
};
self.namespace
.emit(LaserEvents::LiveValues(live_values.build()));
@@ -107,7 +103,52 @@ impl LaserMachine {
self.laser_target.diameter = Length::new::<millimeter>(target_diameter);
self.emit_state();
}
///
/// Roundness = min(x, y) / max(x, y)
///
fn calculate_roundness(&mut self) -> Option<f64> {
match (self.x_diameter, self.y_diameter) {
(Some(x), Some(y)) => {
let x_val = x.get::<millimeter>();
let y_val = y.get::<millimeter>();
if x_val > 0.0 && y_val > 0.0 {
let roundness = f64::min(x_val, y_val) / f64::max(x_val, y_val);
Some(roundness)
} else if x_val == 0.0 && y_val == 0.0 {
Some(0.0)
} else {
None
}
}
_ => None,
}
}
pub fn update(&mut self) {
let laser_data = smol::block_on(async { self.laser.read().await.get_data().await });
self.diameter = Length::new::<millimeter>(
laser_data
.as_ref()
.map(|data| data.diameter.get::<millimeter>())
.unwrap_or(0.0),
);
self.x_diameter = laser_data
.as_ref()
.and_then(|data| data.x_axis.as_ref())
.cloned();
self.y_diameter = laser_data
.as_ref()
.and_then(|data| data.y_axis.as_ref())
.cloned();
self.roundness = self.calculate_roundness();
}
}
#[derive(Debug, Clone)]
pub struct LaserTarget {
diameter: Length,
+8 -1
View File
@@ -5,7 +5,10 @@ use crate::serial::{devices::laser::Laser, registry::SERIAL_DEVICE_REGISTRY};
use super::{LaserMachine, LaserTarget, api::LaserMachineNamespace};
use anyhow::Error;
use control_core::machines::new::MachineNewTrait;
use uom::si::{f64::Length, length::millimeter};
use uom::{
ConstZero,
si::{f64::Length, length::millimeter},
};
impl MachineNewTrait for LaserMachine {
fn new(
@@ -39,6 +42,10 @@ impl MachineNewTrait for LaserMachine {
last_measurement_emit: Instant::now(),
laser_target,
emitted_default_state: false,
diameter: Length::ZERO,
x_diameter: None,
y_diameter: None,
roundness: None,
};
// Emit initial state