commit 767bbb1c560666bd7c70480acdad5f76b0dc8929 Author: Rahix Date: Fri Nov 15 01:10:37 2024 +0100 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..ea8c4bf7f35f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000000..a9f1f9b47072 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,316 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "profirust" +version = "0.2.0" +dependencies = [ + "bitflags 2.6.0", + "bitvec", + "libc", + "log", + "managed", + "rs485", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rs485" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7491424ed2e9f9fa71ce0f6ec1df2aa6780297047803c4c468e5cb7a89ae835a" +dependencies = [ + "bitflags 0.9.1", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valve-tester" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "profirust", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..0bf2b8133501 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "valve-tester" +version = "0.1.0" +edition = "2021" + +[dependencies] +env_logger = "0.11.5" +log = "0.4.22" +profirust = { version = "0.2.0", path = "../profirust", default-features = false, features = ["phy-linux", "std"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 000000000000..b065704239c1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,88 @@ +use profirust::dp; +use profirust::fdl; +use profirust::phy; + +mod tester_io; +mod valve_terminal; + +// Bus Parameters +const MASTER_ADDRESS: u8 = 1; +const BUS_DEVICE: &'static str = "/dev/ttyUSB0"; +const BAUDRATE: profirust::Baudrate = profirust::Baudrate::B500000; + +fn main() -> ! { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_timestamp_micros() + .init(); + + log::info!("Ventilinsel Teststation"); + + let mut dp_scanner = dp::scan::DpScanner::new(); + + let mut dp_master = dp::DpMaster::new(vec![]); + let mut tester_io = tester_io::TesterIo::new(&mut dp_master, BAUDRATE); + let mut valve_terminal = valve_terminal::ValveTerminal::new(&mut dp_master, BAUDRATE); + + let mut fdl = fdl::FdlActiveStation::new( + fdl::ParametersBuilder::new(MASTER_ADDRESS, BAUDRATE) + // We use a rather large T_slot time because USB-RS485 converters + // can induce large delays at times. + .slot_bits(2500) + // .token_rotation_bits(512) + .build_verified(&dp_master), + ); + // We must not poll() too often or to little. T_slot / 2 seems to be a good compromise. + let sleep_time: std::time::Duration = (fdl.parameters().slot_time() / 2).into(); + + log::info!("Connecting to the bus..."); + let mut phy = phy::LinuxRs485Phy::new(BUS_DEVICE, fdl.parameters().baudrate); + + fdl.set_online(); + dp_master.enter_operate(); + loop { + fdl.poll_multi( + profirust::time::Instant::now(), + &mut phy, + &mut [&mut dp_master, &mut dp_scanner], + ); + + let dp_events = dp_master.take_last_events(); + tester_io.update(&mut dp_master, &dp_events); + valve_terminal.update(&mut dp_master, &dp_events); + + // Ventil Zustände von den Eingängen auf die Ventilausgänge übertragen. + valve_terminal.update_valve_states(tester_io.valve_states()); + + let scanner_event = dp_scanner.take_last_event(); + match scanner_event { + Some(dp::scan::DpScanEvent::PeripheralFound(desc)) => { + if valve_terminal.check_ident(desc.ident) { + // Wenn die Ventilinsel nicht erreichbar ist, wird eine neue Adresse vom "DP Scanner" + // übernommen. + if !valve_terminal.is_running(&mut dp_master) { + log::info!( + "Ventilinsel an Adresse #{} gefunden. Parametrieren...", + desc.address + ); + valve_terminal.init_at_address(desc.address, &mut dp_master); + } else { + // Alte Ventilinsel ist noch aktiv?? + log::warn!( + "Neue Ventilinsel an Adresse #{} gefunden, obwohl alte noch aktiv. Wird ignoriert.", + desc.address + ); + } + } else { + log::info!("Andere Station an Adresse #{} gefunden:", desc.address); + log::info!(" - Ident: 0x{:04x}", desc.ident); + } + } + Some(dp::scan::DpScanEvent::PeripheralLost(address)) => { + log::info!("Station an Adresse #{} verloren.", address); + } + _ => (), + } + + std::thread::sleep(sleep_time); + } +} diff --git a/src/tester_io.rs b/src/tester_io.rs new file mode 100644 index 000000000000..aa58d71d8e99 --- /dev/null +++ b/src/tester_io.rs @@ -0,0 +1,106 @@ +//! E/A für die Teststation +//! +//! Als E/A ist hier eine Siemens ET200B 8DI 8DO vorgesehen. Natürlich lässt sich dies auf jede +//! andere E/A Station umbauen. Folgende Geräte sind angeschlossen: +//! +//! ## Eingänge +//! - 8x Kippschalter zum Schalten der einzelnen Ventile +//! +//! ## Ausgänge +//! - Leuchtmelder "Test-Station bereit" +//! - Leuchtmelder "Ventilinsel erkannt" +//! - Leuchtmelder "Fehler" + +use profirust::dp; + +const ET200B_ADDRESS: profirust::Address = 13; + +pub enum TesterStatus { + /// Test-Station bereit, keine Ventilinsel verbunden + Ready, + /// Ventilinsel erkannt und verbunden + ValveTerminalConnected, + /// Fehler + Error, +} + +pub struct TesterIo { + peripheral: dp::PeripheralHandle, + valve_states: u8, + tester_status: TesterStatus, +} + +impl TesterIo { + pub fn new(dp_master: &mut dp::DpMaster, baudrate: profirust::Baudrate) -> Self { + // Options generated by `gsdtool` using "et200b-8di8do.gsd" + let options = profirust::dp::PeripheralOptions { + // "B-8DI/8DO DP " by "SIEMENS " + ident_number: 0x000b, + + // Global Parameters: + // (none) + // + // Selected Modules: + // [0] 1 Byte Out, 1 Byte In + user_parameters: Some(&[0x00, 0x00, 0x00, 0x00, 0x00]), + config: Some(&[0x20, 0x10]), + + // Set max_tsdr depending on baudrate and assert + // that a supported baudrate is used. + max_tsdr: match baudrate { + profirust::Baudrate::B9600 => 60, + profirust::Baudrate::B19200 => 60, + profirust::Baudrate::B45450 => 250, + profirust::Baudrate::B93750 => 60, + profirust::Baudrate::B187500 => 60, + profirust::Baudrate::B500000 => 100, + profirust::Baudrate::B1500000 => 150, + profirust::Baudrate::B3000000 => 250, + profirust::Baudrate::B6000000 => 450, + profirust::Baudrate::B12000000 => 800, + b => panic!( + "Peripheral \"B-8DI/8DO DP \" does not support baudrate {b:?}!" + ), + }, + + fail_safe: false, + ..Default::default() + }; + let buffer_inputs = vec![0u8; 1].leak(); + let buffer_outputs = vec![0u8; 1].leak(); + let buffer_diagnostics = vec![0u8; 13].leak(); + let handle = dp_master.add( + dp::Peripheral::new(ET200B_ADDRESS, options, buffer_inputs, buffer_outputs) + .with_diag_buffer(buffer_diagnostics), + ); + + Self { + peripheral: handle, + valve_states: 0, + tester_status: TesterStatus::Ready, + } + } + + pub fn valve_states(&self) -> u8 { + self.valve_states + } + + pub fn update(&mut self, dp_master: &mut dp::DpMaster, events: &dp::DpEvents) { + let peripheral = dp_master.get_mut(self.peripheral); + if peripheral.is_running() && events.cycle_completed { + // Die 8 Eingänge der ET200B werden auf die 8 Bits für die Ventile gemapped. + self.valve_states = peripheral.pi_i()[0]; + + // Ausgänge: + // - Bit 0: Leuchtmelder "Test-Station bereit" + // - Bit 1: Leuchtmelder "Ventilinsel erkannt" + // - Bit 2: Leuchtmelder "Fehler" + let outputs = match self.tester_status { + TesterStatus::Ready => 0x01, + TesterStatus::ValveTerminalConnected => 0x02, + TesterStatus::Error => 0x04, + }; + peripheral.pi_q_mut()[0] = outputs; + } + } +} diff --git a/src/valve_terminal.rs b/src/valve_terminal.rs new file mode 100644 index 000000000000..b1b90db5a7ec --- /dev/null +++ b/src/valve_terminal.rs @@ -0,0 +1,106 @@ +//! Steuerung für die Ventilinseln +//! +//! Verbindet sich dynamisch mit einer Ventilinsel, die am Bus unter beliebiger Adresse gefunden +//! wurde. Es wird eine bekannte Parametrierung vorausgesetzt. +//! +//! Zur demo ist hier eine CPV14 mit CPV14-GE-DI02-8 Anschaltung vorgesehen. + +use profirust::dp; + +// "Festo CPV-DI02" by "Festo AG&Co." +const VALVE_TERMINAL_IDENT_NUMBER: u16 = 0x0a35; + +pub struct ValveTerminal { + peripheral: dp::PeripheralHandle, + address: profirust::Address, + valve_states: u8, +} + +impl ValveTerminal { + pub fn new(dp_master: &mut dp::DpMaster, baudrate: profirust::Baudrate) -> Self { + // Als erstes wird die Ventilinsel mit einer Standard-Adresse parametriert. Die Adresse + // wird dann später geändert, sobald ein tatsächliches Gerät auf dem Bus gefunden wurde. + let default_address = 42; + + // Options generated by `gsdtool` using "cpv_0A35.gse" + let options = profirust::dp::PeripheralOptions { + // "Festo CPV-DI02" by "Festo AG&Co." + ident_number: 0x0a35, + + // Global Parameters: + // (none) + // + // Selected Modules: + // [0] Base module:16DO + // - monitor U-Load.........: active + // - monitor failure CP-Line: inactive + // - fault mode.............: reset outputs + // - fault state 2 byte.....: 0 + user_parameters: Some(&[0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00]), + config: Some(&[0x21]), + + // Set max_tsdr depending on baudrate and assert + // that a supported baudrate is used. + max_tsdr: match baudrate { + profirust::Baudrate::B9600 => 20, + profirust::Baudrate::B19200 => 20, + profirust::Baudrate::B93750 => 20, + profirust::Baudrate::B187500 => 20, + profirust::Baudrate::B500000 => 20, + profirust::Baudrate::B1500000 => 25, + profirust::Baudrate::B3000000 => 50, + profirust::Baudrate::B6000000 => 100, + profirust::Baudrate::B12000000 => 200, + b => panic!("Peripheral \"Festo CPV-DI02\" does not support baudrate {b:?}!"), + }, + + fail_safe: false, + ..Default::default() + }; + let mut buffer_inputs = vec![0u8; 0].leak(); + let mut buffer_outputs = vec![0u8; 2].leak(); + let mut buffer_diagnostics = vec![0u8; 16].leak(); + let handle = dp_master.add( + dp::Peripheral::new(default_address, options, buffer_inputs, buffer_outputs) + .with_diag_buffer(buffer_diagnostics), + ); + + Self { + peripheral: handle, + address: default_address, + valve_states: 0, + } + } + + /// Überprüfen ob ein gefundenes Gerät eine Ventilinsel von unserem Typ ist + pub fn check_ident(&self, ident: u16) -> bool { + ident == VALVE_TERMINAL_IDENT_NUMBER + } + + /// Gibt `true` zurück wenn die Ventilinsel an der aktuell parametrierten Adresse erfolgreich + /// kommuniziert. + pub fn is_running(&self, dp_master: &mut dp::DpMaster) -> bool { + let peripheral = dp_master.get_mut(self.peripheral); + peripheral.is_running() + } + + pub fn init_at_address(&mut self, address: profirust::Address, dp_master: &mut dp::DpMaster) { + if address != self.address { + let peripheral = dp_master.get_mut(self.peripheral); + peripheral.reset_address(address); + peripheral.pi_q_mut().fill(0u8); + self.valve_states = 0; + } + } + + pub fn update_valve_states(&mut self, valve_states: u8) { + self.valve_states = valve_states; + } + + pub fn update(&mut self, dp_master: &mut dp::DpMaster, events: &dp::DpEvents) { + let peripheral = dp_master.get_mut(self.peripheral); + if peripheral.is_running() && events.cycle_completed { + peripheral.pi_q_mut()[0] = self.valve_states; + } + } +}