linuxcnc_hal/
component.rs

1use crate::{error::ComponentInitError, RegisterResources, Resources};
2use linuxcnc_hal_sys::{hal_exit, hal_init, hal_ready, EINVAL, ENOMEM, HAL_NAME_LEN};
3use signal_hook::iterator::Signals;
4use std::{cell::RefCell, ffi::CString};
5
6/// HAL component
7///
8/// The main HAL component interface. See the [crate documentation](./index.html) for examples.
9///
10/// During registraton, all resource names are prefixed with the component name and a `.` full stop
11/// character. For example, a component named `rust-comp` with a pin named `input-1` will show up in
12/// LinuxCNC as a pin called `rust-comp.input-1`.
13///
14/// `HalComponent` has a custom `Drop` implementation which calls [`hal_exit`] (among other things)
15/// when the variable holding the component goes out of scope. Due to this, the component should be
16/// initialised in `main()` so it lives for the entire life of the program.
17#[derive(Debug)]
18pub struct HalComponent<R> {
19    /// Component name
20    name: &'static str,
21
22    /// Component ID
23    id: i32,
24
25    /// Handles to Unix exit signals
26    signals: RefCell<Signals>,
27
28    /// Handle to resources (pins, signals, etc) used in the component
29    ///
30    /// This is an `Option` so that it can be `Drop`ped before the component itself is dropped.
31    /// Resources references to shared memory in LinuxCNC's HAL must be freed before [`hal_exit`] is
32    /// called.
33    resources: Option<R>,
34}
35
36impl<R> HalComponent<R>
37where
38    R: Resources,
39{
40    /// Create a new HAL component
41    ///
42    /// `new` registers a new HAL component with LinuxCNC, registers the required UNIX signal
43    /// handlers and allocates resources (pins, signals, etc) required by the component.
44    pub fn new(name: &'static str) -> Result<Self, ComponentInitError> {
45        let id = Self::create_component(name)?;
46
47        let resources = R::register_resources(&RegisterResources { id, name })
48            .map_err(|e| ComponentInitError::ResourceRegistration(e.into()))?;
49
50        let signals = Self::register_signals()?;
51
52        let comp = Self {
53            name,
54            id,
55            resources: Some(resources),
56            signals: RefCell::new(signals),
57        };
58
59        comp.ready()
60    }
61
62    /// Register signal handlers so component closes cleanly
63    ///
64    /// These are also required for the component to pass initialisation in LinuxCNC. If LinuxCNC
65    /// hangs during starting waiting for the component to become ready, it might be due to signal
66    /// handlers not being registered.
67    fn register_signals() -> Result<Signals, ComponentInitError> {
68        let signals = Signals::new(&[signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT])
69            .map_err(ComponentInitError::Signals)?;
70
71        debug!("Signals registered");
72
73        Ok(signals)
74    }
75
76    /// Create a HAL component
77    ///
78    /// # Errors
79    ///
80    /// * [`ComponentInitError::NameLength`] - If the component name is longer than [`HAL_NAME_LEN`]
81    /// * [`ComponentInitError::InvalidName`] - If the component name cannot be converted to a
82    ///   [`std::ffi::CString`]
83    /// * [`ComponentInitError::Init`] - If the call to [`hal_init`] returned an [`EINVAL`] status
84    /// * [`ComponentInitError::Memory`] - If there is not enough memory to allocate the component
85    fn create_component(name: &'static str) -> Result<i32, ComponentInitError> {
86        if name.len() > HAL_NAME_LEN as usize {
87            error!(
88                "Component name must be no longer than {} bytes",
89                HAL_NAME_LEN
90            );
91
92            Err(ComponentInitError::NameLength)
93        } else {
94            let name_c = CString::new(name).map_err(|_| ComponentInitError::InvalidName)?;
95
96            let id = unsafe { hal_init(name_c.as_ptr().cast()) };
97
98            match id {
99                x if x == -(EINVAL as i32) => Err(ComponentInitError::Init),
100                x if x == -(ENOMEM as i32) => Err(ComponentInitError::Memory),
101                id if id > 0 => {
102                    debug!("Init component {} with ID {}", name, id);
103
104                    Ok(id)
105                }
106                code => unreachable!("Hit unreachable error code {}", code),
107            }
108        }
109    }
110
111    /// Signal to the HAL that the component is ready
112    fn ready(self) -> Result<Self, ComponentInitError> {
113        let ret = unsafe { hal_ready(self.id) };
114
115        match ret {
116            x if x == -(EINVAL as i32) => Err(ComponentInitError::Ready),
117            0 => {
118                debug!("Component is ready");
119
120                Ok(self)
121            }
122            ret => unreachable!("Unknown error status {} returned from hal_ready()", ret),
123        }
124    }
125
126    /// Get the HAL-assigned ID for this component
127    pub fn id(&self) -> i32 {
128        self.id
129    }
130
131    /// Get the component name
132    pub fn name(&self) -> &str {
133        self.name
134    }
135
136    /// Check whether the component was signalled to shut down
137    pub fn should_exit(&self) -> bool {
138        self.signals.borrow_mut().pending().any(|signal| {
139            matches!(
140                signal,
141                signal_hook::consts::SIGTERM
142                    | signal_hook::consts::SIGINT
143                    | signal_hook::consts::SIGKILL
144            )
145        })
146    }
147
148    /// Get a reference to the component's resources
149    pub fn resources(&self) -> &R {
150        // NOTE: Unwrap is safe here due to `Some(resources)` in HalComponent::new
151        &self.resources.as_ref().unwrap()
152    }
153}
154
155impl<R> Drop for HalComponent<R> {
156    /// Clean up resources, signals and HAL component
157    fn drop(&mut self) {
158        // Force resources to be dropped before close
159        self.resources = None;
160
161        debug!("Closing component ID {}, name {}", self.id, self.name);
162
163        // self.signals.close();
164
165        unsafe {
166            hal_exit(self.id);
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use crate::{
175        error::{ComponentInitError, PinRegisterError},
176        RegisterResources,
177    };
178
179    #[derive(Debug)]
180    struct EmptyResources {}
181    impl Resources for EmptyResources {
182        type RegisterError = PinRegisterError;
183
184        fn register_resources(_comp: &RegisterResources) -> Result<Self, Self::RegisterError> {
185            Ok(Self {})
186        }
187    }
188
189    #[test]
190    fn name_too_long() -> Result<(), ComponentInitError> {
191        let comp = HalComponent::<EmptyResources>::new(
192            "name-thats-way-too-long-for-linuxcnc-to-handle-wow-this-is-ridiculous",
193        );
194
195        println!("{:?}", comp);
196
197        match comp {
198            Err(ComponentInitError::NameLength) => Ok(()),
199            Err(e) => Err(e),
200            Ok(_) => Err(ComponentInitError::Init),
201        }
202    }
203}