Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
305fc84474 | ||
|
|
5e1126e896 | ||
|
|
e91719f902 | ||
|
|
0efa9bcb07 | ||
|
|
37d897d240 | ||
|
|
7488e1d97a |
No files matched your search
+6
-1
@@ -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
|
||||
|
||||
@@ -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://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));
|
||||
|
||||
@@ -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)
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(
|
||||
ðercat_setup.maindevice,
|
||||
info_request
|
||||
.hardware_identification_ethercat
|
||||
.subdevice_index,
|
||||
) {
|
||||
let _res = smol::block_on(write_machine_device_identification(
|
||||
&subdevice,
|
||||
ðercat_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(
|
||||
// ðercat_setup.maindevice,
|
||||
// info_request
|
||||
// .hardware_identification_ethercat
|
||||
// .subdevice_index,
|
||||
// ) {
|
||||
// let _res = smol::block_on(write_machine_device_identification(
|
||||
// &subdevice,
|
||||
// ðercat_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(ðercat_setup.maindevice)
|
||||
.await?;
|
||||
|
||||
// copy inputs to devices
|
||||
for (i, subdevice) in ethercat_setup
|
||||
.group
|
||||
.groups
|
||||
.iter(ðercat_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(ðercat_setup.maindevice)
|
||||
.enumerate()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user