1#[derive(Clone, Copy, Debug, Default)]
18pub struct RawControlWord(pub u16);
19
20#[derive(Clone, Copy, Debug, Default)]
22pub struct RawStatusWord(pub u16);
23
24pub trait Cia402Control {
34 fn raw(&self) -> u16;
36 fn raw_mut(&mut self) -> &mut u16;
38
39 fn set_switch_on(&mut self, v: bool) {
43 self.set_bit(0, v);
44 }
45 fn set_enable_voltage(&mut self, v: bool) {
47 self.set_bit(1, v);
48 }
49 fn set_quick_stop(&mut self, v: bool) {
51 self.set_bit(2, v);
52 }
53 fn set_enable_operation(&mut self, v: bool) {
55 self.set_bit(3, v);
56 }
57 fn set_fault_reset(&mut self, v: bool) {
59 self.set_bit(7, v);
60 }
61
62 fn cmd_shutdown(&mut self) {
72 let w = self.raw_mut();
73 *w = (*w & !0x008F) | 0x0006; }
75
76 fn cmd_switch_on(&mut self) {
79 let w = self.raw_mut();
80 *w = (*w & !0x008F) | 0x0007; }
82
83 fn cmd_enable_operation(&mut self) {
86 let w = self.raw_mut();
87 *w = (*w & !0x008F) | 0x000F; }
89
90 fn cmd_disable_operation(&mut self) {
93 let w = self.raw_mut();
94 *w = (*w & !0x008F) | 0x0007; }
96
97 fn cmd_disable_voltage(&mut self) {
100 let w = self.raw_mut();
101 *w &= !0x0082; }
103
104 fn cmd_quick_stop(&mut self) {
107 let w = self.raw_mut();
108 *w = (*w & !0x0086) | 0x0002; }
110
111 fn cmd_fault_reset(&mut self) {
114 let w = self.raw_mut();
115 *w |= 0x0080; }
117
118
119 fn cmd_clear_fault_reset(&mut self) {
121 self.set_bit(7, false);
122 }
123
124 fn set_bit(&mut self, bit: u8, v: bool) {
126 let w = self.raw_mut();
127 if v {
128 *w |= 1 << bit;
129 } else {
130 *w &= !(1 << bit);
131 }
132 }
133}
134
135pub trait Cia402Status {
143 fn raw(&self) -> u16;
145
146 fn ready_to_switch_on(&self) -> bool {
148 self.raw() & (1 << 0) != 0
149 }
150 fn switched_on(&self) -> bool {
152 self.raw() & (1 << 1) != 0
153 }
154 fn operation_enabled(&self) -> bool {
156 self.raw() & (1 << 2) != 0
157 }
158 fn fault(&self) -> bool {
160 self.raw() & (1 << 3) != 0
161 }
162 fn voltage_enabled(&self) -> bool {
164 self.raw() & (1 << 4) != 0
165 }
166 fn quick_stop_active(&self) -> bool {
168 self.raw() & (1 << 5) != 0
169 }
170 fn switch_on_disabled(&self) -> bool {
172 self.raw() & (1 << 6) != 0
173 }
174 fn warning(&self) -> bool {
176 self.raw() & (1 << 7) != 0
177 }
178 fn remote(&self) -> bool {
180 self.raw() & (1 << 9) != 0
181 }
182 fn target_reached(&self) -> bool {
184 self.raw() & (1 << 10) != 0
185 }
186
187 fn state(&self) -> Cia402State {
194 let w = self.raw();
195 if w & 0x006F == 0x0027 { return Cia402State::OperationEnabled; }
197 if w & 0x006F == 0x0023 { return Cia402State::SwitchedOn; }
198 if w & 0x006F == 0x0021 { return Cia402State::ReadyToSwitchOn; }
199 if w & 0x006F == 0x0007 { return Cia402State::QuickStopActive; }
200 if w & 0x004F == 0x000F { return Cia402State::FaultReactionActive; }
202 if w & 0x004F == 0x0008 { return Cia402State::Fault; }
203 if w & 0x004F == 0x0040 { return Cia402State::SwitchOnDisabled; }
204 if w & 0x004F == 0x0000 { return Cia402State::NotReadyToSwitchOn; }
205 Cia402State::Unknown
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum Cia402State {
212 NotReadyToSwitchOn,
214 SwitchOnDisabled,
216 ReadyToSwitchOn,
218 SwitchedOn,
220 OperationEnabled,
222 QuickStopActive,
224 FaultReactionActive,
226 Fault,
228 Unknown,
230}
231
232impl std::fmt::Display for Cia402State {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 match self {
235 Self::NotReadyToSwitchOn => write!(f, "Not Ready to Switch On"),
236 Self::SwitchOnDisabled => write!(f, "Switch On Disabled"),
237 Self::ReadyToSwitchOn => write!(f, "Ready to Switch On"),
238 Self::SwitchedOn => write!(f, "Switched On"),
239 Self::OperationEnabled => write!(f, "Operation Enabled"),
240 Self::QuickStopActive => write!(f, "Quick Stop Active"),
241 Self::FaultReactionActive => write!(f, "Fault Reaction Active"),
242 Self::Fault => write!(f, "Fault"),
243 Self::Unknown => write!(f, "Unknown"),
244 }
245 }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254#[repr(i8)]
255pub enum ModesOfOperation {
256 ProfilePosition = 1,
258 ProfileVelocity = 3,
260 Homing = 6,
262 InterpolatedPosition = 7,
264 CyclicSynchronousPosition = 8,
266 CyclicSynchronousVelocity = 9,
268 CyclicSynchronousTorque = 10,
270}
271
272impl ModesOfOperation {
273 pub fn from_i8(v: i8) -> Option<Self> {
275 match v {
276 1 => Some(Self::ProfilePosition),
277 3 => Some(Self::ProfileVelocity),
278 6 => Some(Self::Homing),
279 7 => Some(Self::InterpolatedPosition),
280 8 => Some(Self::CyclicSynchronousPosition),
281 9 => Some(Self::CyclicSynchronousVelocity),
282 10 => Some(Self::CyclicSynchronousTorque),
283 _ => None,
284 }
285 }
286
287 pub fn as_i8(self) -> i8 {
289 self as i8
290 }
291}
292
293impl std::fmt::Display for ModesOfOperation {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 match self {
296 Self::ProfilePosition => write!(f, "Profile Position (PP)"),
297 Self::ProfileVelocity => write!(f, "Profile Velocity (PV)"),
298 Self::Homing => write!(f, "Homing"),
299 Self::InterpolatedPosition => write!(f, "Interpolated Position (IP)"),
300 Self::CyclicSynchronousPosition => write!(f, "Cyclic Synchronous Position (CSP)"),
301 Self::CyclicSynchronousVelocity => write!(f, "Cyclic Synchronous Velocity (CSV)"),
302 Self::CyclicSynchronousTorque => write!(f, "Cyclic Synchronous Torque (CST)"),
303 }
304 }
305}
306
307impl Cia402Control for RawControlWord {
309 fn raw(&self) -> u16 {
310 self.0
311 }
312 fn raw_mut(&mut self) -> &mut u16 {
313 &mut self.0
314 }
315}
316impl Cia402Status for RawStatusWord {
317 fn raw(&self) -> u16 {
318 self.0
319 }
320}
321
322pub trait PpControl: Cia402Control {
328 fn set_new_set_point(&mut self, v: bool) {
330 self.set_bit(4, v);
331 }
332 fn set_change_set_immediately(&mut self, v: bool) {
334 self.set_bit(5, v);
335 }
336 fn set_relative(&mut self, v: bool) {
338 self.set_bit(6, v);
339 }
340 fn set_halt(&mut self, v: bool) {
342 self.set_bit(8, v);
343 }
344}
345
346pub trait PpStatus: Cia402Status {
348 fn pp_target_reached(&self) -> bool {
350 self.raw() & (1 << 10) != 0
351 }
352 fn internal_limit(&self) -> bool {
354 self.raw() & (1 << 11) != 0
355 }
356 fn set_point_acknowledge(&self) -> bool {
358 self.raw() & (1 << 12) != 0
359 }
360 fn following_error(&self) -> bool {
362 self.raw() & (1 << 13) != 0
363 }
364}
365
366pub trait PvControl: Cia402Control {
372 fn set_halt(&mut self, v: bool) {
374 self.set_bit(8, v);
375 }
376}
377
378pub trait PvStatus: Cia402Status {
380 fn pv_target_reached(&self) -> bool {
382 self.raw() & (1 << 10) != 0
383 }
384 fn pv_internal_limit(&self) -> bool {
386 self.raw() & (1 << 11) != 0
387 }
388 fn speed_is_zero(&self) -> bool {
390 self.raw() & (1 << 12) != 0
391 }
392 fn max_slippage_error(&self) -> bool {
394 self.raw() & (1 << 13) != 0
395 }
396}
397
398pub trait HomingControl: Cia402Control {
404 fn set_homing_start(&mut self, v: bool) {
406 self.set_bit(4, v);
407 }
408 fn set_halt(&mut self, v: bool) {
410 self.set_bit(8, v);
411 }
412}
413
414pub trait HomingStatus: Cia402Status {
416 fn homing_target_reached(&self) -> bool {
418 self.raw() & (1 << 10) != 0
419 }
420 fn homing_attained(&self) -> bool {
422 self.raw() & (1 << 12) != 0
423 }
424 fn homing_error(&self) -> bool {
426 self.raw() & (1 << 13) != 0
427 }
428}
429
430impl PpControl for RawControlWord {}
432impl PpStatus for RawStatusWord {}
433impl PvControl for RawControlWord {}
434impl PvStatus for RawStatusWord {}
435impl HomingControl for RawControlWord {}
436impl HomingStatus for RawStatusWord {}
437
438#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn test_state_decoding() {
448 assert_eq!(RawStatusWord(0x0000).state(), Cia402State::NotReadyToSwitchOn);
450 assert_eq!(RawStatusWord(0x0040).state(), Cia402State::SwitchOnDisabled);
451 assert_eq!(RawStatusWord(0x0021).state(), Cia402State::ReadyToSwitchOn);
452 assert_eq!(RawStatusWord(0x0023).state(), Cia402State::SwitchedOn);
453 assert_eq!(RawStatusWord(0x0027).state(), Cia402State::OperationEnabled);
454 assert_eq!(RawStatusWord(0x0007).state(), Cia402State::QuickStopActive);
455 assert_eq!(RawStatusWord(0x000F).state(), Cia402State::FaultReactionActive);
456 assert_eq!(RawStatusWord(0x0008).state(), Cia402State::Fault);
457 }
458
459 #[test]
460 fn test_state_decoding_bit5_dont_care() {
461 assert_eq!(RawStatusWord(0x0020).state(), Cia402State::NotReadyToSwitchOn);
466 assert_eq!(RawStatusWord(0x0060).state(), Cia402State::SwitchOnDisabled);
468 assert_eq!(RawStatusWord(0x002F).state(), Cia402State::FaultReactionActive);
470 assert_eq!(RawStatusWord(0x0028).state(), Cia402State::Fault);
472 }
473
474 #[test]
475 fn test_state_decoding_ignores_high_bits() {
476 assert_eq!(RawStatusWord(0xFF27).state(), Cia402State::OperationEnabled);
478 assert_eq!(RawStatusWord(0x8040).state(), Cia402State::SwitchOnDisabled);
479 }
480
481 #[test]
482 fn test_cmd_shutdown() {
483 let mut cw = RawControlWord(0xFF00);
484 cw.cmd_shutdown();
485 assert_eq!(cw.0, 0xFF06);
487 }
488
489 #[test]
490 fn test_cmd_enable_operation() {
491 let mut cw = RawControlWord(0x0000);
492 cw.cmd_enable_operation();
493 assert_eq!(cw.0, 0x000F);
494 }
495
496 #[test]
497 fn test_cmd_fault_reset() {
498 let mut cw = RawControlWord(0x0000);
499 cw.cmd_fault_reset();
500 assert!(cw.0 & 0x0080 != 0); }
502
503 #[test]
504 fn test_modes_of_operation_roundtrip() {
505 for mode in [
506 ModesOfOperation::ProfilePosition,
507 ModesOfOperation::ProfileVelocity,
508 ModesOfOperation::Homing,
509 ] {
510 assert_eq!(ModesOfOperation::from_i8(mode.as_i8()), Some(mode));
511 }
512 assert_eq!(ModesOfOperation::from_i8(99), None);
513 }
514}