Compare commits

..
Author SHA1 Message Date
rob 07c7ee43c2 increased the timeout for loop.rs (temporary fix) 2025-11-25 19:17:02 +01:00
Oshgnacknak fb85cc6db7 Merge pull request #896 from qitechgmbh/877-remove-dependencies-that-are-not-necessary-or-bloated
Remove futures crate
2025-11-24 23:13:55 +01:00
Robin Krämer 4e128ded36 Merge pull request #885 from qitechgmbh/699-add-min-and-max-diameter-on-laser-page2
699 add min and max diameter on laser page
2025-11-24 13:02:35 +01:00
Oshgnacknak f50388b141 Just restart failed tasks 2025-11-24 12:54:40 +01:00
ChristianandCopilot 4025a68fe1 Update electron/src/control/MinMaxValue.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-23 17:58:07 +01:00
ChristianandCopilot 7a05fa206f Update electron/src/control/MinMaxValue.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-23 17:58:07 +01:00
Christian 8594b06c3b Reorganize shared timeframe selector for clarity and improve layout for diameter range display 2025-11-23 17:58:07 +01:00
Christian 17523574d8 Add shared timeframe selector to Laser1ControlPage and expose TIMEFRAME_OPTIONS 2025-11-23 17:58:07 +01:00
Christian 7971797178 Refactor diameter range display for 2-axis lasers and improve layout 2025-11-23 17:58:07 +01:00
Christian a8b0837e58 Add MinMaxValue component for displaying diameter ranges 2025-11-23 17:58:07 +01:00
Oshgnacknak e419974844 Refactor init logic to work without futures crate 2025-11-22 17:30:37 +01:00
Oshgnacknak 773d0f6c15 Remove tonic 2025-11-22 16:15:48 +01:00
Oshgnacknak afc4e0d8b4 Completely remove opentelemetry and update dependencies 2025-11-22 16:15:47 +01:00
Robin Krämer 5e86988ac4 Merge pull request #907 from qitechgmbh/882-add-nozzle-docs-for-extruder
882 add nozzle docs for extruder
2025-11-19 11:38:07 +01:00
snacj 2d28c18b1a Added Heading for new Mechanical Diagram 2025-11-18 14:04:32 +01:00
snacj b19764281a Added Nozzle docs 2025-11-18 13:58:51 +01:00
snacj c25d63f781 Added Nozzle docs 2025-11-18 13:58:51 +01:00
Robin Krämer d79efc412f Merge pull request #904 from qitechgmbh/898-mock-machine-doesnt-work
898 mock machine doesnt work
2025-11-18 12:53:24 +01:00
rob 7ad74cf089 fixed formatting 2025-11-17 18:00:43 +01:00
rob 6073d37892 fixed remaining compile errors 2025-11-17 18:00:43 +01:00
rob c04937553f machines show up but dont get subscribed to 2025-11-17 18:00:43 +01:00
rob e08bdfa3c7 removed unnecessary and outdated docs 2025-11-17 18:00:43 +01:00
rob d282678b19 fixed compile errors for mock-machines 2025-11-17 18:00:43 +01:00
Robin Krämer 9ff4243350 Merge pull request #902 from qitechgmbh/2.12-releasenotes
updated Changelog
2025-11-17 16:38:48 +01:00
rob 6c79b33752 updated Changelog 2025-11-17 16:36:41 +01:00
rob 861b5da7a3 updated Changelog 2025-11-17 16:35:28 +01:00
53 changed files with 1203 additions and 923 deletions

No files matched your search

+16
View File
@@ -1,3 +1,19 @@
# `2.12.0`
_17.11.2025_
## General
- [#847](https://github.com/qitechgmbh/control/pull/847) Improved discovery and initialization
- [#859](https://github.com/qitechgmbh/control/pull/859) Improved update speed
- [#897](https://github.com/qitechgmbh/control/pull/897), [#899](https://github.com/qitechgmbh/control/pull/899), [#852](https://github.com/qitechgmbh/control/pull/852) Improved system stability
- [#817](https://github.com/qitechgmbh/control/pull/817) Added an update bar
## Laser
- [#857](https://github.com/qitechgmbh/control/pull/857) Fixed potential issues with 2 Axis Laser
## Extruder V2
- [#881](https://github.com/qitechgmbh/control/pull/881) Extended Temperature PID Configuration
# `2.11.0`
_21.10.2025_
Generated
+424 -407
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
-43
View File
@@ -1,43 +0,0 @@
# Installation Guide for Qitech-Control Software on Linux
This guide assumes you have an already installed linux distribution like debian,fedora etc.
It needs to a kernel that either has PREEMPT_RT patched or which we heavily recommend a mainline kernel later that 6.12, as versions afterwards have PREEMPT_RT integrated and can be switched with a kernel parameter.
Additionally it is assumed that at least four cpu cores are usable.
## Kernel Parameters
For our Ethercat control to run determinstically while also being as fast as possible you will need to configure a few kernel parameters.
Most Distributions use something like grub to persistently configure kernel parameters.
For Grub add the kernel parameters to the LINUX_COMMANDLINE_DEFAULT:
```
```
isolcpus=2,3 isolates the third and fourth core from the system scheduler, cutting down latency
nohz_full=2,3 isolates third anf fourth core from kernel interrupts
rcu_nocbs=2,3 isolates read copy update from the given cpu cores, you guessed it less latency
preempt="full" turns linux into a realtime capable OS. Offering deterministic Execution for programs
After you changed the kernel parameters you have to rebuild the initramfs and bootloader, with something like grub.
```
# On Most Systems you need this command
sudo update-grub
# Or this one
sudo update-grub2
```
If those commands dont work for you or you dont use grub then simply,
look up for your given distro what you need to do to persist kernel parameters.
## Installation On Generic Linux
This applies to any linux distro, that is not explicitily covered by this guide.
### Download Rust-toolchain
The easiest way is through rustup, simply execute the script as you home user and for everything keep it at defaults
### Download Npm and Node
The exact commands depend on which distro you use, but for most npm should exist as a package you can download in the package manager
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -720,3 +720,7 @@ The following persons must not work on the machine:
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 8](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_8-008.png)
![QiTech Pro Extruder Electrical Diagram 2025 - Sheet 9](images/manuals/extruder/QiTech_Pro_Extruder_Electrical_Diagram_2025_9-009.png)
## Mechanical Diagrams
![QiTech Pro Extruder Nozzle 2025](images/manuals/extruder/Nozzle.png)
+173
View File
@@ -0,0 +1,173 @@
import { Icon, IconName } from "@/components/Icon";
import React, { useState, useMemo } from "react";
import {
getUnitIcon,
renderValueToReactNode,
renderUnitSymbol,
Unit,
} from "./units";
import { Label } from "./Label";
import { TimeSeries, TimeSeriesValue } from "@/lib/timeseries";
type TimeframeOption = {
label: string;
value: number; // in milliseconds
};
export const TIMEFRAME_OPTIONS: TimeframeOption[] = [
{ label: "1 min", value: 1 * 60 * 1000 },
{ label: "5 min", value: 5 * 60 * 1000 },
{ label: "10 min", value: 10 * 60 * 1000 },
{ label: "30 min", value: 30 * 60 * 1000 },
{ label: "1 hour", value: 60 * 60 * 1000 },
];
type Props = {
label: string;
unit?: Unit;
timeseries: TimeSeries;
icon?: IconName;
renderValue?: (value: number) => string;
defaultTimeframe?: number; // in milliseconds, defaults to 5 minutes
/** Optional externally-controlled timeframe (ms). When provided, component becomes controlled. */
timeframe?: number;
/** Optional callback when timeframe is changed via this component's selector */
onTimeframeChange?: (t: number) => void;
/** When true, hide the built-in timeframe selector (useful when sharing a selector outside) */
hideSelector?: boolean;
};
export function MinMaxValue({
unit,
timeseries,
icon,
label,
renderValue,
defaultTimeframe = 5 * 60 * 1000, // 5 minutes default
timeframe,
onTimeframeChange,
hideSelector = false,
}: Props) {
const [internalTimeframe, setInternalTimeframe] = useState(defaultTimeframe);
const selectedTimeframe = timeframe ?? internalTimeframe;
// Calculate min/max from the timeseries using the selected timeframe
const { min, max, hasData } = useMemo(() => {
// Use short series for timeframes <= 5 seconds, otherwise use long series
// Short series typically has ~5 second retention with high resolution
// Long series typically has ~1 hour retention with lower resolution
const series =
selectedTimeframe <= 5000 ? timeseries.short : timeseries.long;
// Manually calculate min/max to exclude zero values (which might be invalid readings)
const cutoffTime = series.lastTimestamp - selectedTimeframe;
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
let hasValidData = false;
let dataCount = 0;
const { values: raw, index, size, validCount } = series;
const startIdx = validCount < size ? 0 : index;
for (let i = 0; i < validCount; i++) {
const idx = (startIdx + i) % size;
const val = raw[idx];
if (
val &&
val.timestamp > 0 &&
val.timestamp >= cutoffTime &&
val.value > 0
) {
hasValidData = true;
dataCount++;
if (val.value < min) min = val.value;
if (val.value > max) max = val.value;
}
}
if (!hasValidData) {
return { min: 0, max: 0, hasData: false };
}
return { min, max, hasData: true };
}, [
timeseries.short,
timeseries.long,
timeseries.current,
selectedTimeframe,
]);
return (
<div className="flex w-full flex-col gap-3">
<Label label={label}>
<div className="flex flex-row items-center gap-6">
{/* Min Value */}
<div className="flex flex-row items-center gap-2">
<Icon
name={icon ?? (unit ? getUnitIcon(unit) : undefined)}
className="size-5 text-blue-500"
/>
<div className="flex flex-col">
<span className="text-xs text-gray-500">Min</span>
<div className="flex flex-row items-center gap-1">
<span className="font-mono text-xl font-bold">
{hasData
? renderValueToReactNode(min, unit, renderValue)
: "-"}
</span>
{hasData && (
<span className="text-sm">{renderUnitSymbol(unit)}</span>
)}
</div>
</div>
</div>
{/* Max Value */}
<div className="flex flex-row items-center gap-2">
<Icon
name={icon ?? (unit ? getUnitIcon(unit) : undefined)}
className="size-5 text-red-500"
/>
<div className="flex flex-col">
<span className="text-xs text-gray-500">Max</span>
<div className="flex flex-row items-center gap-1">
<span className="font-mono text-xl font-bold">
{hasData
? renderValueToReactNode(max, unit, renderValue)
: "-"}
</span>
{hasData && (
<span className="text-sm">{renderUnitSymbol(unit)}</span>
)}
</div>
</div>
</div>
</div>
</Label>
{/* Timeframe Selector (hidden when sharing selector externally) */}
{!hideSelector && (
<div className="flex flex-row flex-wrap gap-2">
{TIMEFRAME_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => {
if (onTimeframeChange) onTimeframeChange(option.value);
else setInternalTimeframe(option.value);
}}
className={`rounded-md px-3 py-1 text-sm transition-colors ${
selectedTimeframe === option.value
? "bg-blue-500 text-white"
: "bg-gray-200 text-gray-700 hover:bg-gray-300"
}`}
>
{option.label}
</button>
))}
</div>
)}
</div>
);
}
@@ -3,6 +3,7 @@ import { Page } from "@/components/Page";
import React from "react";
import { ControlGrid } from "@/control/ControlGrid";
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
import { MinMaxValue, TIMEFRAME_OPTIONS } from "@/control/MinMaxValue";
import { EditValue } from "@/control/EditValue";
import { Label } from "@/control/Label";
@@ -28,6 +29,12 @@ export function Laser1ControlPage() {
const targetDiameter = state?.laser_state?.target_diameter ?? 0;
const lowerTolerance = state?.laser_state?.lower_tolerance ?? 0;
const higherTolerance = state?.laser_state?.higher_tolerance ?? 0;
// Detect if this is a 2-axis laser
const isTwoAxis = !!x_diameter?.current || !!y_diameter?.current;
// Shared timeframe state (default 5 minutes)
const [timeframe, setTimeframe] = React.useState<number>(5 * 60 * 1000);
return (
<Page>
<ControlGrid columns={2}>
@@ -77,6 +84,57 @@ export function Laser1ControlPage() {
/>
</div>
)}
<div className="mt-4 border-t pt-4">
{isTwoAxis ? (
// For 2-axis lasers: show diameter and roundness min/max side by side
<div className="grid grid-cols-2 gap-4">
<MinMaxValue
label="Diameter Range"
unit="mm"
timeseries={diameter}
renderValue={(value) => value.toFixed(3)}
timeframe={timeframe}
hideSelector
/>
{roundness?.current && (
<MinMaxValue
label="Roundness Range"
timeseries={roundness}
renderValue={(value) => value.toFixed(3)}
timeframe={timeframe}
hideSelector
/>
)}
</div>
) : (
// For single-axis lasers: show only diameter min/max with shared selector
<MinMaxValue
label="Diameter Range"
unit="mm"
timeseries={diameter}
renderValue={(value) => value.toFixed(3)}
timeframe={timeframe}
hideSelector
/>
)}
{/* Shared timeframe selector for diameter/roundness */}
<div className="mt-3 flex flex-row flex-wrap gap-2">
{TIMEFRAME_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => setTimeframe(option.value)}
className={`rounded-md px-3 py-1 text-sm transition-colors ${
timeframe === option.value
? "bg-blue-500 text-white"
: "bg-gray-200 text-gray-700 hover:bg-gray-300"
}`}
>
{option.label}
</button>
))}
</div>
</div>
</ControlCard>
<ControlCard title="Settings">
<Label label="Set Target Diameter">
+1 -1
View File
@@ -10,10 +10,10 @@ workspace = true
clap = { version = "4.5.40", features = ["derive"] }
ethercrab = "0.6.0"
interfaces = "0.0.9"
smol = "2.0.2"
tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] }
ethercat_hal = { path = "../ethercat-hal" }
control_core = { path = "../control-core" }
futures = "0.3.31"
anyhow = "1.0.100"
+3 -3
View File
@@ -7,7 +7,7 @@ use ethercat_hal::{
helpers::ethercrab_types::EthercrabSubDeviceGroupPreoperational,
};
use ethercrab::MainDevice;
use futures::executor::block_on;
use smol;
use crate::{MAX_SUBDEVICES, PDI_LEN, print::print_markdown_table};
@@ -34,7 +34,7 @@ pub fn ls(
let driver = device_from_subdevice_identity(&identity);
let identification_adresses = get_identification_addresses(&identity, device.name());
let identification = match identification_adresses {
Ok(_) => block_on(machine_device_identification(&device, maindevice)),
Ok(_) => smol::block_on(machine_device_identification(&device, maindevice)),
Err(_) => Err(anyhow!("")),
};
@@ -122,7 +122,7 @@ pub fn ls(
print_markdown_table(&table, true);
// Put group into op state
match block_on(group.into_op(maindevice)) {
match smol::block_on(group.into_op(maindevice)) {
Ok(_) => println!("Successfully put group into OP state"),
Err(e) => println!("Failed to put group into OP state: {}", e),
}
+5 -4
View File
@@ -2,9 +2,9 @@ use std::sync::Arc;
use dump::dump_eeprom;
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, std::ethercat_now};
use futures::executor::block_on;
use read::read_eeprom;
use restore::restore_eeoprom;
use smol;
/// Maximum number of SubDevices that can be stored. This must be a power of 2 greater than 1.
const MAX_SUBDEVICES: usize = 16;
@@ -57,7 +57,7 @@ async fn main() {
.get_one::<usize>("SUBDEVICE")
.expect("subdevice index is required");
let file = sub_matches.get_one::<String>("file");
let result = block_on(dump_eeprom(&group, &maindevice, *subdevice_index, file));
let result = smol::block_on(dump_eeprom(&group, &maindevice, *subdevice_index, file));
if let Err(e) = result {
eprintln!("Error reading EEPROM: {}", e);
}
@@ -69,7 +69,8 @@ async fn main() {
let file = sub_matches
.get_one::<String>("file")
.expect("file is required");
let result = block_on(restore_eeoprom(&group, &maindevice, *subdevice_index, file));
let result =
smol::block_on(restore_eeoprom(&group, &maindevice, *subdevice_index, file));
if let Err(e) = result {
eprintln!("Error writing EEPROM: {}", e);
}
@@ -78,7 +79,7 @@ async fn main() {
let file = sub_matches
.get_one::<String>("file")
.expect("file is required");
let result = block_on(read_eeprom(file));
let result = smol::block_on(read_eeprom(file));
if let Err(e) = result {
eprintln!("Error parsing EEPROM: {}", e);
}
-1
View File
@@ -14,7 +14,6 @@ units = { path = "../units" }
bitvec = { version = "1.0.1", features = ["alloc"] }
smol = "2.0.2"
rand = "0.9.2"
futures = "0.3.31"
tracing = "0.1.41"
[dev-dependencies]
+1 -2
View File
@@ -1,7 +1,6 @@
use std::fmt;
use std::sync::Arc;
use futures::executor::block_on;
use physical::{AnalogInputRange, AnalogInputValue};
use smol::lock::RwLock;
@@ -40,7 +39,7 @@ impl AnalogInput {
let get_input = Box::new(move || -> AnalogInputInput {
let device2 = Arc::clone(&device);
let port_clone = port.clone();
block_on(async move {
smol::block_on(async move {
let device = device2.read().await;
device.get_input(port_clone)
})
+1 -2
View File
@@ -1,6 +1,5 @@
use std::{fmt, sync::Arc};
use futures::executor::block_on;
use smol::lock::RwLock;
/// Digital Input (DI) device
@@ -27,7 +26,7 @@ impl DigitalInput {
let port2 = port;
let device2 = device.clone();
let get_input = Box::new(move || {
let device = block_on(device2.read());
let device = smol::block_on(device2.read());
device.get_input(port2.clone())
});
+2 -3
View File
@@ -1,6 +1,5 @@
use std::{fmt, sync::Arc};
use futures::executor::block_on;
use smol::lock::RwLock;
/// Digital Output (DO) device
@@ -29,7 +28,7 @@ impl DigitalOutput {
let port1 = port.clone();
let device1 = device.clone();
let set_output = Box::new(move |value: DigitalOutputOutput| {
let mut device = block_on(device1.write());
let mut device = smol::block_on(device1.write());
device.set_output(port1.clone(), value);
});
@@ -37,7 +36,7 @@ impl DigitalOutput {
let port2 = port;
let device2 = device.clone();
let get_output = Box::new(move || -> DigitalOutputOutput {
let device = block_on(device2.read());
let device = smol::block_on(device2.read());
device.get_output(port2.clone())
});
+4 -5
View File
@@ -1,6 +1,5 @@
use std::{fmt, sync::Arc};
use futures::executor::block_on;
use smol::lock::RwLock;
/// Encoder Input device
@@ -34,7 +33,7 @@ impl EncoderInput {
let port_counter = port.clone();
let device_counter = device.clone();
let get_counter = Box::new(move || {
let device = block_on(device_counter.read());
let device = smol::block_on(device_counter.read());
device.get_counter_value(port_counter.clone())
});
@@ -42,7 +41,7 @@ impl EncoderInput {
let port_frequency = port.clone();
let device_frequency = device.clone();
let get_frequency = Box::new(move || {
let device = block_on(device_frequency.read());
let device = smol::block_on(device_frequency.read());
device.get_frequency(port_frequency.clone())
});
@@ -50,7 +49,7 @@ impl EncoderInput {
let port_period = port.clone();
let device_period = device.clone();
let get_period = Box::new(move || {
let device = block_on(device_period.read());
let device = smol::block_on(device_period.read());
device.get_period(port_period.clone())
});
@@ -58,7 +57,7 @@ impl EncoderInput {
let port_set = port;
let device_set = device;
let set_counter = Box::new(move |value: u32| {
let mut device = block_on(device_set.write());
let mut device = smol::block_on(device_set.write());
device.set_counter(port_set.clone(), value)
});
+3 -4
View File
@@ -1,6 +1,5 @@
use std::{fmt, sync::Arc};
use futures::executor::block_on;
use smol::lock::RwLock;
/// Pulse Train Output (PTO) device
@@ -29,21 +28,21 @@ impl<'device> PulseTrainOutput {
// build sync write closure
let device1 = device.clone();
let set_output = Box::new(move |value: PulseTrainOutputOutput| {
let mut device = block_on(device1.write());
let mut device = smol::block_on(device1.write());
device.set_output(port, value);
});
// build sync get closure
let device2 = device.clone();
let get_output = Box::new(move || -> PulseTrainOutputOutput {
let device = block_on(device2.read());
let device = smol::block_on(device2.read());
device.get_output(port)
});
// build sync get closure
let device2 = device;
let get_input = Box::new(move || -> PulseTrainOutputInput {
let device = block_on(device2.read());
let device = smol::block_on(device2.read());
device.get_input(port)
});
@@ -2,7 +2,6 @@ use std::{fmt, sync::Arc};
use crate::helpers::el70xx_velocity_converter::EL70x1VelocityConverter;
use anyhow::Error;
use futures::executor::block_on;
use smol::lock::RwLock;
/// Pulse Train Output (PTO) device
@@ -47,7 +46,7 @@ impl<'device> StepperVelocityEL70x1 {
// build sync get closure
let device2 = device.clone();
let get_input = Box::new(move || -> Result<StepperVelocityEL70x1Input, Error> {
block_on(async {
smol::block_on(async {
let device = device2.read().await;
device.get_input(port)
})
@@ -55,7 +54,7 @@ impl<'device> StepperVelocityEL70x1 {
let device3 = device.clone();
let get_output = Box::new(move || -> Result<StepperVelocityEL70x1Output, Error> {
block_on(async {
smol::block_on(async {
let device = device3.read().await;
device.get_output(port)
})
@@ -65,7 +64,7 @@ impl<'device> StepperVelocityEL70x1 {
let device3 = device;
let get_speed_range = Box::new(
move || -> Result<crate::shared_config::el70x1::EL70x1SpeedRange, Error> {
block_on(async {
smol::block_on(async {
let device = device3.read().await;
Ok(device.get_speed_range(port))
})
+3 -5
View File
@@ -1,4 +1,3 @@
use futures::executor::block_on;
use smol::lock::RwLock;
use crate::pdo::basic::Limit;
@@ -29,10 +28,9 @@ impl TemperatureInput {
let get_input = Box::new(move || {
let device2 = device2.clone();
let port_clone = port2.clone();
block_on(async move {
let device = device2.read().await;
device.get_input(port_clone)
})
let device = device2.read_blocking();
device.get_input(port_clone)
});
Self { get_input }
}
-1
View File
@@ -19,7 +19,6 @@ euclid = "0.22.11"
# concurrency
smol = "2.0.2"
futures = "0.3.31"
# web
serde_json = "1.0.143"
+32 -2
View File
@@ -1,9 +1,17 @@
use crate::machines::extruder1::mock::ExtruderV2;
use control_core::machines::new::MachineAct;
use super::ExtruderV2;
use crate::MachineAct;
use crate::MachineMessage;
use std::time::{Duration, Instant};
impl MachineAct for ExtruderV2 {
fn act(&mut self, now: Instant) {
let msg = self.api_receiver.try_recv();
match msg {
Ok(msg) => {
let _res = self.act_machine_message(msg);
}
Err(_) => (),
};
// 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.maybe_emit_state_event();
@@ -12,4 +20,26 @@ impl MachineAct for ExtruderV2 {
self.last_measurement_emit = now;
}
}
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
match msg {
MachineMessage::SubscribeNamespace(namespace) => {
self.namespace.namespace = Some(namespace);
self.emit_state();
tracing::info!("extruder1 received subscribe");
}
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
MachineMessage::HttpApiJsonRequest(value) => {
use crate::MachineApi;
let _res = self.api_mutate(value);
}
MachineMessage::ConnectToMachine(_machine_connection) => (),
MachineMessage::DisconnectMachine(_machine_connection) =>
/*Doesnt connec to any Machine do nothing*/
{
()
}
}
}
}
+12 -6
View File
@@ -1,8 +1,8 @@
use crate::machines::extruder1::{HeatingType, api::Mutation, mock::ExtruderV2};
use control_core::machines::api::MachineApi;
use control_core::socketio::namespace::Namespace;
use smol::lock::Mutex;
use std::sync::Arc;
use super::ExtruderV2;
use crate::{
MachineApi,
extruder1::{HeatingType, api::Mutation},
};
impl MachineApi for ExtruderV2 {
fn api_mutate(&mut self, request_body: serde_json::Value) -> Result<(), anyhow::Error> {
@@ -45,7 +45,13 @@ impl MachineApi for ExtruderV2 {
Ok(())
}
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
fn api_event_namespace(
&mut self,
) -> std::option::Option<control_core::socketio::namespace::Namespace> {
self.namespace.namespace.clone()
}
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
self.api_sender.clone()
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
use crate::machines::extruder1::{
use crate::extruder1::{
ExtruderV2Mode, HeatingType,
api::{ExtruderV2Events, LiveValuesEvent, ModeState, PidSettings, StateEvent, TemperaturePid},
mock::ExtruderV2,
};
use control_core::{
helpers::hasher_serializer::hash_with_serde_model,
socketio::{event::BuildEvent, namespace::NamespaceCacheingLogic},
+28 -5
View File
@@ -1,14 +1,21 @@
#[cfg(feature = "mock-machine")]
use control_core::machines::identification::MachineIdentification;
use smol::channel::Receiver;
#[cfg(feature = "mock-machine")]
use control_core::machines::identification::MachineIdentificationUnique;
use smol::channel::Sender;
#[cfg(feature = "mock-machine")]
use control_core_derive::Machine;
use crate::{AsyncThreadMessage, Machine};
#[cfg(feature = "mock-machine")]
use crate::{
MachineIdentificationUnique, MachineMessage, machine_identification::MachineIdentification,
};
#[cfg(feature = "mock-machine")]
use std::time::Instant;
#[cfg(feature = "mock-machine")]
use crate::machines::{
use crate::{
MACHINE_EXTRUDER_V1, VENDOR_QITECH,
extruder1::{
ExtruderV2Mode,
@@ -31,9 +38,25 @@ pub mod mock_emit;
pub mod new;
#[cfg(feature = "mock-machine")]
#[derive(Debug, Machine)]
impl Machine for ExtruderV2 {
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
self.machine_identification_unique.clone()
}
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
self.main_sender.clone()
}
}
#[cfg(feature = "mock-machine")]
#[derive(Debug)]
pub struct ExtruderV2 {
api_receiver: Receiver<MachineMessage>,
api_sender: Sender<MachineMessage>,
main_sender: Option<Sender<AsyncThreadMessage>>,
machine_identification_unique: MachineIdentificationUnique,
namespace: ExtruderV2Namespace,
last_measurement_emit: Instant,
last_status_hash: Option<u64>,
+19 -14
View File
@@ -1,18 +1,19 @@
use crate::machines::extruder1::{
ExtruderV2Mode,
api::{
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
TemperaturePidStates,
use crate::{
MachineNewHardware, MachineNewParams, MachineNewTrait,
extruder1::{
ExtruderV2Mode,
api::{
ExtruderSettingsState, ExtruderV2Namespace, HeatingState, HeatingStates,
InverterStatusState, ModeState, MotorStatusValues, PidSettings, PidSettingsStates,
PressureState, RegulationState, RotationState, ScrewState, TemperaturePid,
TemperaturePidStates,
},
mock::ExtruderV2,
},
mock::ExtruderV2,
};
impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
fn new(
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
) -> Result<Self, anyhow::Error>
impl MachineNewTrait for ExtruderV2 {
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
where
Self: Sized,
{
@@ -20,19 +21,23 @@ impl control_core::machines::new::MachineNewTrait for ExtruderV2 {
// For the mock machine, we don't need to actually use the hardware
// We just validate that we have the expected hardware type
match params.hardware {
control_core::machines::new::MachineNewHardware::Serial(_) => {
MachineNewHardware::Serial(_) => {
// For serial mode, we could potentially use the serial device if needed
// but for a mock machine, we'll just note it and proceed
}
control_core::machines::new::MachineNewHardware::Ethercat(_) => {
MachineNewHardware::Ethercat(_) => {
// For ethercat mode, we could potentially use the ethercat devices
// but for a mock machine, we'll just note it and proceed
}
}
let now = std::time::Instant::now();
let (sender, receiver) = smol::channel::unbounded();
let mut extruder_mock_machine = Self {
main_sender: params.main_thread_channel.clone(),
api_receiver: receiver,
api_sender: sender,
machine_identification_unique: params.get_machine_identification_unique(),
namespace: ExtruderV2Namespace {
namespace: params.namespace.clone(),
+8 -2
View File
@@ -2,17 +2,22 @@
use std::time::Instant;
#[cfg(not(feature = "mock-machine"))]
use serde::{Deserialize, Serialize};
use smol::channel::Receiver;
#[cfg(not(feature = "mock-machine"))]
use smol::channel::Sender;
use serde::{Deserialize, Serialize};
#[cfg(not(feature = "mock-machine"))]
use units::electric_current::ampere;
#[cfg(not(feature = "mock-machine"))]
use units::electric_potential::volt;
use crate::{AsyncThreadMessage, Machine};
use units::f64::*;
use units::thermodynamic_temperature::degree_celsius;
use crate::{AsyncThreadMessage, Machine};
#[cfg(not(feature = "mock-machine"))]
use crate::{
MACHINE_EXTRUDER_V1, MachineMessage, VENDOR_QITECH,
@@ -94,6 +99,7 @@ pub struct ExtruderV2 {
emitted_default_state: bool,
}
#[cfg(not(feature = "mock-machine"))]
impl Machine for ExtruderV2 {
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
self.machine_identification_unique.clone()
@@ -6,7 +6,6 @@ use control_core::{
transmission::{Transmission, fixed::FixedTransmission},
};
use ethercat_hal::io::analog_input::AnalogInput;
use futures::executor::block_on;
use units::angular_velocity::revolution_per_minute;
use units::electric_current::milliampere;
use units::f64::*;
@@ -206,7 +205,7 @@ impl ScrewSpeedController {
pub fn update(&mut self, now: Instant, is_extruding: bool) {
// TODO: move this logic elsewhere or make non async
block_on(self.inverter.act(now));
smol::block_on(self.inverter.act(now));
let wiring_error = self.get_wiring_error();
if wiring_error {
+1 -1
View File
@@ -1,5 +1,5 @@
#[cfg(feature = "mock-machine")]
use crate::machines::{extruder1::mock::ExtruderV2, mock::MockMachine, winder2::mock::Winder2};
use crate::{extruder1::mock::ExtruderV2, mock::MockMachine, winder2::mock::Winder2};
use crate::{Machine, MachineNewParams, machine_identification::MachineIdentification};
#[cfg(not(feature = "mock-machine"))]
@@ -1,16 +1,15 @@
use std::sync::Arc;
use control_core::{
helpers::hashing::{byte_folding_u16, hash_djb2},
machines::identification::{
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
use smol::lock::RwLock;
use crate::{
MACHINE_EXTRUDER_V1, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
},
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
};
use smol::lock::RwLock;
use crate::machines::{MACHINE_EXTRUDER_V1, VENDOR_QITECH};
/// Mock serial device for testing MockMachine
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
+6 -7
View File
@@ -1,16 +1,15 @@
use std::sync::Arc;
use control_core::{
helpers::hashing::{byte_folding_u16, hash_djb2},
machines::identification::{
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
use smol::lock::RwLock;
use crate::{
MACHINE_MOCK, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
},
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
};
use smol::lock::RwLock;
use crate::machines::{MACHINE_MOCK, VENDOR_QITECH};
/// Mock serial device for testing MockMachine
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
@@ -1,16 +1,13 @@
use std::sync::Arc;
use control_core::{
helpers::hashing::{byte_folding_u16, hash_djb2},
machines::identification::{
use crate::{
MACHINE_WINDER_V1, SerialDevice, SerialDeviceNew, SerialDeviceNewParams, VENDOR_QITECH,
machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationSerial, DeviceIdentification,
DeviceMachineIdentification, MachineIdentification, MachineIdentificationUnique,
},
serial::{SerialDevice, SerialDeviceNew, SerialDeviceNewParams},
};
use control_core::helpers::hashing::{byte_folding_u16, hash_djb2};
use smol::lock::RwLock;
use crate::machines::{MACHINE_WINDER_V1, VENDOR_QITECH};
use std::sync::Arc;
/// Mock serial device for testing MockMachine
/// This provides a minimal SerialDevice implementation that doesn't require actual hardware
+13 -22
View File
@@ -1,6 +1,5 @@
use serialport::UsbPortInfo;
use smol::Task;
use std::{collections::HashMap, time::Duration};
use std::collections::HashMap;
/*
*@author: Alisher Darmenov
*@company: QiTech
@@ -75,38 +74,30 @@ impl SerialDetection {
}
}
// Async function that runs until at least one serial device is found.
pub async fn find_serial() -> Option<HashMap<String, UsbPortInfo>> {
smol::Timer::after(Duration::from_secs(1)).await;
/*
let devices = SerialDetection::detect_devices();
if !devices.is_empty() {
return Some(devices);
} else {
return None;
pub async fn start_serial_discovery(app_state: Arc<>) {
loop {
let devices = SerialDetection::detect_devices();
if !devices.is_empty() {
handle_serial_device_hotplug(app_state.clone(), devices).await;
}
smol::Timer::after(Duration::from_secs(1)).await;
}
}
// Returns a smol::Task that resolves when atleast one device is found.
pub fn start_serial_discovery() -> Task<Option<HashMap<String, UsbPortInfo>>> {
smol::spawn(find_serial())
}
/*
pub async fn handle_serial_device_hotplug(
app_state: Arc<SharedState>,
map: Option<HashMap<String, UsbPortInfo>>,
map: HashMap<String, UsbPortInfo>,
) {
let laser_ident = SerialDeviceIdentification {
vendor_id: 0x0403,
product_id: 0x6001,
};
let laser = match map {
Some(map) => SerialDetection::get_path_by_id(laser_ident, map),
None => None,
};
let laser = SerialDetection::get_path_by_id(laser_ident, map);
let mut unique_ident: Option<MachineIdentificationUnique> = None;
+12 -15
View File
@@ -2,25 +2,22 @@
mod winder2_imports {
pub use super::super::Winder2Mode;
pub use super::super::puller_speed_controller::{GearRatio, PullerRegulationMode};
pub use serde::Deserialize;
pub use serde::Serialize;
pub use std::{sync::Arc, time::Duration};
pub use control_core::{
machines::{
connection::MachineCrossConnectionState, identification::MachineIdentificationUnique,
},
socketio::{
event::{Event, GenericEvent},
namespace::{
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
cache_first_and_last_event,
},
pub use control_core::socketio::{
event::{Event, GenericEvent},
namespace::{
CacheFn, CacheableEvents, Namespace, NamespaceCacheingLogic, cache_duration,
cache_first_and_last_event,
},
};
pub use control_core_derive::BuildEvent;
pub use serde::{Deserialize, Serialize};
pub use serde_json::Value;
pub use smol::lock::Mutex;
pub use std::{
sync::Arc,
time::{Duration, Instant},
};
pub use tracing::instrument;
}
+32 -2
View File
@@ -1,13 +1,43 @@
use crate::machines::winder2::mock::Winder2;
use control_core::machines::new::MachineAct;
use super::Winder2;
use crate::MachineAct;
use crate::MachineMessage;
use std::time::{Duration, Instant};
impl MachineAct for Winder2 {
fn act(&mut self, now: Instant) {
let msg = self.api_receiver.try_recv();
match msg {
Ok(msg) => {
let _res = self.act_machine_message(msg);
}
Err(_) => (),
};
// 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();
self.last_measurement_emit = now;
}
}
fn act_machine_message(&mut self, msg: crate::MachineMessage) {
match msg {
MachineMessage::SubscribeNamespace(namespace) => {
self.namespace.namespace = Some(namespace);
self.emit_state();
tracing::info!("extruder1 received subscribe");
}
MachineMessage::UnsubscribeNamespace => self.namespace.namespace = None,
MachineMessage::HttpApiJsonRequest(value) => {
use crate::MachineApi;
let _res = self.api_mutate(value);
}
MachineMessage::ConnectToMachine(_machine_connection) => (),
MachineMessage::DisconnectMachine(_machine_connection) =>
/*Doesnt connec to any Machine do nothing*/
{
()
}
}
}
}
+10 -9
View File
@@ -1,12 +1,7 @@
use std::{sync::Arc, time::Instant};
use control_core::{machines::api::MachineApi, socketio::namespace::Namespace};
use serde_json::Value;
use smol::lock::Mutex;
use crate::machines::winder2::api::Mutation;
use super::Winder2;
use crate::{MachineApi, winder2::api::Mutation};
use serde_json::Value;
use std::time::Instant;
impl MachineApi for Winder2 {
fn api_mutate(&mut self, request_body: Value) -> Result<(), anyhow::Error> {
@@ -61,7 +56,13 @@ impl MachineApi for Winder2 {
Ok(())
}
fn api_event_namespace(&mut self) -> Arc<Mutex<Namespace>> {
fn api_event_namespace(
&mut self,
) -> std::option::Option<control_core::socketio::namespace::Namespace> {
self.namespace.namespace.clone()
}
fn api_get_sender(&self) -> smol::channel::Sender<crate::MachineMessage> {
self.api_sender.clone()
}
}
+27 -16
View File
@@ -1,26 +1,23 @@
use std::time::Instant;
use crate::machines::winder2::Winder2Mode;
use crate::machines::winder2::api::LiveValuesEvent;
use crate::machines::winder2::api::{
ModeState, SpoolAutomaticActionMode, StateEvent, Winder2Events,
};
use crate::machines::winder2::puller_speed_controller::{GearRatio, PullerRegulationMode};
use crate::machines::winder2::spool_speed_controller::SpoolSpeedControllerType;
use crate::machines::{MACHINE_WINDER_V1, VENDOR_QITECH};
use control_core::machines::identification::{MachineIdentification, MachineIdentificationUnique};
use super::Winder2;
use crate::machine_identification::{MachineIdentification, MachineIdentificationUnique};
use crate::winder2::Winder2Mode;
use crate::winder2::api::LiveValuesEvent;
use crate::winder2::api::{ModeState, SpoolAutomaticActionMode, StateEvent, Winder2Events};
use crate::winder2::puller_speed_controller::{GearRatio, PullerRegulationMode};
use crate::winder2::spool_speed_controller::SpoolSpeedControllerType;
use crate::{MACHINE_WINDER_V1, VENDOR_QITECH};
use control_core::socketio::event::BuildEvent;
use control_core::socketio::namespace::NamespaceCacheingLogic;
use super::Winder2;
impl Winder2 {
pub const MACHINE_IDENTIFICATION: MachineIdentification = MachineIdentification {
vendor: VENDOR_QITECH,
machine: MACHINE_WINDER_V1,
};
pub fn stop_or_pull_spool_reset(&mut self, now: Instant) {}
pub fn stop_or_pull_spool_reset(&mut self, _now: Instant) {}
/// Implement Mode
pub fn set_mode(&mut self, mode: &Winder2Mode) {
self.mode_state = ModeState {
@@ -86,6 +83,20 @@ impl Winder2 {
}
pub fn build_state_event(&mut self) -> StateEvent {
use crate::MachineCrossConnectionState;
let connected_machine = self.connected_machines.get(0);
let ident = match connected_machine {
Some(machine) => Some(machine.ident.clone()),
None => None,
};
let cross_conn = MachineCrossConnectionState {
machine_identification_unique: ident,
is_available: connected_machine.is_some(),
};
StateEvent {
is_default_state: self.is_default_state,
traverse_state: self.traverse_state.clone(),
@@ -94,7 +105,7 @@ impl Winder2 {
mode_state: self.mode_state.clone(),
tension_arm_state: self.tension_arm_state.clone(),
spool_speed_controller_state: self.spool_speed_controller_state.clone(),
connected_machine_state: self.connected_machine_state.clone(),
connected_machine_state: cross_conn,
}
}
@@ -110,7 +121,7 @@ impl Winder2 {
/// It will set [`Self::spool_mode`]
fn set_traverse_mode(&mut self, mode: &Winder2Mode) {
self.mode_state = ModeState {
mode: crate::machines::winder2::api::Mode::Standby,
mode: crate::winder2::api::Mode::Standby,
can_wind: true,
};
self.emit_state();
@@ -227,7 +238,7 @@ impl Winder2 {
/// set connected buffer
pub fn set_connected_buffer(
&mut self,
machine_identification_unique: MachineIdentificationUnique,
_machine_identification_unique: MachineIdentificationUnique,
) {
self.emit_state();
}
@@ -235,7 +246,7 @@ impl Winder2 {
/// disconnect buffer
pub fn disconnect_buffer(
&mut self,
machine_identification_unique: MachineIdentificationUnique,
_machine_identification_unique: MachineIdentificationUnique,
) {
self.emit_state();
}
+29 -9
View File
@@ -1,21 +1,21 @@
use std::time::Instant;
use control_core::machines::{
connection::MachineCrossConnectionState, identification::MachineIdentificationUnique,
};
use control_core_derive::Machine;
pub mod act;
pub mod api;
pub mod mock_emit;
pub mod new;
use crate::machines::winder2::api::Winder2Namespace;
use super::api::{
ModeState, PullerState, SpoolAutomaticActionState, SpoolSpeedControllerState, TensionArmState,
TraverseState,
TraverseState, Winder2Namespace,
};
use crate::{
AsyncThreadMessage, Machine, MachineConnection, MachineMessage,
machine_identification::MachineIdentificationUnique,
};
use smol::channel::{Receiver, Sender};
#[derive(Debug, Machine)]
#[derive(Debug)]
pub struct Winder2 {
machine_identification_unique: MachineIdentificationUnique,
namespace: Winder2Namespace,
@@ -33,8 +33,18 @@ pub struct Winder2 {
pub tension_arm_state: TensionArmState,
/// spool speed controller state
pub spool_speed_controller_state: SpoolSpeedControllerState,
/// connected machine state
pub connected_machine_state: MachineCrossConnectionState,
/// Receive from Api or MainThread
api_receiver: Receiver<MachineMessage>,
api_sender: Sender<MachineMessage>,
/// Communicate with main thread
main_sender: Option<Sender<AsyncThreadMessage>>,
/// All currently "connected" Machines
connected_machines: Vec<MachineConnection>,
/// Defaults to limit of 2
max_connected_machines: usize,
}
impl std::fmt::Display for Winder2 {
@@ -42,3 +52,13 @@ impl std::fmt::Display for Winder2 {
write!(f, "Winder2")
}
}
impl Machine for Winder2 {
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
self.machine_identification_unique.clone()
}
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>> {
self.main_sender.clone()
}
}
+18 -14
View File
@@ -1,16 +1,15 @@
use crate::machines::winder2::{
api::{
use crate::{
MachineNewParams, MachineNewTrait,
winder2::api::{
ModeState, PullerState, SpoolAutomaticActionState, SpoolSpeedControllerState,
TensionArmState, TraverseState, Winder2Namespace,
},
mock::Winder2,
};
use control_core::machines::connection::MachineCrossConnectionState;
impl control_core::machines::new::MachineNewTrait for Winder2 {
fn new(
params: &control_core::machines::new::MachineNewParams<'_, '_, '_, '_, '_, '_, '_>,
) -> Result<Self, anyhow::Error>
use super::Winder2;
impl MachineNewTrait for Winder2 {
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, anyhow::Error>
where
Self: Sized,
{
@@ -18,11 +17,11 @@ impl control_core::machines::new::MachineNewTrait for Winder2 {
// For the mock machine, we don't need to actually use the hardware
// We just validate that we have the expected hardware type
match params.hardware {
control_core::machines::new::MachineNewHardware::Serial(_) => {
crate::MachineNewHardware::Serial(_) => {
// For serial mode, we could potentially use the serial device if needed
// but for a mock machine, we'll just note it and proceed
}
control_core::machines::new::MachineNewHardware::Ethercat(_) => {
crate::MachineNewHardware::Ethercat(_) => {
// For ethercat mode, we could potentially use the ethercat devices
// but for a mock machine, we'll just note it and proceed
}
@@ -30,7 +29,12 @@ impl control_core::machines::new::MachineNewTrait for Winder2 {
let now = std::time::Instant::now();
let mut extruder_mock_machine = Self {
let (sender, receiver) = smol::channel::unbounded();
let mut winder_mock_machine = Self {
main_sender: params.main_thread_channel.clone(),
max_connected_machines: 2,
api_receiver: receiver,
api_sender: sender,
machine_identification_unique: params.get_machine_identification_unique(),
namespace: Winder2Namespace {
namespace: params.namespace.clone(),
@@ -43,11 +47,11 @@ impl control_core::machines::new::MachineNewTrait for Winder2 {
mode_state: ModeState::default(),
tension_arm_state: TensionArmState::default(),
spool_speed_controller_state: SpoolSpeedControllerState::default(),
connected_machine_state: MachineCrossConnectionState::default(),
connected_machines: vec![],
};
extruder_mock_machine.emit_state();
winder_mock_machine.emit_state();
Ok(extruder_mock_machine)
Ok(winder_mock_machine)
}
}
+5 -5
View File
@@ -19,10 +19,9 @@ mod winder2_imports {
pub use super::api::SpoolAutomaticActionMode;
pub use std::time::Instant;
pub use units::f64::Length;
pub use units::length::meter;
}
use units::Length;
#[cfg(not(feature = "mock-machine"))]
mod winder2_imports {
pub use super::api::SpoolAutomaticActionMode;
@@ -36,19 +35,19 @@ mod winder2_imports {
digital_input::DigitalInput, digital_output::DigitalOutput,
stepper_velocity_el70x1::StepperVelocityEL70x1,
};
pub use smol::channel::{Receiver, Sender};
pub use smol::lock::RwLock;
pub use std::{fmt::Debug, sync::Weak, time::Instant};
pub use crate::buffer1::BufferV1;
pub use crate::{AsyncThreadMessage, Machine};
pub use units::ConstZero;
pub use units::f64::Length;
pub use units::{length::meter, length::millimeter, velocity::meter_per_second};
}
#[cfg(not(feature = "mock-machine"))]
use smol::channel::{Receiver, Sender};
pub use winder2_imports::*;
use crate::{AsyncThreadMessage, Machine};
#[cfg(not(feature = "mock-machine"))]
use crate::{
MACHINE_WINDER_V1, MachineConnection, MachineMessage, VENDOR_QITECH,
@@ -74,6 +73,7 @@ impl Default for SpoolAutomaticAction {
}
}
#[cfg(not(feature = "mock-machine"))]
impl Machine for Winder2 {
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique {
self.machine_identification_unique.clone()
+1 -6
View File
@@ -23,7 +23,7 @@ bitvec = "1.0.1"
# concurrency
smol = "2.0.2"
tokio = { version = "1.45.1", features = ["rt-multi-thread"] }
futures = "0.3.31"
regex = "1.11.3"
# web
serde_json = "1.0.143"
@@ -37,14 +37,12 @@ serialport = "4.7.3"
#tracing
tracing = { version = "0.1.41", features = ["attributes"] }
tracing-journald = { version = "0.3.1", optional = true }
tracing-futures = "0.2.5"
tracing-subscriber = { version = "0.3.20", features = [
"env-filter",
"chrono",
"registry",
] }
utils = { version = "0.1.0", path="../utils" }
tonic = { version = "0.13.1", optional = true }
spin_sleep = "1.3.3"
dhat = { version = "0.3.3", optional = true }
ctrlc = { version = "3.3", optional = true }
@@ -71,6 +69,3 @@ heap-profile = ["dhat"]
[profile.release]
codegen-units = 1
lto = "fat"
[package.metadata.cargo-machete]
ignored = ["tonic"]
-11
View File
@@ -1,11 +0,0 @@
# Server Structure
- `/server`: Is Backend part that makes connection between Beckhoff and Electron. Implements machine logic.
## EtherCAT
- `/ethercat`: Implements logic of EtherCAT
## Serial
- `/serial`: Implements logic of Serial, starting from serial device detection ending with serial device registration as machine
+1 -1
View File
@@ -142,7 +142,7 @@ impl SharedState {
pub async fn add_machines_if_not_exists(&self, machines: Vec<MachineObj>) {
let mut current_machines = self.current_machines_meta.lock().await;
tracing::info!("add_machines_if_not_exists: {:?}", current_machines);
// Track existing machine identifiers for quick lookup
let existing_ids: HashSet<_> = current_machines
.iter()
@@ -17,6 +17,7 @@ pub async fn send_ethercat_discovering(app_state: Arc<SharedState>) {
.write()
.await
.main_namespace;
let event = EthercatInterfaceDiscoveryEvent::Discovering(true).build();
main_namespace.emit(MainNamespaceEvents::EthercatInterfaceDiscoveryEvent(event));
}
@@ -31,6 +32,7 @@ pub async fn send_ethercat_found(app_state: Arc<SharedState>, interface: &str) {
.write()
.await
.main_namespace;
let event = EthercatInterfaceDiscoveryEvent::Done(interface).build();
main_namespace.emit(MainNamespaceEvents::EthercatInterfaceDiscoveryEvent(event));
}
+26 -7
View File
@@ -1,13 +1,18 @@
use control_core::ethercat::interface_discovery::discover_ethercat_interface;
use smol::{Task, Timer};
use std::time::Duration;
use smol::Timer;
use std::{sync::Arc, time::Duration};
pub async fn find_ethercat_interface() -> Result<String, anyhow::Error> {
use crate::{
app_state::SharedState,
ethercat::{ethercat_discovery_info::send_ethercat_found, setup::setup_loop},
};
pub async fn find_ethercat_interface() -> String {
loop {
match discover_ethercat_interface().await {
Ok(interface) => {
tracing::info!("Found EtherCAT Interface at: {}", interface);
return Ok(interface);
return interface;
}
Err(e) => {
tracing::warn!("No working interface found: {}. Retrying...", e);
@@ -17,7 +22,21 @@ pub async fn find_ethercat_interface() -> Result<String, anyhow::Error> {
}
}
// Returns a Future to the interface
pub fn start_interface_discovery() -> Task<Result<String, anyhow::Error>> {
smol::spawn(find_ethercat_interface())
pub async fn start_interface_discovery(app_state: Arc<SharedState>) {
let interface = find_ethercat_interface().await;
tracing::info!("Calling setup_loop");
let res = setup_loop(&interface, app_state.clone()).await;
match res {
Ok(_) => tracing::info!("Successfully initialized EtherCAT network"),
Err(e) => {
tracing::error!(
"[{}::main] Failed to initialize EtherCAT network \n{:?}",
module_path!(),
e
);
}
}
send_ethercat_found(app_state, &interface).await;
}
+8 -6
View File
@@ -7,13 +7,13 @@ use crate::{
ethercat::config::{MAX_FRAMES, MAX_PDU_DATA, MAX_SUBDEVICES, PDI_LEN},
};
#[cfg(all(target_os = "linux", not(feature = "development-build")))]
use control_core::irq_handling::set_irq_affinity;
use control_core::{irq_handling::set_irq_affinity, realtime::set_realtime_priority};
use machines::machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat, DeviceIdentification,
DeviceIdentificationIdentified, MachineIdentificationUnique, read_device_identifications,
};
use control_core::realtime::{set_core_affinity, set_realtime_priority};
use control_core::realtime::set_core_affinity;
use control_core::socketio::namespace::NamespaceCacheingLogic;
use ethercat_hal::devices::devices_from_subdevices;
use ethercrab::std::ethercat_now;
@@ -109,9 +109,11 @@ pub async fn set_ethercat_devices<const MAX_SUBDEVICES: usize, const MAX_PDI: us
shared_state: Arc<SharedState>,
socket_queue_tx: Sender<(SocketRef, Arc<control_core::socketio::event::GenericEvent>)>,
) -> Result<(), anyhow::Error> {
tracing::info!("set_ethercat_devices");
let device_grouping_result = group_devices_by_identification(device_identifications);
tracing::info!("{:?}", device_grouping_result.unidentified_devices);
tracing::info!(
"set_ethercat_devices: {:?}",
device_grouping_result.unidentified_devices
);
let machine_new_hardware = MachineNewHardware::Ethercat(hardware);
let mut machines: Vec<Box<dyn Machine>> = vec![];
@@ -194,10 +196,9 @@ pub async fn setup_loop(
#[cfg(not(all(target_os = "linux", feature = "io-uring")))]
{
use ethercrab::std::tx_rx_task;
use futures::executor::block_on;
let rt = smol::LocalExecutor::new();
let _ = block_on(rt.run(async {
let _ = smol::block_on(rt.run(async {
tx_rx_task(&interface, tx, rx)
.expect("spawn TX/RX task")
.await
@@ -245,6 +246,7 @@ pub async fn setup_loop(
.write()
.await
.main_namespace;
let event = EthercatDevicesEventBuilder().initializing();
main_namespace.emit(MainNamespaceEvents::EthercatDevicesEvent(event));
}
+1
View File
@@ -92,6 +92,7 @@ pub fn start_loop_thread(
.retain(|m| m.get_machine_identification_unique() != unique_id);
}
HotThreadMessage::AddMachines(machine_vec) => {
tracing::info!("received machines{:?}", machine_vec);
for new_machine in machine_vec {
let id = new_machine.get_machine_identification_unique();
if !rt_loop_inputs
+92 -68
View File
@@ -1,4 +1,3 @@
use futures::{FutureExt, select};
use machines::{
AsyncThreadMessage, MachineConnection, MachineNewHardware, MachineNewHardwareSerial,
MachineNewParams, SerialDevice, SerialDeviceIdentification, SerialDeviceNew,
@@ -8,37 +7,43 @@ use machines::{
DeviceIdentification, DeviceIdentificationIdentified, MachineIdentificationUnique,
},
registry::{MACHINE_REGISTRY, MachineRegistry},
serial::{
devices::laser::Laser,
init::{SerialDetection, start_serial_discovery},
},
serial::{devices::laser::Laser, init::SerialDetection},
winder2::api::GenericEvent,
};
#[cfg(feature = "mock-machine")]
use mock::init::init_mock;
#[cfg(feature = "mock-machine")]
pub mod mock;
#[cfg(feature = "development-build")]
use std::sync::atomic::{AtomicBool, Ordering};
use app_state::{HotThreadMessage, SharedState};
use ethercat::{
ethercat_discovery_info::{send_ethercat_discovering, send_ethercat_found},
init::start_interface_discovery,
setup::setup_loop,
};
use ethercat::ethercat_discovery_info::send_ethercat_discovering;
use r#loop::start_loop_thread;
use panic::init_panic_handling;
use rest::init::start_api_thread;
use serialport::UsbPortInfo;
use smol::{
channel::{Receiver, Sender},
future,
lock::RwLock,
};
use socketio::{main_namespace::machines_event::MachineObj, queue::start_socketio_queue};
use socketio::main_namespace::machines_event::MachineObj;
use socketioxide::extract::SocketRef;
use std::{collections::HashMap, sync::Arc, time::Duration};
#[cfg(feature = "mock-machine")]
use mock_init::init_mock;
use crate::{
ethercat::{
ethercat_discovery_info::send_ethercat_found, init::find_ethercat_interface,
setup::setup_loop,
},
socketio::queue::socketio_queue_worker,
};
#[cfg(feature = "mock-machine")]
pub mod mock_init;
pub mod app_state;
pub mod ethercat;
pub mod logging;
@@ -83,7 +88,10 @@ pub async fn add_serial_device(
let machine = match new_machine {
Ok(machine) => machine,
Err(_) => return,
Err(e) => {
tracing::error!("{:?}", e);
return;
}
};
shared_state
@@ -103,21 +111,34 @@ pub async fn add_serial_device(
.rt_machine_creation_channel
.send(HotThreadMessage::AddMachines(vec![machine]))
.await;
shared_state.clone().send_machines_event().await;
}
async fn setup_ethercat(
interface: &str,
pub async fn start_serial_discovery(app_state: Arc<SharedState>) {
loop {
let devices = SerialDetection::detect_devices();
if !devices.is_empty() {
handle_serial_device_hotplug(app_state.clone(), devices).await;
}
smol::Timer::after(Duration::from_secs(1)).await;
}
}
pub async fn start_interface_discovery(
app_state: Arc<SharedState>,
sender: Sender<HotThreadMessage>,
) {
tracing::info!("Calling setup_loop");
let interface = find_ethercat_interface().await;
tracing::info!("Inferface found {}, setting up EtherCAT loop", interface);
let res = setup_loop(&interface, app_state.clone()).await;
match res {
Ok(setup) => {
let _ = sender.send(HotThreadMessage::AddEtherCatSetup(setup)).await;
tracing::info!("Successfully initialized EtherCAT network");
tracing::info!("Successfully initialized EtherCAT devices");
}
Err(e) => {
@@ -128,23 +149,20 @@ async fn setup_ethercat(
);
}
}
send_ethercat_found(app_state.clone(), &interface).await;
}
pub async fn handle_serial_device_hotplug(
app_state: Arc<SharedState>,
map: Option<HashMap<String, UsbPortInfo>>,
map: HashMap<String, UsbPortInfo>,
) {
let laser_ident = SerialDeviceIdentification {
vendor_id: 0x0403,
product_id: 0x6001,
};
let laser = match map {
Some(map) => SerialDetection::get_path_by_id(laser_ident, map),
None => None,
};
let laser = SerialDetection::get_path_by_id(laser_ident, map);
let mut unique_ident: Option<MachineIdentificationUnique> = None;
{
@@ -264,10 +282,18 @@ async fn handle_async_requests(recv: Receiver<AsyncThreadMessage>, shared_state:
}
}
}
tracing::warn!("Async handler task finished");
}
#[cfg(feature = "development-build")]
use std::sync::atomic::{AtomicBool, Ordering};
pub async fn start_socketio_queue(app_state: Arc<SharedState>) {
let app_state = app_state.as_ref();
loop {
let res = socketio_queue_worker(app_state).await;
tracing::error!("SocketIO task finished, but should never finish: {:?}", res);
tracing::error!("Restarting SocketIO...");
}
}
#[cfg(feature = "development-build")]
fn setup_ctrlc_handler() -> Arc<AtomicBool> {
@@ -297,32 +323,33 @@ fn main() {
#[cfg(feature = "development-build")]
let running = setup_ctrlc_handler();
const CYCLE_TARGET_TIME: Duration = Duration::from_micros(300);
const CYCLE_TARGET_TIME: Duration = Duration::from_micros(700);
// for the "hot thread"
let (sender, receiver) = smol::channel::unbounded();
let (main_sender, main_receiver) = smol::channel::unbounded();
let shared_state = SharedState::new(sender.clone(), main_sender.clone());
let shared_state = SharedState::new(sender.clone(), main_sender);
let app_state = Arc::new(shared_state);
let _loop_thread = start_loop_thread(receiver, CYCLE_TARGET_TIME);
#[cfg(feature = "mock-machine")]
init_mock(app_state.clone()).expect("Failed to initialize mock machines");
let _ = start_api_thread(app_state.clone());
let mut socketio_fut = start_socketio_queue(app_state.clone()).fuse();
let mut ethercat_fut = start_interface_discovery().fuse();
let mut serial_fut = start_serial_discovery().fuse();
let mut handle_async_machine_requests =
smol::spawn(handle_async_requests(main_receiver, app_state.clone())).fuse();
let mut socketio_task = smol::spawn(start_socketio_queue(app_state.clone()));
let mut serial_task = smol::spawn(start_serial_discovery(app_state.clone()));
let mut async_machine_task = smol::spawn(handle_async_requests(
main_receiver.clone(),
app_state.clone(),
));
#[cfg(not(feature = "mock-machine"))]
smol::spawn(start_interface_discovery(app_state.clone(), sender)).detach();
smol::block_on(async {
send_empty_machines_event(app_state.clone()).await;
send_ethercat_discovering(app_state.clone()).await;
});
#[cfg(feature = "mock-machine")]
init_mock(app_state.clone()).expect("Failed to initialize mock machines");
smol::block_on(async {
loop {
#[cfg(feature = "development-build")]
@@ -330,32 +357,29 @@ fn main() {
tracing::info!("Shutdown signal received, exiting main loop.");
break;
}
// lets the async runtime decide which future to run next
select! {
res = ethercat_fut => {
tracing::info!("EtherCAT task finished: {:?}", res);
match res {
Ok(interface) =>
{
setup_ethercat(&interface,app_state.clone(),sender.clone()).await;
},
Err(_) => (),
};
},
res = serial_fut => {
let _ = handle_serial_device_hotplug(app_state.clone(),res).await;
serial_fut = start_serial_discovery().fuse();
},
res = socketio_fut => {
// In theory it should never finish
tracing::warn!("SocketIO task finished: {:?}", res);
},
res = handle_async_machine_requests => {
tracing::warn!("Async handler task finished: {:?}", res);
},
if serial_task.is_finished() {
tracing::warn!("Serial task died! Restarting...");
serial_task.cancel().await;
serial_task = smol::spawn(start_serial_discovery(app_state.clone()));
}
if socketio_task.is_finished() {
tracing::warn!("SocketIO task died! Restarting...");
socketio_task.cancel().await;
socketio_task = smol::spawn(start_socketio_queue(app_state.clone()));
}
if async_machine_task.is_finished() {
tracing::warn!("Async handler task died! Restarting...");
async_machine_task.cancel().await;
async_machine_task = smol::spawn(handle_async_requests(
main_receiver.clone(),
app_state.clone(),
));
}
future::yield_now().await;
}
});
}
-124
View File
@@ -1,124 +0,0 @@
#[cfg(feature = "mock-machine")]
use crate::serial::devices::extruder_mock::ExtruderMockSerialDevice;
use crate::serial::devices::winder_mock::WinderMockSerialDevice;
#[cfg(feature = "mock-machine")]
use {
crate::app_state::AppState,
crate::machines::registry::MACHINE_REGISTRY,
crate::serial::devices::mock::MockSerialDevice,
crate::socketio::main_namespace::{MainNamespaceEvents, machines_event::MachinesEventBuilder},
control_core::{
serial::{SerialDeviceNew, SerialDeviceNewParams},
socketio::namespace::NamespaceCacheingLogic,
},
std::sync::Arc,
};
#[cfg(feature = "mock-machine")]
pub fn init_mock(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
// For mock devices, we need to manually create and add them to the machine manager
// since they won't be detected by the serial detection loop
return smol::block_on(async {
// Create a mock serial device manually
let serial_params = SerialDeviceNewParams {
path: "/dev/mock-serial".to_string(),
};
// Create the mock serial device
let _ = match MockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
let mut machine_guard = app_state.machines.write().await;
machine_guard.add_serial_device(
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.socketio_setup.socket_queue_tx.clone(),
Arc::downgrade(&app_state.machines),
);
}
// Notify clients via socketio about the new machine
let app_state_event = app_state.clone();
let main_namespace = &mut app_state_event
.socketio_setup
.namespaces
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
Ok::<(), anyhow::Error>(())
}
Err(e) => {
tracing::error!("Failed to create mock serial device: {}", e);
return Err(e);
}
};
let _ = match ExtruderMockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
let mut machine_guard = app_state.machines.write().await;
machine_guard.add_serial_device(
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.socketio_setup.socket_queue_tx.clone(),
Arc::downgrade(&app_state.machines),
);
}
// Notify clients via socketio about the new machine
let app_state_event = app_state.clone();
let main_namespace = &mut app_state_event
.socketio_setup
.namespaces
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
Ok::<(), anyhow::Error>(())
}
Err(e) => {
tracing::error!("Failed to create extruder mock serial device: {}", e);
return Err(e);
}
};
match WinderMockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
let mut machine_guard = app_state.machines.write().await;
machine_guard.add_serial_device(
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.socketio_setup.socket_queue_tx.clone(),
Arc::downgrade(&app_state.machines),
);
}
// Notify clients via socketio about the new machine
let app_state_event = app_state.clone();
let main_namespace = &mut app_state_event
.socketio_setup
.namespaces
.write()
.await
.main_namespace;
let event = MachinesEventBuilder().build(app_state_event.clone());
main_namespace.emit(MainNamespaceEvents::MachinesEvent(event));
Ok(())
}
Err(e) => {
tracing::error!("Failed to create extruder mock serial device: {}", e);
return Err(e);
}
}
});
}
-1
View File
@@ -1 +0,0 @@
pub mod init;
+94
View File
@@ -0,0 +1,94 @@
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;
pub fn init_mock(app_state: Arc<SharedState>) -> Result<(), anyhow::Error> {
// For mock devices, we need to manually create and add them to the machine manager
// since they won't be detected by the serial detection loop
return smol::block_on(async {
// Create a mock serial device manually
use machines::{
SerialDeviceNew, SerialDeviceNewParams,
serial::devices::{
extruder_mock::ExtruderMockSerialDevice, mock::MockSerialDevice,
winder_mock::WinderMockSerialDevice,
},
};
let serial_params = SerialDeviceNewParams {
path: "/dev/mock-serial".to_string(),
};
// Create the mock serial device
let _ = match MockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
use crate::add_serial_device;
use machines::registry::MACHINE_REGISTRY;
add_serial_device(
app_state.clone(),
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.socketio_setup.socket_queue_tx.clone(),
)
.await;
}
Ok::<(), anyhow::Error>(())
}
Err(e) => {
tracing::error!("Failed to create mock serial device: {}", e);
return Err(e);
}
};
let _ = match ExtruderMockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
{
use crate::add_serial_device;
use machines::registry::MACHINE_REGISTRY;
add_serial_device(
app_state.clone(),
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.socketio_setup.socket_queue_tx.clone(),
)
.await;
}
Ok::<(), anyhow::Error>(())
}
Err(e) => {
tracing::error!("Failed to create extruder mock serial device: {}", e);
return Err(e);
}
};
match WinderMockSerialDevice::new_serial(&serial_params) {
Ok((device_identification, mock_serial_device)) => {
// Add the mock device to the machine manager
add_serial_device(
app_state.clone(),
&device_identification,
mock_serial_device,
&MACHINE_REGISTRY,
app_state.clone().socketio_setup.socket_queue_tx.clone(),
)
.await;
Ok(())
}
Err(e) => {
tracing::error!("Failed to create extruder mock serial device: {}", e);
return Err(e);
}
}
});
}
-56
View File
@@ -1,56 +0,0 @@
# Serial Devices
Serial communication interface through which information transfers in or out sequentially one bit at a time.
`Important NOTE: Vendor ID 0x0403 and Product ID 0x6001 is not Vendor & Product ID of Laser, but USB Modbus itself.`
Current Serial Devices:
- Laser, connected through RTU Modbus (USB).
## Current Serial Detection (./server/src/serial/serial_detection.rs)
Serial Detection contains 3 main components:
- `Serial Registry`, the interface that is used as buffer for all registered Serial Devices, and responsible for start of registered Serial Devices and returns link to started Serial Device. (./control-core/src/serial/registry.rs)
- `Connected Serial USB`, the hashmap that saves the path to USB port with the link to started Serial Device under this port path.
- `Ports`, previously detected USB ports with pathes and usb port information (such as Vendor & Product ID, Serial Number)
The cycle of Serial Detection is consisted from following steps:
`1)` Detection of all current Serial Connections
`2)` Extraction of USB Port connections (only this type of connection contains information about vid and pid of modbus itself)
`3)` Comparison of previously detected USB ports with currently detected USB ports (from the step 2)
`4)` Resave previously detected USB ports with current ports
`5)` Start connection of newly detected ports with Serial Registry and save the path to the port with the link to started Serial Device to `Connected Serial USB`
`6)` Remove serial ports from `Connected Serial USB` that was not detected (in the step 2)
`7)` The last step is to check whether the connection with the serial devices is still active. Using the `Connected Devices` parameters or functions, we can verify if the serial device can still fulfill its functionalities. If not, the link to the connected device should be resaved with a disconnection message.
The next step is to check if the devices that were previously disconnected or unable to establish a connection can now connect to the serial device during this iteration. If they can, save the link to the device in `Connected Serial USB`.
## Laser (/server/src/machines/laser/new.rs)
Laser Device has simple structure, it has two parameters (measured diameter) and the path to the serial port, for communication establishment. Also it is connected to the Trait of all Serial Devices.
- After the start of Laser, it will constantly update measured diameter parameter, under current Configurations of Modbus RTU connection (30 diameter updates per second).
- If Connection could not be established with Laser 10 times in a row, then diameter update will stop, and instead of diameter parameter will be error message.
- Diameter Update is running as a separate thread task.
### Current Laser Parameters
`1)` Baud Rate 38400 (for maximum efficiency)
`2)` 8 bits per character.
`3)` No parity bit.
`4)` One stop bit.
## Serial Init Loop (/server/src/serial/init.rs)
The main task of Serial Init loop is to run `Serial Detection` as a separate thread task. It executes `Cycle of Serial Detection` each 300 ms.
## Serial SERIAL_DETECTION lazy static (/server/src/serial/register.rs)
The main goal is to create a global Serial Detection static using already registered Serial Product Configurations. These configurations should be accessible and updatable from different thread tasks.
## TODO list
- We need to assign Vendor & Product ID of Laser separately as machine.
- Remake retry connection, so it will work for all devices and not only Laser specific
+2 -4
View File
@@ -1,13 +1,11 @@
use super::namespace_id::NamespaceId;
use crate::app_state::SharedState;
use socketioxide::ParserConfig;
use socketioxide::extract::SocketRef;
use socketioxide::layer::SocketIoLayer;
use std::str::FromStr;
use std::sync::Arc;
use crate::app_state::SharedState;
use super::namespace_id::NamespaceId;
pub async fn init_socketio(app_state: Arc<SharedState>) -> SocketIoLayer {
// create
let (socketio_layer, io) = socketioxide::SocketIoBuilder::new()
+9 -7
View File
@@ -1,9 +1,7 @@
use smol::Task;
use crate::app_state::SharedState;
use std::{sync::Arc, time::Instant};
use tracing::{debug, info, instrument, trace};
use crate::app_state::SharedState;
/// Send a single event with retry logic
#[instrument(skip_all)]
async fn send_event_with_retry(
@@ -60,7 +58,7 @@ async fn send_event_with_retry(
}
}
async fn socketio_queue_worker(app_state: Arc<SharedState>) {
pub async fn socketio_queue_worker(app_state: &SharedState) {
tracing::info!("SocketIO global queue listener started");
let mut event_count = 0;
let mut batch_start = Instant::now();
@@ -89,7 +87,11 @@ async fn socketio_queue_worker(app_state: Arc<SharedState>) {
info!("SocketIO global queue listener stopped");
}
/// Returns a spawned async task handling the Socket.IO event queue
pub fn start_socketio_queue(app_state: Arc<SharedState>) -> Task<()> {
smol::spawn(socketio_queue_worker(app_state))
pub async fn start_socketio_queue(app_state: Arc<SharedState>) {
let app_state = app_state.as_ref();
loop {
let res = socketio_queue_worker(app_state).await;
tracing::error!("SocketIO task finished, but should never finish: {:?}", res);
tracing::error!("Restarting SocketIO...");
}
}