wago modules detected and wrapped as an EtherCatDevice

This commit is contained in:
rob committed 2025-12-16 08:25:19 +01:00
1 parent e4d0b3c5be
commit 8c7e810359
17 files changed
+1110 -94

No files matched your search

+21
View File
@@ -251,6 +251,27 @@ pub fn ethercat_device_derive(input: TokenStream) -> TokenStream {
fn as_any(&self) -> &dyn std::any::Any {
self
}
#[doc="Implemented by the ethercat_hal_derive::EthercatDevice derive macro"]
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
#[doc="Implemented by the ethercat_hal_derive::EthercatDevice derive macro"]
fn is_module(&self) -> bool {
false
}
#[doc="Implemented by the ethercat_hal_derive::EthercatDevice derive macro"]
fn get_module(&self) -> Option<crate::devices::Module> {
None
}
#[doc="Implemented by the ethercat_hal_derive::EthercatDevice derive macro"]
fn set_module(&mut self,module : crate::devices::Module){
()
}
}
impl crate::devices::EthercatDeviceUsed for #name {
+37 -6
View File
@@ -21,6 +21,7 @@ pub mod el7031;
pub mod el7031_0030;
pub mod el7041_0052;
pub mod wago_750_354;
pub mod wago_modules;
use super::devices::el1008::EL1008;
use crate::{
@@ -42,17 +43,29 @@ use el3021::EL3021_IDENTITY_A;
use el3024::EL3024_IDENTITY_A;
use el3062_0030::EL3062_0030_IDENTITY_A;
use el3204::EL3204_IDENTITY_A;
use el3204::EL3204_IDENTITY_B;
use el4002::EL4002_IDENTITY_A;
use el5152::{EL5152, EL5152_IDENTITY_A};
use el6021::{EL6021_IDENTITY_A, EL6021_IDENTITY_B, EL6021_IDENTITY_C, EL6021_IDENTITY_D};
use el3204::EL3204_IDENTITY_B;
use el7031::{EL7031_IDENTITY_A, EL7031_IDENTITY_B};
use el7031_0030::EL7031_0030_IDENTITY_A;
use el7041_0052::EL7041_0052_IDENTITY_A;
use ethercrab::{MainDevice, SubDeviceIdentity};
use smol::lock::RwLock;
use wago_750_354::{WAGO_750_354_IDENTITY_A, Wago750_354};
use std::{any::Any, fmt::Debug, sync::Arc};
use wago_750_354::{WAGO_750_354_IDENTITY_A, Wago750_354};
#[derive(Debug, Clone, Copy)]
pub struct Module {
pub slot: u16,
pub belongs_to_addr: u16,
pub has_tx: bool,
pub has_rx: bool,
pub vendor_id: u32,
pub product_id: u32,
pub tx_offset: usize,
pub rx_offset: usize,
}
/// A trait for all devices
///
@@ -77,7 +90,7 @@ where
/// automatically validate input length, then calls input
fn input_checked(&mut self, input: &BitSlice<u8, Lsb0>) -> Result<(), anyhow::Error> {
// validate input has correct length
let expected = self.input_len();
/* let expected = self.input_len();
let actual = input.len();
if actual != expected {
return Err(anyhow::anyhow!(
@@ -88,7 +101,7 @@ where
expected,
expected / 8
));
}
}*/
self.input(input)
}
@@ -103,7 +116,7 @@ where
self.output(output)?;
// validate input has correct length
let expected = self.output_len();
/*let expected = self.output_len();
let actual = output.len();
if output.len() != expected {
return Err(anyhow::anyhow!(
@@ -114,12 +127,26 @@ where
expected,
expected / 8
));
}
}*/
Ok(())
}
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn is_module(&self) -> bool;
fn get_module(&self) -> Option<Module>;
fn set_module(&mut self, module: Module);
}
pub trait DynamicEthercatDevice: EthercatDevice + EthercatDynamicPDO {}
pub trait EthercatDynamicPDO {
fn get_tx_offset(&self) -> usize;
fn get_rx_offset(&self) -> usize;
fn set_tx_offset(&mut self, offset: usize);
fn set_rx_offset(&mut self, offset: usize);
}
/// A trait for devices that want to process input and output data
@@ -257,6 +284,10 @@ pub async fn specific_device_from_devices<DEVICE: EthercatDevice>(
}
pub type SubDeviceIdentityTuple = (u32, u32, u32);
// Is vendor id at 0, and prodid at 1
pub type SubDeviceProductTuple = (u32, u32);
/// function that converts SubDeviceIdentity to tuple
pub const fn subdevice_identity_to_tuple(identity: &SubDeviceIdentity) -> SubDeviceIdentityTuple {
(identity.vendor_id, identity.product_id, identity.revision)
@@ -1,6 +0,0 @@
pub const WAGO_750_1506_VENDOR_ID: u32 = 0x00000021;
pub const WAGO_750_1506_PRODUCT_ID: u32 = 0x07500354;
+276 -60
View File
@@ -1,52 +1,155 @@
use std::sync::Arc;
use crate::helpers::ethercrab_types::EthercrabSubDevicePreoperational;
use super::{EthercatDevice, EthercatDeviceProcessing, NewEthercatDevice, SubDeviceIdentityTuple};
use super::{
EthercatDevice, EthercatDeviceProcessing, EthercatDeviceUsed, NewEthercatDevice,
SubDeviceIdentityTuple,
};
use crate::devices::wago_modules::*;
use crate::{
devices::{
DynamicEthercatDevice, Module,
wago_modules::{
wago_750_501::{WAGO_750_501_MODULE_IDENT, WAGO_750_501_PRODUCT_ID},
wago_750_652::{WAGO_750_652_MODULE_IDENT, WAGO_750_652_PRODUCT_ID},
wago_750_1506::{WAGO_750_1506_MODULE_IDENT, WAGO_750_1506_PRODUCT_ID},
},
},
helpers::ethercrab_types::EthercrabSubDevicePreoperational,
};
use anyhow::Error;
use ethercat_hal_derive::EthercatDevice;
use smol::lock::RwLock;
use std::sync::Arc;
const MODULE_COUNT_INDEX : (u16,u8) = (0xf050,0x00);
const TX_MAPPING_INDEX : (u16,u8) = (0x1c13,0x00);
const RX_MAPPING_INDEX : (u16,u8) = (0x1c12,0x00);
const MODULE_COUNT_INDEX: (u16, u8) = (0xf050, 0x00);
const TX_MAPPING_INDEX: (u16, u8) = (0x1c13, 0x00);
const RX_MAPPING_INDEX: (u16, u8) = (0x1c12, 0x00);
// For both the rx and tx The Wago Coupler has 4 bytes, which we dont care about and skip
/// Wago750_354 bus coupler
#[derive(Clone, EthercatDevice)]
/*
The "Modules" simply write at an offset into rx and read at an offset in tx
*/
pub struct Wago750_354 {
is_used: bool,
pub slots: [Option<Module>; 64],
pub slot_devices: [Option<Arc<RwLock<dyn DynamicEthercatDevice>>>; 64],
pub dev_count: usize,
pub module_count: usize,
rx_offsets: Vec<usize>,
tx_offsets: Vec<usize>,
tx_size: usize,
rx_size: usize,
}
impl EthercatDevice for Wago750_354 {
fn input(
&mut self,
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
for slot_device in &mut self.slot_devices {
match slot_device {
Some(device) => {
// Give all Modules access to the couplers input image and call their input func
let mut d = device.write_blocking();
let _ = d.input(input);
drop(d);
}
None => break,
}
}
Ok(())
}
fn input_len(&self) -> usize {
self.tx_size
}
fn output(
&self,
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
for slot_device in &self.slot_devices {
match slot_device {
Some(device) => {
// Give all Modules access to the couplers Output image and call their output func
let d = device.read_blocking();
let _ = d.output(output);
drop(d);
}
None => break,
}
}
Ok(())
}
fn output_len(&self) -> usize {
self.rx_size
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn is_module(&self) -> bool {
false
}
fn get_module(&self) -> Option<Module> {
None
}
fn set_module(&mut self, module: Module) {
self.slots[self.module_count] = Some(module);
self.module_count += 1;
self.tx_size += module.tx_offset;
self.rx_size += module.rx_offset;
}
}
impl EthercatDeviceUsed for Wago750_354 {
fn is_used(&self) -> bool {
self.is_used
}
fn set_used(&mut self, used: bool) {
self.is_used = used;
}
}
impl EthercatDeviceProcessing for Wago750_354 {}
impl NewEthercatDevice for Wago750_354 {
fn new() -> Self {
Self { is_used: false }
Self {
is_used: false,
slots: [const { None }; 64],
slot_devices: [const { None }; 64],
module_count: 0,
dev_count: 0,
tx_size: 0,
rx_size: 0,
rx_offsets: vec![],
tx_offsets: vec![],
}
}
}
impl std::fmt::Debug for Wago750_354 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Wago_750_354")
}
}
#[derive(Debug)]
pub struct Module {
pub slot : u16,
pub belongs_to_slave_index : u16,
pub has_tx : bool,
pub has_rx : bool,
pub vendor_id : u32,
pub product_id : u32,
pub name : String,
}
impl Wago750_354 {
pub async fn get_pdo_offsets<'a>(device: &EthercrabSubDevicePreoperational<'a>,get_tx : bool) -> Result<Vec<usize>,Error> {
let mut vec : Vec<usize>= vec![];
pub async fn get_pdo_offsets<'a>(
&mut self,
device: &EthercrabSubDevicePreoperational<'a>,
get_tx: bool,
) -> Result<(), Error> {
let mut vec: Vec<usize> = vec![];
let mut bit_offset = 0;
let start_subindex = 0x1;
let index = match get_tx {
@@ -57,69 +160,182 @@ impl Wago750_354 {
for i in 0..count {
vec.push(bit_offset);
let pdo_index = device.sdo_read(index.0, start_subindex+i).await?;
let pdo_index = device.sdo_read(index.0, start_subindex + i).await?;
if pdo_index != 0 {
let pdo_map_count = device.sdo_read::<u8>(pdo_index, 0).await?;
// Iterate over every PDO Mapped entry and add it to the cumulative bit offset
for j in 0..pdo_map_count {
let pdo_mapping : u32 = device.sdo_read(pdo_index, 1 + j).await?;
// We only need / Want the bit len
let pdo_mapping: u32 = device.sdo_read(pdo_index, 1 + j).await?;
// We only need / Want the bit len, which we extract with a bitmask extracting the lsb
let bit_length = (pdo_mapping & 0xFF) as u8;
bit_offset += bit_length as usize;
}
}
}
}
tracing::info!("{} is tx: {}",count,get_tx);
Ok(vec)
if get_tx {
self.tx_offsets = vec;
} else {
self.rx_offsets = vec;
}
Ok(())
}
// The Coupler gets itself as a Preop Subdevice
pub async fn get_module_count<'a>(device: &EthercrabSubDevicePreoperational<'a>) -> Result<usize,Error> {
match device.sdo_read::<u8>(MODULE_COUNT_INDEX.0, MODULE_COUNT_INDEX.1).await {
pub async fn get_module_count<'a>(
device: &EthercrabSubDevicePreoperational<'a>,
) -> Result<usize, Error> {
match device
.sdo_read::<u8>(MODULE_COUNT_INDEX.0, MODULE_COUNT_INDEX.1)
.await
{
Ok(value) => Ok(value as usize),
Err(e) => Err(anyhow::anyhow!("Failed to read Module Count for Wago750_354: {:?}",e)),
Err(e) => Err(anyhow::anyhow!(
"Failed to read Module Count for Wago750_354: {:?}",
e
)),
}
}
pub async fn get_modules<'a>(device: &EthercrabSubDevicePreoperational<'a>, module_count : usize) -> Result<Vec<Module>,Error> {
const MODULES_START_ADDR : u16 = 0x9000;
let modules : Vec<Module> = vec![];
// This should probably be a generic function instead
pub async fn get_modules<'a>(
device: &EthercrabSubDevicePreoperational<'a>,
module_count: usize,
) -> Result<Vec<crate::devices::Module>, Error> {
const MODULES_START_ADDR: u16 = 0x9000;
const MODULE_IDENT_SUBINDEX: u8 = 0x0a;
let mut modules: Vec<Module> = vec![];
for i in 0..module_count {
let module_addr = MODULES_START_ADDR + (i * 0x10) as u16;
let ident = device.sdo_read::<u32>(module_addr, 0x00).await?;
device.sdo_read(index, sub_index).await?;
let ident_iom = device
.sdo_read::<u32>(module_addr, MODULE_IDENT_SUBINDEX)
.await?;
// For Wago the IOM well be the product ID
let mut module = Module {
slot: i as u16,
belongs_to_addr: device.configured_address(),
vendor_id: device.identity().vendor_id,
product_id: ident_iom,
has_tx: false,
has_rx: false,
tx_offset: 0,
rx_offset: 0,
};
match ident_iom {
// Add Module idents here:
WAGO_750_1506_PRODUCT_ID => {
module.has_tx = true;
module.has_rx = true;
}
WAGO_750_501_PRODUCT_ID => {
module.has_tx = false;
module.has_rx = true;
}
WAGO_750_652_PRODUCT_ID => {
module.has_tx = true;
module.has_rx = true;
}
_ => println!(
"Wago-750-354 found Unknown/Unimplemented Module: {}",
ident_iom
),
}
modules.push(module);
}
Ok(vec![])
Ok(modules)
}
pub async fn initialize_modules<'a> (device: &EthercrabSubDevicePreoperational<'a>) -> Result<Vec<Arc<RwLock<dyn EthercatDevice>>>,Error> {
/// Call after all modules have been added
pub fn init_slot_modules<'a>(&mut self, device: &EthercrabSubDevicePreoperational<'a>) {
// Already initialized
if self.dev_count != 0 {
return;
}
let mut tx_index = 1;
let mut rx_index = 1;
smol::block_on(async {
let _ = self.get_pdo_offsets(device, true).await;
let _ = self.get_pdo_offsets(device, false).await;
});
for module in self.slots {
match module {
Some(m) => {
// Map ModuleIdent's to Terminals
let dev: Arc<RwLock<dyn DynamicEthercatDevice>> = match (
m.vendor_id,
m.product_id,
) {
WAGO_750_501_MODULE_IDENT => {
Arc::new(RwLock::new(wago_750_501::Wago750_501::new()))
}
WAGO_750_1506_MODULE_IDENT => {
Arc::new(RwLock::new(wago_750_1506::Wago750_1506::new()))
}
WAGO_750_652_MODULE_IDENT => {
Arc::new(RwLock::new(wago_750_652::Wago750_652::new()))
}
_ => {
println!(
"{} Missing Implementation for Module Identification: vendor_id: {:?}, module ident: {:?} !",
module_path!(),
m.vendor_id,
m.product_id
);
return;
}
};
let tx_pdo_offset = self.tx_offsets.get(tx_index as usize);
if m.has_tx {
tx_index += 1;
}
let rx_pdo_offset = self.rx_offsets.get(rx_index as usize);
if m.has_rx {
rx_index += 1;
}
let mut dev_guard = dev.write_blocking();
match tx_pdo_offset {
Some(offset) => dev_guard.set_tx_offset(*offset),
None => (),
}
match rx_pdo_offset {
Some(offset) => dev_guard.set_rx_offset(*offset),
None => (),
}
drop(dev_guard);
self.slot_devices[self.dev_count] = Some(dev);
self.dev_count += 1;
}
None => break,
}
}
}
pub async fn initialize_modules<'a>(
device: &EthercrabSubDevicePreoperational<'a>,
) -> Result<Vec<Module>, Error> {
let count = match Wago750_354::get_module_count(device).await {
Ok(count) => count,
Err(e) => return Err(e),
};
if count == 0 {
return Ok(vec![]);
}
let tx_offsets = Wago750_354::get_pdo_offsets(device,true).await?;
let rx_offsets = Wago750_354::get_pdo_offsets(device,false).await?;
println!("{:?}",tx_offsets);
println!("{:?}",rx_offsets);
Ok(vec![])
let modules = Wago750_354::get_modules(device, count).await?;
Ok(modules)
}
}
pub const WAGO_750_354_VENDOR_ID: u32 = 0x00000021;
pub const WAGO_750_354_PRODUCT_ID: u32 = 0x07500354;
pub const WAGO_750_354_REVISION_A: u32 = 0x2;
pub const WAGO_750_354_IDENTITY_A: SubDeviceIdentityTuple =
(WAGO_750_354_VENDOR_ID, WAGO_750_354_PRODUCT_ID, WAGO_750_354_REVISION_A);
pub const WAGO_750_354_IDENTITY_A: SubDeviceIdentityTuple = (
WAGO_750_354_VENDOR_ID,
WAGO_750_354_PRODUCT_ID,
WAGO_750_354_REVISION_A,
);
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,3 @@
pub mod wago_750_1506;
pub mod wago_750_501;
pub mod wago_750_652;
@@ -0,0 +1,335 @@
use crate::{
devices::{
DynamicEthercatDevice, EthercatDevice, EthercatDeviceProcessing, EthercatDeviceUsed,
EthercatDynamicPDO, Module, NewEthercatDevice, SubDeviceProductTuple,
},
io::{
digital_input::{DigitalInputDevice, DigitalInputInput},
digital_output::{DigitalOutputDevice, DigitalOutputOutput},
},
};
#[derive(Debug, Clone)]
pub enum Wago750_1506OutputPort {
DO1,
DO2,
DO3,
DO4,
DO5,
DO6,
DO7,
DO8,
}
impl From<Wago750_1506OutputPort> for usize {
fn from(value: Wago750_1506OutputPort) -> Self {
match value {
Wago750_1506OutputPort::DO1 => 0,
Wago750_1506OutputPort::DO2 => 1,
Wago750_1506OutputPort::DO3 => 2,
Wago750_1506OutputPort::DO4 => 3,
Wago750_1506OutputPort::DO5 => 4,
Wago750_1506OutputPort::DO6 => 5,
Wago750_1506OutputPort::DO7 => 6,
Wago750_1506OutputPort::DO8 => 7,
}
}
}
#[derive(Debug, Clone)]
pub enum Wago750_1506InputPort {
DO1,
DO2,
DO3,
DO4,
DO5,
DO6,
DO7,
DO8,
}
impl From<Wago750_1506InputPort> for usize {
fn from(value: Wago750_1506InputPort) -> Self {
match value {
Wago750_1506InputPort::DO1 => 0,
Wago750_1506InputPort::DO2 => 1,
Wago750_1506InputPort::DO3 => 2,
Wago750_1506InputPort::DO4 => 3,
Wago750_1506InputPort::DO5 => 4,
Wago750_1506InputPort::DO6 => 5,
Wago750_1506InputPort::DO7 => 6,
Wago750_1506InputPort::DO8 => 7,
}
}
}
#[derive(Clone, Default)]
pub struct Wago750_1506RxPdo {
port1: bool,
port2: bool,
port3: bool,
port4: bool,
port5: bool,
port6: bool,
port7: bool,
port8: bool,
}
#[derive(Clone, Default)]
pub struct Wago750_1506TxPdo {
port1: bool,
port2: bool,
port3: bool,
port4: bool,
port5: bool,
port6: bool,
port7: bool,
port8: bool,
}
impl DigitalInputDevice<Wago750_1506InputPort> for Wago750_1506 {
fn get_input(&self, port: Wago750_1506InputPort) -> Result<DigitalInputInput, anyhow::Error> {
Ok(DigitalInputInput {
value: match port {
Wago750_1506InputPort::DO1 => self.tx_pdo.port1,
Wago750_1506InputPort::DO2 => self.tx_pdo.port2,
Wago750_1506InputPort::DO3 => self.tx_pdo.port3,
Wago750_1506InputPort::DO4 => self.tx_pdo.port4,
Wago750_1506InputPort::DO5 => self.tx_pdo.port5,
Wago750_1506InputPort::DO6 => self.tx_pdo.port6,
Wago750_1506InputPort::DO7 => self.tx_pdo.port7,
Wago750_1506InputPort::DO8 => self.tx_pdo.port8,
},
})
}
}
impl DigitalOutputDevice<Wago750_1506OutputPort> for Wago750_1506 {
/// Writes the new output value into the device's RXPDO structure (in-memory PDI).
fn set_output(&mut self, port: Wago750_1506OutputPort, value: DigitalOutputOutput) {
// The DigitalOutputOutput is converted to a bool using the From trait
let output_value: bool = value.into();
match port {
Wago750_1506OutputPort::DO1 => self.rx_pdo.port1 = output_value,
Wago750_1506OutputPort::DO2 => self.rx_pdo.port2 = output_value,
Wago750_1506OutputPort::DO3 => self.rx_pdo.port3 = output_value,
Wago750_1506OutputPort::DO4 => self.rx_pdo.port4 = output_value,
Wago750_1506OutputPort::DO5 => self.rx_pdo.port5 = output_value,
Wago750_1506OutputPort::DO6 => self.rx_pdo.port6 = output_value,
Wago750_1506OutputPort::DO7 => self.rx_pdo.port7 = output_value,
Wago750_1506OutputPort::DO8 => self.rx_pdo.port8 = output_value,
}
}
/// Reads the current output value from the device's RXPDO structure (in-memory PDI).
fn get_output(&self, port: Wago750_1506OutputPort) -> DigitalOutputOutput {
let current_value = match port {
Wago750_1506OutputPort::DO1 => self.rx_pdo.port1,
Wago750_1506OutputPort::DO2 => self.rx_pdo.port2,
Wago750_1506OutputPort::DO3 => self.rx_pdo.port3,
Wago750_1506OutputPort::DO4 => self.rx_pdo.port4,
Wago750_1506OutputPort::DO5 => self.rx_pdo.port5,
Wago750_1506OutputPort::DO6 => self.rx_pdo.port6,
Wago750_1506OutputPort::DO7 => self.rx_pdo.port7,
Wago750_1506OutputPort::DO8 => self.rx_pdo.port8,
};
// Wrap the bool back into the type-safe wrapper
DigitalOutputOutput(current_value)
}
}
#[derive(Clone)]
pub struct Wago750_1506 {
is_used: bool,
tx_bit_offset: usize,
rx_bit_offset: usize,
// Should always have on but not when calling constructor
module: Option<Module>,
rx_pdo: Wago750_1506RxPdo,
tx_pdo: Wago750_1506TxPdo,
}
impl DynamicEthercatDevice for Wago750_1506 {}
impl EthercatDynamicPDO for Wago750_1506 {
fn get_tx_offset(&self) -> usize {
self.tx_bit_offset
}
fn get_rx_offset(&self) -> usize {
self.rx_bit_offset
}
fn set_tx_offset(&mut self, offset: usize) {
self.tx_bit_offset = offset
}
fn set_rx_offset(&mut self, offset: usize) {
self.rx_bit_offset = offset
}
}
impl EthercatDeviceUsed for Wago750_1506 {
fn is_used(&self) -> bool {
self.is_used
}
fn set_used(&mut self, used: bool) {
self.is_used = used;
}
}
impl Wago750_1506 {}
impl EthercatDevice for Wago750_1506 {
// input_len 48 offset: 40
fn input(
&mut self,
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
let base = self.tx_bit_offset;
let idx1 = base + Into::<usize>::into(Wago750_1506InputPort::DO1);
self.tx_pdo.port1 = *input.get(idx1).expect("Bit 1 out of bounds");
let idx2 = base + Into::<usize>::into(Wago750_1506InputPort::DO2);
self.tx_pdo.port2 = *input.get(idx2).expect("Bit 2 out of bounds");
let idx3 = base + Into::<usize>::into(Wago750_1506InputPort::DO3);
self.tx_pdo.port3 = *input.get(idx3).expect("Bit 3 out of bounds");
let idx4 = base + Into::<usize>::into(Wago750_1506InputPort::DO4);
self.tx_pdo.port4 = *input.get(idx4).expect("Bit 4 out of bounds");
let idx5 = base + Into::<usize>::into(Wago750_1506InputPort::DO5);
self.tx_pdo.port5 = *input.get(idx5).expect("Bit 5 out of bounds");
let idx6 = base + Into::<usize>::into(Wago750_1506InputPort::DO6);
self.tx_pdo.port6 = *input.get(idx6).expect("Bit 6 out of bounds");
let idx7 = base + Into::<usize>::into(Wago750_1506InputPort::DO7);
self.tx_pdo.port7 = *input.get(idx7).expect("Bit 7 out of bounds");
let idx8 = base + Into::<usize>::into(Wago750_1506InputPort::DO8);
self.tx_pdo.port8 = *input.get(idx8).expect("Bit 8 out of bounds");
Ok(())
}
fn input_len(&self) -> usize {
8
}
fn output(
&self,
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
// println!("output: {} {}",self.rx_bit_offset,_output);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO1),
self.rx_pdo.port1,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO2),
self.rx_pdo.port2,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO3),
self.rx_pdo.port3,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO4),
self.rx_pdo.port4,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO5),
self.rx_pdo.port5,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO6),
self.rx_pdo.port6,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO7),
self.rx_pdo.port7,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_1506OutputPort::DO8),
self.rx_pdo.port8,
);
Ok(())
}
fn output_len(&self) -> usize {
8
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn is_module(&self) -> bool {
true
}
fn input_checked(
&mut self,
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn output_checked(
&self,
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn get_module(&self) -> Option<Module> {
self.module.clone()
}
fn set_module(&mut self, module: Module) {
self.tx_bit_offset = module.tx_offset;
self.rx_bit_offset = module.rx_offset;
self.module = Some(module);
}
}
impl EthercatDeviceProcessing for Wago750_1506 {
fn input_post_process(&mut self) -> Result<(), anyhow::Error> {
Ok(())
}
fn output_pre_process(&mut self) -> Result<(), anyhow::Error> {
Ok(())
}
}
impl NewEthercatDevice for Wago750_1506 {
fn new() -> Self {
Self {
is_used: false,
tx_bit_offset: 0,
rx_bit_offset: 0,
module: None,
rx_pdo: Wago750_1506RxPdo::default(),
tx_pdo: Wago750_1506TxPdo::default(),
}
}
}
impl std::fmt::Debug for Wago750_1506 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Wago750_1506")
}
}
pub const WAGO_750_1506_VENDOR_ID: u32 = 0x00000021;
pub const WAGO_750_1506_PRODUCT_ID: u32 = 2147483779;
pub const WAGO_750_1506_MODULE_IDENT: SubDeviceProductTuple =
(WAGO_750_1506_VENDOR_ID, WAGO_750_1506_PRODUCT_ID);
@@ -0,0 +1,188 @@
use crate::devices::{
DynamicEthercatDevice, EthercatDevice, EthercatDeviceUsed, EthercatDynamicPDO, Module,
SubDeviceProductTuple,
};
use crate::devices::{EthercatDeviceProcessing, NewEthercatDevice};
use crate::io::digital_output::{DigitalOutputDevice, DigitalOutputOutput};
#[derive(Clone)]
pub struct Wago750_501 {
is_used: bool,
tx_bit_offset: usize,
rx_bit_offset: usize,
pub rxpdo: Wago750_501RxPdo,
module: Option<Module>,
}
#[derive(Debug, Clone)]
pub enum Wago750_501Port {
Port1,
Port2,
}
impl From<Wago750_501Port> for usize {
fn from(value: Wago750_501Port) -> Self {
match value {
Wago750_501Port::Port1 => 0,
Wago750_501Port::Port2 => 1,
}
}
}
#[derive(Clone, Default)]
pub struct Wago750_501RxPdo {
pub port1: bool,
pub port2: bool,
}
impl DigitalOutputDevice<Wago750_501Port> for Wago750_501 {
/// Writes the new output value into the device's RXPDO structure (in-memory PDI).
fn set_output(&mut self, port: Wago750_501Port, value: DigitalOutputOutput) {
// The DigitalOutputOutput is converted to a bool using the From trait
let output_value: bool = value.into();
match port {
Wago750_501Port::Port1 => {
// Modify the internal PDO representation
self.rxpdo.port1 = output_value;
}
Wago750_501Port::Port2 => {
// Modify the internal PDO representation
self.rxpdo.port2 = output_value;
}
}
}
/// Reads the current output value from the device's RXPDO structure (in-memory PDI).
fn get_output(&self, port: Wago750_501Port) -> DigitalOutputOutput {
let current_value = match port {
Wago750_501Port::Port1 => self.rxpdo.port1,
Wago750_501Port::Port2 => self.rxpdo.port2,
};
// Wrap the bool back into the type-safe wrapper
DigitalOutputOutput(current_value)
}
}
impl EthercatDeviceUsed for Wago750_501 {
fn is_used(&self) -> bool {
self.is_used
}
fn set_used(&mut self, used: bool) {
self.is_used = used;
}
}
impl DynamicEthercatDevice for Wago750_501 {}
impl EthercatDynamicPDO for Wago750_501 {
fn get_tx_offset(&self) -> usize {
self.tx_bit_offset
}
fn get_rx_offset(&self) -> usize {
self.rx_bit_offset
}
fn set_tx_offset(&mut self, offset: usize) {
self.tx_bit_offset = offset
}
fn set_rx_offset(&mut self, offset: usize) {
self.rx_bit_offset = offset
}
}
impl EthercatDevice for Wago750_501 {
fn input(
&mut self,
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn input_len(&self) -> usize {
0
}
fn output(
&self,
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_501Port::Port1),
self.rxpdo.port1,
);
output.set(
self.rx_bit_offset + Into::<usize>::into(Wago750_501Port::Port2),
self.rxpdo.port2,
);
Ok(())
}
fn output_len(&self) -> usize {
2
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn is_module(&self) -> bool {
true
}
fn input_checked(
&mut self,
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn output_checked(
&self,
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn get_module(&self) -> Option<crate::devices::Module> {
self.module.clone()
}
fn set_module(&mut self, module: crate::devices::Module) {
self.tx_bit_offset = module.tx_offset;
self.rx_bit_offset = module.rx_offset;
self.module = Some(module)
}
}
impl EthercatDeviceProcessing for Wago750_501 {}
impl NewEthercatDevice for Wago750_501 {
fn new() -> Self {
Self {
is_used: false,
tx_bit_offset: 0,
rx_bit_offset: 0,
module: None,
rxpdo: Wago750_501RxPdo::default(),
}
}
}
impl std::fmt::Debug for Wago750_501 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Wago750_501")
}
}
pub const WAGO_750_501_VENDOR_ID: u32 = 0x00000021;
pub const WAGO_750_501_PRODUCT_ID: u32 = 2147483682;
pub const WAGO_750_501_MODULE_IDENT: SubDeviceProductTuple =
(WAGO_750_501_VENDOR_ID, WAGO_750_501_PRODUCT_ID);
@@ -0,0 +1,159 @@
use crate::devices::{
DynamicEthercatDevice, EthercatDevice, EthercatDeviceUsed, EthercatDynamicPDO, Module,
SubDeviceProductTuple,
};
use crate::devices::{EthercatDeviceProcessing, NewEthercatDevice};
#[derive(Clone)]
pub struct Wago750_652 {
is_used: bool,
tx_bit_offset: usize,
rx_bit_offset: usize,
module: Option<Module>,
}
impl EthercatDeviceUsed for Wago750_652 {
fn is_used(&self) -> bool {
self.is_used
}
fn set_used(&mut self, used: bool) {
self.is_used = used;
}
}
impl DynamicEthercatDevice for Wago750_652 {}
impl EthercatDynamicPDO for Wago750_652 {
fn get_tx_offset(&self) -> usize {
self.tx_bit_offset
}
fn get_rx_offset(&self) -> usize {
self.rx_bit_offset
}
fn set_tx_offset(&mut self, offset: usize) {
self.tx_bit_offset = offset
}
fn set_rx_offset(&mut self, offset: usize) {
self.rx_bit_offset = offset
}
}
impl EthercatDevice for Wago750_652 {
/*
OK so with ethercrab we receive the bitslice of the current subdevice in our Loop
*/
fn input(
&mut self,
_input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn input_len(&self) -> usize {
0
}
fn output(
&self,
_output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
Ok(())
}
fn output_len(&self) -> usize {
0
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn is_module(&self) -> bool {
true
}
fn input_checked(
&mut self,
input: &bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
// validate input has correct length
let expected = self.input_len();
let actual = input.len();
if actual != expected {
return Err(anyhow::anyhow!(
"[{}::Device::input_checked] Input length is {} ({} bytes) and must be {} bits ({} bytes)",
module_path!(),
actual,
actual / 8,
expected,
expected / 8
));
}
self.input(input)
}
fn output_checked(
&self,
output: &mut bitvec::prelude::BitSlice<u8, bitvec::prelude::Lsb0>,
) -> Result<(), anyhow::Error> {
self.output(output)?;
// validate input has correct length
let expected = self.output_len();
let actual = output.len();
if output.len() != expected {
return Err(anyhow::anyhow!(
"[{}::Device::output_checked] Output length is {} ({} bytes) and must be {} bits ({} bytes)",
module_path!(),
actual,
actual / 8,
expected,
expected / 8
));
}
Ok(())
}
fn get_module(&self) -> Option<Module> {
self.module.clone()
}
fn set_module(&mut self, module: Module) {
self.tx_bit_offset = module.tx_offset;
self.rx_bit_offset = module.rx_offset;
self.module = Some(module);
}
}
impl EthercatDeviceProcessing for Wago750_652 {}
impl NewEthercatDevice for Wago750_652 {
fn new() -> Self {
Self {
is_used: false,
tx_bit_offset: 0,
rx_bit_offset: 0,
module: None,
}
}
}
impl std::fmt::Debug for Wago750_652 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Wago750_652")
}
}
pub const WAGO_750_652_VENDOR_ID: u32 = 0x00000021;
pub const WAGO_750_652_PRODUCT_ID: u32 = 106043250;
pub const WAGO_750_652_MODULE_IDENT: SubDeviceProductTuple =
(WAGO_750_652_VENDOR_ID, WAGO_750_652_PRODUCT_ID);
-2
View File
@@ -59,14 +59,12 @@ use super::{
impl MachineNewTrait for ExtruderV2 {
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
// validate general stuff
use crate::{
MachineNewHardware, MachineNewHardwareEthercat, validate_no_role_dublicates,
validate_same_machine_identification_unique,
};
let device_identification = params.device_group.to_vec();
validate_same_machine_identification_unique(&device_identification)?;
validate_no_role_dublicates(&device_identification)?;
+2
View File
@@ -1,5 +1,6 @@
use std::fmt::Display;
use ethercat_hal::devices::wago_750_354::WAGO_750_354_IDENTITY_A;
use serde::Deserialize;
use serde::Serialize;
@@ -345,6 +346,7 @@ pub fn get_identification_addresses(
let identity_tuple = subdevice_identity_to_tuple(subdevice_identity);
Ok(match identity_tuple {
WAGO_750_354_IDENTITY_A => MachineIdentificationAddresses::default(),
EK1100_IDENTITY_A => MachineIdentificationAddresses::default(),
EL1002_IDENTITY_A => MachineIdentificationAddresses::default(),
EL1008_IDENTITY_A => MachineIdentificationAddresses::default(),
+1 -1
View File
@@ -71,7 +71,7 @@ impl MachineApi for TestMachine {
Mutation::SetAllLeds { on } => self.set_all_leds(on),
}
for (led, &on) in self.leds.iter().zip(self.led_on.iter()) {
for (led, &on) in self.douts.iter().zip(self.led_on.iter()) {
led.set(on);
}
+2 -2
View File
@@ -2,6 +2,7 @@ use crate::machine_identification::{MachineIdentification, MachineIdentification
use crate::test_machine::api::{StateEvent, TestMachineEvents};
use crate::{AsyncThreadMessage, Machine, MachineMessage};
use control_core::socketio::namespace::NamespaceCacheingLogic;
use ethercat_hal::io::digital_output::DigitalOutput;
use smol::channel::{Receiver, Sender};
use std::time::Instant;
pub mod act;
@@ -9,7 +10,6 @@ pub mod api;
pub mod new;
use crate::test_machine::api::TestMachineNamespace;
use crate::{TEST_MACHINE, VENDOR_QITECH};
use ethercat_hal::io::digital_output::DigitalOutput;
#[derive(Debug)]
pub struct TestMachine {
@@ -20,7 +20,7 @@ pub struct TestMachine {
pub last_state_emit: Instant,
pub led_on: [bool; 4],
pub main_sender: Option<Sender<AsyncThreadMessage>>,
pub leds: [DigitalOutput; 4],
pub douts: [DigitalOutput; 4],
}
impl Machine for TestMachine {
+40 -1
View File
@@ -7,10 +7,19 @@ use crate::{
MachineNewHardware, MachineNewParams, MachineNewTrait, get_ethercat_device,
validate_no_role_dublicates, validate_same_machine_identification_unique,
};
use anyhow::Error;
use ethercat_hal::devices::el2004::{EL2004, EL2004_IDENTITY_A, EL2004Port};
use ethercat_hal::io::digital_output::DigitalOutput;
//Imports For Wago
/*
use ethercat_hal::devices::wago_750_354::{WAGO_750_354_IDENTITY_A, Wago750_354};
use ethercat_hal::devices::{EthercatDevice, downcast_device};
use smol::lock::RwLock;
use std::sync::Arc;
*/
impl MachineNewTrait for TestMachine {
fn new<'maindevice>(params: &MachineNewParams) -> Result<Self, Error> {
// validate general stuff
@@ -32,6 +41,35 @@ impl MachineNewTrait for TestMachine {
}
};
block_on(async {
/*
// Example usage of a Wago Coupler and a 750-1506 in the first slot, where the Output Port 1,2,3,4 is used
let _wago_750_354 = get_ethercat_device::<Wago750_354>(
hardware,
params,
0,
[WAGO_750_354_IDENTITY_A].to_vec(),
)
.await?;
let modules = Wago750_354::initialize_modules(_wago_750_354.1).await?;
let mut coupler = _wago_750_354.0.write().await;
for module in modules {
coupler.set_module(module);
}
coupler.init_slot_modules(_wago_750_354.1);
let dev = coupler.slot_devices.get(1).unwrap().clone().unwrap();
let wago750_1506: Arc<RwLock<Wago750_1506>> =
downcast_device::<Wago750_1506>(dev).await?;
let do1 = DigitalOutput::new(wago750_1506.clone(), Wago750_1506OutputPort::DO1);
let do2 = DigitalOutput::new(wago750_1506.clone(), Wago750_1506OutputPort::DO2);
let do3 = DigitalOutput::new(wago750_1506.clone(), Wago750_1506OutputPort::DO3);
let do4 = DigitalOutput::new(wago750_1506.clone(), Wago750_1506OutputPort::DO4);
drop(coupler);
*/
let el2004 =
get_ethercat_device::<EL2004>(hardware, params, 1, [EL2004_IDENTITY_A].to_vec())
.await?
@@ -40,6 +78,7 @@ impl MachineNewTrait for TestMachine {
let do2 = DigitalOutput::new(el2004.clone(), EL2004Port::DO2);
let do3 = DigitalOutput::new(el2004.clone(), EL2004Port::DO3);
let do4 = DigitalOutput::new(el2004.clone(), EL2004Port::DO4);
let (sender, receiver) = smol::channel::unbounded();
let mut my_test = Self {
api_receiver: receiver,
@@ -51,7 +90,7 @@ impl MachineNewTrait for TestMachine {
last_state_emit: Instant::now(),
led_on: [false; 4],
main_sender: params.main_thread_channel.clone(),
leds: [do1, do2, do3, do4],
douts: [do1, do2, do3, do4],
};
my_test.emit_state();
Ok(my_test)
+45 -16
View File
@@ -12,6 +12,10 @@ use control_core::socketio::namespace::NamespaceCacheingLogic;
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::wago_750_354::{
WAGO_750_354_PRODUCT_ID, WAGO_750_354_VENDOR_ID, Wago750_354,
};
use ethercrab::std::ethercat_now;
use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, RetryBehaviour, Timeouts};
use machines::machine_identification::{
@@ -110,10 +114,6 @@ pub async fn set_ethercat_devices<const MAX_SUBDEVICES: usize, const MAX_PDI: us
socket_queue_tx: Sender<(SocketRef, Arc<control_core::socketio::event::GenericEvent>)>,
) -> Result<(), anyhow::Error> {
let device_grouping_result = group_devices_by_identification(device_identifications);
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![];
@@ -300,7 +300,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<_>,
@@ -328,32 +328,57 @@ pub async fn setup_loop(
acc.0.push(identified_device_identification.clone());
acc.1.push(identified_device);
acc.2.push(identified_subdevice);
ethercat_meta_devices.push(EtherCatDeviceMetaData::from_subdevice(identified_subdevice,identified_device_identification));
let meta = EtherCatDeviceMetaData::from_subdevice(identified_subdevice,identified_device_identification);
ethercat_meta_devices.push(meta);
acc
},
);
tracing::info!("Found Devices: {:?}", ethercat_meta_devices);
drop(ethercat_meta_devices);
// We always need to have atleast one subdevice anyways
let coupler = subdevices.get(0).unwrap();
let _resp = get_most_recent_diagnosis_message(coupler).await;
/*
handle special edge cases,
Wago couplers handle all subdevices as part of the coupler instead of their own devices,
meaning we need to convert the PDO Mappings to seperate SubDevices to enable more ease of use
OR alternatively you could show it like TwinCAT with "slots" on the Coupler
*/
match (coupler.identity().vendor_id,coupler.identity().product_id) {
(WAGO_750_354_VENDOR_ID,WAGO_750_354_PRODUCT_ID) => {
match (coupler.identity().vendor_id, coupler.identity().product_id) {
(WAGO_750_354_VENDOR_ID, WAGO_750_354_PRODUCT_ID) => {
let r = Wago750_354::initialize_modules(coupler).await?;
println!("{:?}",r);
},
for module in r {
if coupler.configured_address() == module.belongs_to_addr {
match ethercat_meta_devices.get(0) {
Some(meta) => {
let meta_data = EtherCatDeviceMetaData {
configured_address: module.slot,
name: "wago module".to_owned(),
vendor_id:module.vendor_id,
product_id: module.product_id,
revision: 0x2,
device_identification: DeviceIdentification{
device_machine_identification: meta.device_identification.device_machine_identification.clone(),
device_hardware_identification: machines::machine_identification::DeviceHardwareIdentification::Ethercat(DeviceHardwareIdentificationEthercat{ subdevice_index: module.slot as usize }) }
};
ethercat_meta_devices.push(meta_data);
}
None => break,
}
}
}
}
_ => (),
};
drop(ethercat_meta_devices);
for subdevice in subdevices.iter() {
// Hack so El5152 goes into OP
if subdevice.name() == "EL5152" {
subdevice.sdo_write(SM_INPUT, 0x1, 0x00u16).await?; //set sync mode (1) for free run (0)
subdevice.sdo_write(SM_OUTPUT, 0x1, 0x00u16).await?; //set sync mode (1) for free run (0)
}
}
// remove subdevice from devices tuple
let devices = devices
@@ -388,11 +413,16 @@ pub async fn setup_loop(
/*
Make DC Slaves Happy
Does this potentially cause issues with Non DC-Sync devices?
*/
let res = group_safe.tx_rx_sync_system_time(&maindevice).await;
match res {
Ok(_) => (),
Err(e) => tracing::error!("[{}::setup_loop] Failed to sync dc time: {:?}",e, module_path!()),
Err(e) => tracing::error!(
"[{}::setup_loop] Failed to sync dc time: {:?}",
e,
module_path!()
),
}
// Put group in operational state
@@ -421,7 +451,6 @@ pub async fn setup_loop(
.await;
main_namespace.emit(MainNamespaceEvents::EthercatDevicesEvent(event));
}
tracing::info!("DONE WITH INIT");
return Ok(EthercatSetup {
devices,
+1
View File
@@ -68,6 +68,7 @@ pub fn start_loop_thread(
match msg {
HotThreadMessage::NoMsg => {}
HotThreadMessage::AddEtherCatSetup(ethercat_setup) => {
println!("EthercatSetup: {:?}", ethercat_setup.devices);
rt_loop_inputs.ethercat_setup = Some(Box::new(ethercat_setup));
}
HotThreadMessage::WriteMachineDeviceInfo(info_request) => {