Compare commits

..
Author SHA1 Message Date
Oshgnacknak 305fc84474 Splitting groups to get aquapath working with winder.
Did not work.
2026-01-25 21:04:19 +01:00
Milan von dem Bussche 5e1126e896 Revise machine table in README.md
Updated the machine table format and added version details.
2026-01-23 12:38:14 +01:00
Milan von dem Bussche e91719f902 Update getting started documentation with new sections 2026-01-23 12:37:30 +01:00
Milan von dem Bussche 0efa9bcb07 Revise README structure and add documentation links
Updated README to enhance structure and clarity, added minimal hardware examples section, and included API documentation link.
2026-01-23 12:33:58 +01:00
Milan von dem Bussche 37d897d240 Revise machine table with new entries and videos
Updated machine specifications and added video links for various machines.
2026-01-23 11:38:36 +01:00
Robin Krämer 7488e1d97a updated Change Log (#1069) 2026-01-22 20:35:25 +01:00
9 changed files with 298 additions and 182 deletions

No files matched your search

+6 -1
View File
@@ -1,3 +1,9 @@
# `2.14.0`
_22.01.2026_
## General
- [#1040](https://github.com/qitechgmbh/control/pull/1040) Added API for reading Machine Data
# `2.13.0`
_07.01.2026_
@@ -5,7 +11,6 @@ _07.01.2026_
- [#925](https://github.com/qitechgmbh/control/pull/925) Switch to Setup when selected machine is disconnected
- [#937](https://github.com/qitechgmbh/control/pull/937) Added minimal Support for WAGO
- [#945](https://github.com/qitechgmbh/control/pull/945) Improvements for On-Screen Keyboard usage in Ethercat Setup Page
- []
## Extruder V3
- [#930](https://github.com/qitechgmbh/control/pull/930) Added Extruder V3
+40 -98
View File
@@ -17,22 +17,22 @@ QiTech Control combines the modularity and reliability of standard EtherCAT term
# Repo Structure
Frontend
**Frontend**
- `/electron`: Frontend code for the control software built with React and Electron.
Backend
**Backend**
- `/server`: Glue between Beckhoff and Electron. Implements machine logic.
- `/ethercat-hal`: Hardware abstraction layer for Beckhoff (and possibly other EtherCat) devices and protocols.
- `/ethercat-hal-derive`: Macros for `ethercat-hal`
- `/control-core`: Core control logic for the server.
Operating System
**Operating System**
- `/nixos`: Custom Linux with realtime kernel & preconfigured for touchscreens.
Other
**Other**
- `/docs`: Documentation for the project.
@@ -54,115 +54,57 @@ We could combine the code of the frontend and backend using [Doxius](https://dio
[Developer Documentation](./docs/developer-docs/)
## Backend
[API Documentation](https://github.com/qitechgmbh/control/blob/master/docs/rest-api.md)
-> Backend
- Rust stable 1.86^ toolchain (install via [rustup](https://rustup.rs/))
- `rust-analyzer` extension for VSCode
- Set your interface in `server/src/ethercat/init.rs` like `en10`
- Connect a Beckhoff EK1100 to your interface
- run `cd server && cargo run` to start the server (localhost:3001)
## Frontend
-> Frontend
- nodejs and npm installed
- run `cd electron && npm i && npm run start` to start the frontend
# Machines
| Machine Type | Version | Release Date | Description | Change to Previous Version | Vendor ID | Machine ID | Implemented | Docs |
| ------------ | ------- | ------------ | --------------------------- | ------------------------------------------------------ | -------------------------- | ---------- | ----------- | ------------------------------- |
| Winder | V1 | ??? | Winding Filaments & Similar | - | 1 (Qitech Industries GmbH) | 1 | Reserved | - |
| Winder | V2 | 2025 | Winding Filaments & Similar | Reengineered Traverse | 1 (Qitech Industries GmbH) | 2 | Yes | [](./docs/machines/winder-1.md) |
| Extruder | V1 | ??? | Single Screw Extruder | - | 1 (Qitech Industries GmbH) | 3 | Reserved | - |
| Extruder | V2 | 2025 | Single Screw Extruder | PT100 Thermometers, Optional Additional Heating Zone 4 | 1 (Qitech Industries GmbH) | 4 | Yes | |
| Waterway | V1 | 2025 | Filament Water Cooling | - | 1 (Qitech Industries GmbH) | 5 | In Progress | |
| Laser | V1 | ??? | Diameter Measuring Laser | - | 1 (Qitech Industries GmbH) | 6 | Yes | |
| Mock | - | ??? | Mock Machine for Testing | - | 1 (Qitech Industries GmbH) | 7 | Yes | - |
# Minimal Hardware Examples
## Minimal Hardware Examples
For complete step-by-step tutorials on setting up your first hardware, including wiring diagrams and software configuration, see the [Getting Started Guide](./docs/developer-docs/getting-started.md#minimal-hardware-examples).
Available examples:
- **LED Control with EL2004** - Digital output control (simplest setup)
- **Analog Input with EL3021** - Reading analog current measurements
| Example | Vendor | Hardware | Description | How-To Video | Docs |
| :----------------- | :------- | :------------ | :-------------------------------------- | :----------- | :---------------------------- |
| **Digital Input** | WAGO | 750-402 | 4-Channel Digital Input | [ ] | - |
| **Digital Output** | WAGO | 750-753 | 8-Channel Digital Output | [ ] | - |
| **Analog Input** | WAGO | 750-455 | 4-Channel Analog Input | [ ] | - |
| **Analog Output** | WAGO | 750-553 | 4-Channel Analog Output | [ ] | - |
| **Serial Comms** | WAGO | 750-652 | RS-485 Modbus Module | [ ] | - |
| **Stepper Drive** | WAGO | 750-67x | Stepper Controller | [ ] | - |
| **Power Supply** | WAGO | 2787-214 | Power Supply Unit | [ ] | - |
| **LED Control** | Beckhoff | EL2004 | Digital output control (simplest setup) | [ ] | [](./docs/hardware/el2004.md) |
| **Analog Input** | Beckhoff | EL3021 | Reading analog current measurements | [ ] | [](./docs/hardware/el3021.md) |
# QiTech Machines
# More Docs
| Machine Name | Description | Machine ID | Implemented | Video | Docs |
| :---------------- | :-------------------------- | :--------- | :---------- | :-------------------------------------------------------------- | :------------------------------ |
| Winder V1 | Winding Filaments & Similar | 1 | Legacy | [Video](https://youtu.be/4aE4cFhioKA?si=Xdj_LVnFrAYWnLm6) | - |
| Winder V2 | Winding Filaments & Similar | 2 | Yes | [Video](https://youtu.be/f2kzh6kpQWE?si=HEYcLOaC9gWSp2Wo) | [](./docs/machines/winder-1.md) |
| XL Winder V1 | Large Scale Winder | 3 | Yes | [Video](https://youtu.be/ynI6ioWIQQY?si=4EDVKXbgIqrvGGtY) | - |
| Buffer V1 | Filament buffering system | 4 | In Progress | [Video](https://youtu.be/VR5mEdZDPA0?si=eip7dQXU4zaXK7Ev) | - |
| Extruder V1 | Single Screw Extruder | 5 | Legacy | [Video](https://youtu.be/gchoG-yGczI?si=bEM4daf5eOVaU5_2) | - |
| Extruder V2 | Single Screw Extruder | 6 | Yes | [Video](https://youtu.be/mexRYGDNWa4?si=h7JBz_XKMwKLPenG) | |
| Waterway V1 | Filament Water Cooling | 7 | In Progress | [Video](https://youtu.be/_T5z1J8bl_k?si=uOC1hIQ1EAP0cHIF) | |
| Laser V1 | Diameter Measuring Laser | 8 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | |
| 2-Axis-Laser V1 | Dual Axis Laser | 9 | Yes | [Video](https://youtu.be/WDM34lj4afM?si=MZzUKkHCrzH4P0aA) | - |
| Mock | Mock Machine for Testing | 10 | Yes | | - |
| Extruder V3 | Single Screw Extruder | 11 | Yes | [Video](https://youtu.be/ipHHuPzCvn4?si=hkZ1b93rVuwDxhFD) | |
| Mini Schredder V1 | mini plastic crusher | 12 | Legacy | [Video](https://youtu.be/m8NplNqdu2Q?si=x6zjDktJtpbSMSmu) | - |
| Pro Schredder V1 | large plastic crusher | 13 | Legacy | [Video](https://youtu.be/pSaVMqp06pU?si=y61enutRpxKscBm4) | - |
| Dryer V1 | polymer dryer | 14 | Reserved | [Video](https://youtu.be/6hdmUUAdZp0?si=eKOW1WlgkqTpnfdS) | - |
| Pelletizier V1 | Filament chopper | 15 | Reserved | | - |
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
# Current Restructuring Efforts
- [x] Example Winder V2
- [ ] Electron
- Folder Structure
- Routing with TanStack Router
- Design with Tailwind & Shadcn
- ...
- [ ] Interfacing with Electron/Server
- [ ] SocketIO
- Machine Namespace
- Main Namespace
- [ ] REST
- Machine Mutations
- Write Device Identification
- Server
- [x] [Threading](./docs/control-loop.md)
- [x] [Logging](./docs/logging.md)
- [ ] Control Loop Setup
- Control Loop Thread
- [ ] realtime
- Maindevice
- Group
- Extracting Device Identifications
- Identifying Groups
- Validating Machines
- Run Control Loop
- [x] [Control Loop](./docs/control-loop.md)
- [x] [Machine/Device Identification](./docs/identification.md)
- [ ] Machines
- When to create a new Machine?
- Versioning
- Code sharing
- Creating/Validating a Machine
- Validation
- Configuration
- [ ] Machine Implementation Guide
- Link: How to create a Device
- Link: How to create an Actor
- Link: How to create a Machine
- API (SocketIO + REST)
- Creation/Validation Logic
- Optional/Mandatory Devices
- Validate Devices
- Business Logic
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
- Forward `act` in winder.
- [ ] Control Core
- [x] [Actors](./docs/actors.md)
- [ ] SocketIO
- Namespaces & Caching
- Joining leaving namespaces
- NamespaceId
- Caching
- Serverside Caching
- Clientside Caching
- [ ] REST
- [x] Ethercat HAL
- [x] [Devices](./docs/devices.md)
- [x] [Configuration (CoE)](./docs/coe.md)
- [x] [IO](./docs/io.md)
- [x] [PDO](./docs/pdo.md)
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
- [x] [NixOS Operating System](./docs/nixos/README.md)
[![](https://img.youtube.com/vi/UlbnSVIhfLI/maxresdefault.jpg)](https://www.youtube.com/live/UlbnSVIhfLI?si=ZTotC5B7gd87tUim)
*Video: Live stream of software dev meeting*
@@ -1,6 +1,5 @@
use ethercrab::{
MainDevice, MainDeviceConfig, PduStorage, Timeouts,
std::{ethercat_now, tx_rx_task},
MainDevice, MainDeviceConfig, PduStorage, SubDeviceGroup, Timeouts, std::{ethercat_now, tx_rx_task}
};
use interfaces::Interface;
use std::{process::Command, sync::Arc, time::Duration};
@@ -11,6 +10,13 @@ const IFACE_DISCOVERY_MAX_PDU_DATA: usize = PduStorage::element_size(1100);
const IFACE_DISCOVERY_MAX_FRAMES: usize = 16;
const IFACE_DISCOVERY_MAX_PDI_LEN: usize = 128;
#[derive(Default)]
pub struct Groups {
aquapath: SubDeviceGroup<IFACE_DISCOVERY_MAX_SUBDEVICES, IFACE_DISCOVERY_MAX_PDI_LEN>,
rest: SubDeviceGroup<IFACE_DISCOVERY_MAX_SUBDEVICES, IFACE_DISCOVERY_MAX_PDI_LEN>,
}
/// Sets a network interface to unmanaged by NetworkManager.
/// Returns true if the command succeeded.
pub fn set_interface_managed(interface: &str, managed: bool) -> bool {
@@ -97,7 +103,7 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
interface = Some(&interfaces[i].name);
break;
}
Err(_) => (),
Err(e) => tracing::warn!("Error initializing interface {}: {}", interfaces[i].name, e),
}
}
@@ -119,7 +125,7 @@ pub async fn discover_ethercat_interface() -> Result<String, anyhow::Error> {
}
fn test_interface(interface: &str) -> Result<(), anyhow::Error> {
tracing::info!("Testing interface: {}", interface);
tracing::info!("Testing interface by OSH: {}", interface);
let pdu_storage = Box::leak(Box::new(PduStorage::<
IFACE_DISCOVERY_MAX_FRAMES,
@@ -137,6 +143,8 @@ fn test_interface(interface: &str) -> Result<(), anyhow::Error> {
.map_err(|e| anyhow::anyhow!("Failed to spawn TX/RX task: {}", e))?,
);
println!("CREATING MAIN DEVICE");
let maindevice = Arc::new(MainDevice::new(
pdu_loop,
Timeouts {
@@ -161,10 +169,29 @@ fn test_interface(interface: &str) -> Result<(), anyhow::Error> {
},
));
println!("MAIN DEVICE CREATED");
let result = maindevice
.init_single_group::<IFACE_DISCOVERY_MAX_SUBDEVICES, IFACE_DISCOVERY_MAX_PDI_LEN>(
ethercat_now,
)
.init::<IFACE_DISCOVERY_MAX_SUBDEVICES, _>(ethercat_now, |groups: &Groups, subdevice| {
panic!("GOT HERE");
println!("SUBDEVICE NAME {} ID {}", subdevice.name(), subdevice.identity());
if subdevice.name() == "EL5152" {
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
println!("USING AQUAPATH GROUP DUING INIT");
return Ok(&groups.aquapath);
}
Ok(&groups.rest)
})
.await
.map(|_| ())
.map_err(|e| anyhow::anyhow!("Failed to initialize group: {}", e));
+80
View File
@@ -91,3 +91,83 @@ To get started with actual hardware, check out these step-by-step tutorials:
- **[Analog Input with EL3021](./minimal-example-el3021.md)** - Reading analog current measurements
These examples provide complete hardware wiring diagrams and software setup instructions.
# More Docs
- [x] [Architecture & Data Flow](./docs/architecture-overview.md)
- [x] Example Winder V2
- [ ] Electron
- Folder Structure
- Routing with TanStack Router
- Design with Tailwind & Shadcn
- ...
- [ ] Interfacing with Electron/Server
- [ ] SocketIO
- Machine Namespace
- Main Namespace
- [ ] REST
- Machine Mutations
- Write Device Identification
- Server
- [x] [Threading](./docs/control-loop.md)
- [x] [Logging](./docs/logging.md)
- [ ] Control Loop Setup
- Control Loop Thread
- [ ] realtime
- Maindevice
- Group
- Extracting Device Identifications
- Identifying Groups
- Validating Machines
- Run Control Loop
- [x] [Control Loop](./docs/control-loop.md)
- [x] [Machine/Device Identification](./docs/identification.md)
- [ ] Machines
- When to create a new Machine?
- Versioning
- Code sharing
- Creating/Validating a Machine
- Validation
- Configuration
- [ ] Machine Implementation Guide
- Link: How to create a Device
- Link: How to create an Actor
- Link: How to create a Machine
- API (SocketIO + REST)
- Creation/Validation Logic
- Optional/Mandatory Devices
- Validate Devices
- Business Logic
- Link: How to create Machine Abstraction (Like Traverse/Puller/...)
- Forward `act` in winder.
- [ ] Control Core
- [x] [Actors](./docs/actors.md)
- [ ] SocketIO
- Namespaces & Caching
- Joining leaving namespaces
- NamespaceId
- Caching
- Serverside Caching
- Clientside Caching
- [ ] REST
- [x] Ethercat HAL
- [x] [Devices](./docs/devices.md)
- [x] [Configuration (CoE)](./docs/coe.md)
- [x] [IO](./docs/io.md)
- [x] [PDO](./docs/pdo.md)
- [x] [Ethercat Basics](./docs/ethercat-basics.md)
- [x] [NixOS Operating System](./docs/nixos/README.md)
+1 -1
View File
@@ -30,7 +30,7 @@ if (!gotTheLock) {
}
function createWindow() {
const preload = path.resolve(DIR, "preload.js");
const preload = path.join(DIR, "preload.js");
console.log("Setting preload.js to", preload);
const mainWindow = new BrowserWindow({
+19 -39
View File
@@ -2,6 +2,7 @@ use std::fmt::Display;
use ethercat_hal::devices::wago_750_354::WAGO_750_354_IDENTITY_A;
use ethercat_hal::devices::wago_modules::ip20_ec_di8_do8::IP20_EC_DI8_DO8_IDENTITY;
use ethercrab::SubDevice;
use serde::Deserialize;
use serde::Serialize;
@@ -233,61 +234,40 @@ pub async fn read_device_identifications<'maindevice>(
let mut result = Vec::new();
for subdevice in subdevices.iter() {
let identification = machine_device_identification(subdevice, maindevice).await;
result.push(identification);
result.push(identification.map_err(|e| anyhow!(
"[{}::machine_device_identification] Failed to read role from EEPROM for device {}: {}",
module_path!(),
subdevice.name(),
e
)));
}
result
}
/// Reads the machine device identification from the EEPROM
pub async fn machine_device_identification<'maindevice>(
subdevice: &'maindevice EthercrabSubDevicePreoperational<'maindevice>,
pub async fn machine_device_identification(
subdevice: &SubDevice,
maindevice: &MainDevice<'_>,
) -> Result<DeviceMachineIdentification, Error> {
let addresses = match get_identification_addresses(&subdevice.identity(), subdevice.name()) {
Ok(x) => x,
Err(e) => {
// u16dump(subdevice, maindevice, 0, 128).await?;
return Err(e);
}
};
) -> Result<DeviceMachineIdentification, ethercrab::error::Error> {
let addresses = get_identification_addresses(&subdevice.identity(), subdevice.name())?;
let mdi = DeviceMachineIdentification {
machine_identification_unique: MachineIdentificationUnique {
machine_identification: MachineIdentification {
vendor: subdevice
.eeprom_read::<u16>(maindevice, addresses.vendor_word)
.await
.or(Err(anyhow!(
"[{}::machine_device_identification] Failed to read vendor from EEPROM for device {}",
module_path!(),
subdevice.name()
)))?,
.await?,
machine: subdevice
.eeprom_read::<u16>(maindevice, addresses.machine_word)
.await
.or(Err(anyhow!(
"[{}::machine_device_identification] Failed to read machine from EEPROM for device {}",
module_path!(),
subdevice.name()
)))?,
.await?,
},
serial: subdevice
.eeprom_read::<u16>(maindevice, addresses.serial_word)
.await
.or(Err(anyhow!(
"[{}::machine_device_identification] Failed to read serial from EEPROM for device {}",
module_path!(),
subdevice.name()
)))?,
.await?,
},
role: subdevice
.eeprom_read::<u16>(maindevice, addresses.role_word)
.await
.or(Err(anyhow!(
"[{}::machine_device_identification] Failed to read role from EEPROM for device {}",
module_path!(),
subdevice.name()
)))?,
.await?,
};
tracing::debug!(
@@ -382,7 +362,7 @@ pub async fn write_machine_device_identification<'maindevice, const MAX_PDI: usi
pub fn get_identification_addresses(
subdevice_identity: &SubDeviceIdentity,
subdevice_name: &str,
) -> Result<MachineIdentificationAddresses, Error> {
) -> Result<MachineIdentificationAddresses, ethercrab::error::Error> {
let identity_tuple = subdevice_identity_to_tuple(subdevice_identity);
Ok(match identity_tuple {
@@ -414,14 +394,14 @@ pub fn get_identification_addresses(
_ => {
// block_on(u16dump(&subdevice, maindevice, 0x00, 0xff))?;
Err(anyhow!(
"[{}::get_identification_addresses] Unknown MDI addresses for device {:?} vendor: 0x{:08x} product: 0x{:08x} revision: 0x{:08x}",
tracing::error!("[{}::get_identification_addresses] Unknown MDI addresses for device {:?} vendor: 0x{:08x} product: 0x{:08x} revision: 0x{:08x}",
module_path!(),
subdevice_name,
subdevice_identity.vendor_id,
subdevice_identity.product_id,
subdevice_identity.revision
))?
);
return Err(ethercrab::error::Error::UnknownSubDevice);
}
})
}
+4 -3
View File
@@ -1,4 +1,5 @@
use crate::ethercat::config::{MAX_SUBDEVICES, PDI_LEN};
use crate::ethercat::setup::Groups;
use crate::rest::handlers::write_machine_device_identification::MachineDeviceInfoRequest;
use crate::socketio::main_namespace::MainNamespaceEvents;
use crate::socketio::main_namespace::machines_event::{MachineObj, MachinesEventBuilder};
@@ -102,7 +103,7 @@ pub struct EthercatSetup {
/// All Ethercat devices
/// Generic interface for all devices
/// Needed to interface with the devices on an Ethercat level
pub group: SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, Op>,
pub groups: Groups<Op>,
/// The Ethercat main device
/// Needed to interface with the devices
pub maindevice: MainDevice<'static>,
@@ -111,12 +112,12 @@ pub struct EthercatSetup {
impl EthercatSetup {
pub fn new(
devices: Vec<(DeviceIdentification, Arc<RwLock<dyn EthercatDevice>>)>,
group: SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, Op>,
groups: Groups<Op>,
maindevice: MainDevice<'static>,
) -> Self {
Self {
devices,
group,
groups,
maindevice,
}
}
+94 -18
View File
@@ -11,23 +11,24 @@ use control_core::socketio::namespace::NamespaceCacheingLogic;
#[cfg(all(target_os = "linux", not(feature = "development-build")))]
use control_core::{irq_handling::set_irq_affinity, realtime::set_realtime_priority};
use ethercat_hal::debugging::diagnosis_history::get_most_recent_diagnosis_message;
use ethercat_hal::devices::devices_from_subdevices;
use ethercat_hal::devices::{EthercatDevice, devices_from_subdevices};
use ethercat_hal::devices::wago_750_354::{
WAGO_750_354_PRODUCT_ID, WAGO_750_354_VENDOR_ID, Wago750_354,
};
use ethercat_hal::devices::wago_modules::ip20_ec_di8_do8::{
IP20_EC_DI8_DO8_PRODUCT_ID, IP20_EC_DI8_DO8_VENDOR_ID, IP20EcDi8Do8,
};
use ethercrab::subdevice_group::{Op, PreOp, SafeOp};
use smol::lock::RwLock;
use crate::utils::{start_dnsmasq, stop_dnsmasq};
use ethercrab::std::ethercat_now;
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, SubDevice, SubDeviceGroup, SubDevicePdi, SubDeviceRef, Timeouts};
use machines::machine_identification::{
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat, DeviceIdentification,
DeviceIdentificationIdentified, MachineIdentificationUnique, read_device_identifications,
DeviceHardwareIdentification, DeviceHardwareIdentificationEthercat, DeviceIdentification, DeviceIdentificationIdentified, MachineIdentificationUnique, machine_device_identification, read_device_identifications
};
use machines::registry::{MACHINE_REGISTRY, MachineRegistry};
use machines::{Machine, MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams};
use machines::{MACHINE_AQUAPATH_V1, Machine, MachineNewHardware, MachineNewHardwareEthercat, MachineNewParams};
use smol::channel::Sender;
use socketioxide::extract::SocketRef;
use std::{sync::Arc, time::Duration};
@@ -167,6 +168,73 @@ pub async fn set_ethercat_devices<const MAX_SUBDEVICES: usize, const MAX_PDI: us
Ok(())
}
pub struct Groups<S = PreOp> {
aquapath: SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, S>,
rest: SubDeviceGroup<MAX_SUBDEVICES, PDI_LEN, S>,
}
impl Default for Groups<PreOp> {
fn default() -> Self {
Self {
aquapath: SubDeviceGroup::default(),
rest: SubDeviceGroup::default(),
}
}
}
impl Groups<PreOp> {
pub fn get_devices(&mut self, maindevice: &MainDevice) -> Result<Vec<Arc<RwLock<dyn EthercatDevice>>>, anyhow::Error> {
let mut devices = devices_from_subdevices::<MAX_SUBDEVICES, PDI_LEN>(&mut self.rest, maindevice)?;
devices.extend(devices_from_subdevices::<MAX_SUBDEVICES, PDI_LEN>(&mut self.aquapath, maindevice)?);
Ok(devices)
}
pub async fn into_safe_op(self, maindevice: &MainDevice<'_>) -> Result<Groups<SafeOp>, ethercrab::error::Error> {
Ok(Groups {
rest: self.rest.into_safe_op(maindevice).await?,
aquapath: self.aquapath.into_safe_op(maindevice).await?,
})
}
pub fn iter<'groups, 'maindevice>(&'groups self, maindevice: &'maindevice MainDevice<'groups>) -> Vec<SubDeviceRef<'maindevice, &'groups SubDevice>> {
self.rest.iter(&maindevice)
.chain(self.aquapath.iter(&maindevice))
.collect::<Vec<_>>()
}
}
impl Groups<SafeOp> {
pub async fn tx_rx_sync_system_time<'maindevice>(&self, maindevice: &'maindevice MainDevice<'maindevice>) -> Result<(), ethercrab::error::Error> {
self.rest.tx_rx_sync_system_time(maindevice).await?;
self.aquapath.tx_rx_sync_system_time(maindevice).await?;
Ok(())
}
pub async fn into_op<'maindevice>(self, maindevice: &'maindevice MainDevice<'maindevice>) -> Result<Groups<Op>, ethercrab::error::Error> {
Ok(Groups {
aquapath: self.aquapath.into_op(maindevice).await?,
rest: self.rest.into_op(maindevice).await?,
})
}
}
impl Groups<Op> {
pub fn iter<'groups, 'maindevice>(&'groups self, maindevice: &'maindevice MainDevice<'groups>) -> impl Iterator<Item = SubDeviceRef<'maindevice, SubDevicePdi<'maindevice, PDI_LEN>>> {
self.rest.iter(&maindevice)
.chain(self.aquapath.iter(&maindevice))
}
pub async fn tx_rx<'maindevice>(&self, maindevice: &'maindevice MainDevice<'maindevice>) -> Result<(), ethercrab::error::Error> {
self.rest.tx_rx(maindevice).await?;
self.aquapath.tx_rx(maindevice).await?;
Ok(())
}
}
pub async fn setup_loop(
interface: &str,
app_state: Arc<SharedState>,
@@ -263,12 +331,21 @@ pub async fn setup_loop(
// Initalize subdevices
// Fails if DC setup detects a mispatching working copunter, then just try again in loop
let mut group_preop = match maindevice
.init_single_group::<MAX_SUBDEVICES, PDI_LEN>(ethercat_now)
let mut groups_preop = match maindevice
.init::<MAX_SUBDEVICES, _>(ethercat_now, |groups: &Groups, subdevice| {
let identification = smol::block_on(machine_device_identification(subdevice, &maindevice))?;
let machine = identification.machine_identification_unique.machine_identification.machine;
if machine == MACHINE_AQUAPATH_V1 {
return Ok(&groups.aquapath);
}
Ok(&groups.rest)
})
.await
{
Ok(group) => {
tracing::info!("Initialized {} subdevices", &group.len());
tracing::info!("Initialized {} subdevices", &maindevice.num_subdevices());
group
}
Err(err) => Err(anyhow::anyhow!(
@@ -279,9 +356,8 @@ pub async fn setup_loop(
};
// create devices
let devices =
devices_from_subdevices::<MAX_SUBDEVICES, PDI_LEN>(&mut group_preop, &maindevice)?;
let subdevices = group_preop.iter(&maindevice).collect::<Vec<_>>();
let devices = groups_preop.get_devices(&maindevice)?;
let subdevices = groups_preop.iter(&maindevice);
// extract device identifications
let device_identifications = read_device_identifications(&subdevices, &maindevice)
@@ -310,7 +386,7 @@ pub async fn setup_loop(
// filter devices and if Option<DeviceMachineIdentification> is Some
// return identified_devices, identified_device_identifications, identified_subdevices
let (identified_device_identifications,identified_devices, identified_subdevices): (
let (identified_device_identifications, identified_devices, identified_subdevices): (
Vec<_>,
Vec<_>,
Vec<_>,
@@ -433,10 +509,10 @@ pub async fn setup_loop(
)
.await?;
let group_safe = match group_preop.into_safe_op(&maindevice).await {
Ok(group_op) => {
let groups_safe = match groups_preop.into_safe_op(&maindevice).await {
Ok(groups) => {
tracing::info!("Group in Safe-OP state");
group_op
groups
}
Err(err) => Err(anyhow::anyhow!(
"[{}::setup_loop] Failed to put group in Safe-OP state: {:?}",
@@ -450,7 +526,7 @@ pub async fn setup_loop(
// For now we just check if we use wago coupler or IP20
if has_dc {
for _ in 1..1000 {
let res = group_safe.tx_rx_sync_system_time(&maindevice).await;
let res = groups_safe.tx_rx_sync_system_time(&maindevice).await;
match res {
Ok(_) => (),
Err(e) => tracing::error!(
@@ -463,7 +539,7 @@ pub async fn setup_loop(
}
// Put group in operational state
let group_op = match group_safe.into_op(&maindevice).await {
let groups_op = match groups_safe.into_op(&maindevice).await {
Ok(group_op) => {
tracing::info!("Group in OP state");
group_op
@@ -498,7 +574,7 @@ pub async fn setup_loop(
Ok(EthercatSetup {
devices,
group: group_op,
groups: groups_op,
maindevice,
})
}
+20 -15
View File
@@ -93,18 +93,23 @@ pub fn start_loop_thread(
}
HotThreadMessage::WriteMachineDeviceInfo(info_request) => {
if let Some(ethercat_setup) = &rt_loop_inputs.ethercat_setup {
if let Ok(subdevice) = ethercat_setup.group.subdevice(
&ethercat_setup.maindevice,
info_request
.hardware_identification_ethercat
.subdevice_index,
) {
let _res = smol::block_on(write_machine_device_identification(
&subdevice,
&ethercat_setup.maindevice,
&info_request.device_machine_identification,
));
}
// TODO: this will target the wrong subdevice. Whereever we assign the
// subdevie indecies, we have to account for the groups being slip and
// add an offset accordingly. For now, this is just disabled to test
// stuff.
//
// if let Ok(subdevice) = ethercat_setup.group.subdevice(
// &ethercat_setup.maindevice,
// info_request
// .hardware_identification_ethercat
// .subdevice_index,
// ) {
// let _res = smol::block_on(write_machine_device_identification(
// &subdevice,
// &ethercat_setup.maindevice,
// &info_request.device_machine_identification,
// ));
// }
}
}
HotThreadMessage::DeleteMachine(unique_id) => {
@@ -166,13 +171,13 @@ pub async fn copy_ethercat_inputs(
// - copy inputs to devices
if let Some(ethercat_setup) = ethercat_setup {
ethercat_setup
.group
.groups
.tx_rx(&ethercat_setup.maindevice)
.await?;
// copy inputs to devices
for (i, subdevice) in ethercat_setup
.group
.groups
.iter(&ethercat_setup.maindevice)
.enumerate()
{
@@ -219,7 +224,7 @@ pub async fn copy_ethercat_outputs(
if let Some(ethercat_setup) = ethercat_setup {
// copy outputs from devices
for (i, subdevice) in ethercat_setup
.group
.groups
.iter(&ethercat_setup.maindevice)
.enumerate()
{