linuxcnc_hal/
lib.rs

1//! A safe, high-level interface to LinuxCNC's HAL (Hardware Abstraction Layer) module.
2//!
3//! For lower level, unsafe use, see the
4//! [`linuxcnc-hal-sys`](https://crates.io/crates/linuxcnc-hal-sys) crate.
5//!
6//! # Development setup
7//!
8//! [`bindgen`](https://github.com/rust-lang/rust-bindgen) must be set up correctly. Follow the
9//! [requirements section of its docs](https://rust-lang.github.io/rust-bindgen/requirements.html).
10//!
11//! To run and debug any HAL components, the LinuxCNC simulator can be set up. There's a guide
12//! [here](https://wapl.es/cnc/2020/01/25/linuxcnc-simulator-build-linux-mint.html) for Linux Mint
13//! (and other Debian derivatives).
14//!
15//! # Project setup
16//!
17//! This crate depends on the `linuxcnc-hal-sys` crate which requires the `LINUXCNC_SRC` environment
18//! variable to be set to correctly generate the C bindings. The value must be the absolute path to
19//! the root of the LinuxCNC source code.
20//!
21//! **The version of the LinuxCNC sources must match the LinuxCNC version used in the machine
22//! control.**
23//!
24//! ```bash
25//! # Clone LinuxCNC source code into linuxcnc/
26//! git clone https://github.com/LinuxCNC/linuxcnc.git
27//!
28//! # Check out a specific version tag. This may also be a commit, but must match the version in use by the machine control.
29//! cd linuxcnc && git checkout v2.8.0 && cd ..
30//!
31//! # Create your component lib
32//! cargo new --lib my_comp
33//!
34//! cd my_comp
35//!
36//! # Add LinuxCNC HAL bindings as a Cargo dependency with cargo-edit
37//! cargo add linuxcnc-hal
38//!
39//! LINUXCNC_SRC=/path/to/linuxcnc/source/code cargo build
40//! ```
41//!
42//! If LinuxCNC is configured to run in place, `liblinuxcnchal.so.0` may not be found on startup. To
43//! fix, try setting the library path with e.g. `export LD_LIBRARY_PATH=~/Repositories/linuxcnc/lib`
44//!
45//! # Examples
46//!
47//! ## Create a component with input and output
48//!
49//! This example creates a component called `"pins"` with a single input (`"input-1"`) and output
50//! pin (`"output-1"`). It enters an infinite loop which updates the value of `output-1` every
51//! second. LinuxCNC convention dictates that component and pin names should be `dash-cased`.
52//!
53//! This example can be loaded into LinuxCNC with a `.hal` file that looks similar to this:
54//!
55//! ```hal
56//! loadusr -W /path/to/your/component/target/debug/comp_bin_name
57//! net input-1 spindle.0.speed-out pins.input-1
58//! net output-1 pins.output-1
59//! ```
60//!
61//! Pins and other resources are registered using the [`Resources`] trait. This example creates a
62//! `Pins` struct which holds the two pins. [`HalComponent::new`] handles component creation,
63//! resources (pin, signal, etc) initialisation and UNIX signal handler registration.
64//!
65//! ```rust,no_run
66//! use linuxcnc_hal::{
67//!     error::PinRegisterError,
68//!     hal_pin::{InputPin, OutputPin},
69//!     prelude::*,
70//!     HalComponent, RegisterResources, Resources,
71//! };
72//! use std::{
73//!     error::Error,
74//!     thread,
75//!     time::{Duration, Instant},
76//! };
77//!
78//! struct Pins {
79//!     input_1: InputPin<f64>,
80//!     output_1: OutputPin<f64>,
81//! }
82//!
83//! impl Resources for Pins {
84//!     type RegisterError = PinRegisterError;
85//!
86//!     fn register_resources(comp: &RegisterResources) -> Result<Self, Self::RegisterError> {
87//!         Ok(Pins {
88//!             input_1: comp.register_pin::<InputPin<f64>>("input-1")?,
89//!             output_1: comp.register_pin::<OutputPin<f64>>("output-1")?,
90//!         })
91//!     }
92//! }
93//!
94//! fn main() -> Result<(), Box<dyn Error>> {
95//!     rtapi_logger::init();
96//!
97//!     // Create a new HAL component called `rust-comp`
98//!     let comp: HalComponent<Pins> = HalComponent::new("rust-comp")?;
99//!
100//!     // Get a reference to the `Pins` struct
101//!     let pins = comp.resources();
102//!
103//!     let start = Instant::now();
104//!
105//!     // Main control loop
106//!     while !comp.should_exit() {
107//!         let time = start.elapsed().as_secs() as i32;
108//!
109//!         // Set output pin to elapsed seconds since component started
110//!         pins.output_1.set_value(time.into())?;
111//!
112//!         // Print the current value of the input pin
113//!         println!("Input: {:?}", pins.input_1.value());
114//!
115//!         // Sleep for 1000ms. This should be a lower time if the component needs to update more
116//!         // frequently.
117//!         thread::sleep(Duration::from_millis(1000));
118//!     }
119//!
120//!     // The custom implementation of `Drop` for `HalComponent` ensures that `hal_exit()` is called
121//!     // at this point. Registered signal handlers are also deregistered.
122//!
123//!     Ok(())
124//! }
125//! ```
126
127#![deny(missing_docs)]
128#![deny(rustdoc::broken_intra_doc_links)]
129
130#[macro_use]
131extern crate log;
132
133mod component;
134pub mod error;
135mod hal_parameter;
136pub mod hal_pin;
137pub mod prelude;
138
139use hal_parameter::ParameterPermissions;
140
141pub use crate::component::HalComponent;
142pub use crate::hal_parameter::Parameter;
143use crate::{
144    error::{ParameterRegisterError, PinRegisterError, ResourcesError},
145    hal_parameter::HalParameter,
146    hal_pin::HalPin,
147};
148
149/// Resources for a component
150pub trait Resources: Sized {
151    /// The type of error to return if a resource registration failed
152    ///
153    /// This must be convertable into a [`ResourcesError`].
154    type RegisterError: Into<ResourcesError>;
155
156    /// Register resources against a component
157    fn register_resources(comp: &RegisterResources) -> Result<Self, Self::RegisterError>;
158}
159
160/// Component metadata used when registering resources
161pub struct RegisterResources {
162    /// Component name
163    name: &'static str,
164
165    /// Component ID
166    id: i32,
167}
168
169impl RegisterResources {
170    /// Register a pin with this component.
171    ///
172    /// The pin name will be prefixed with the component name
173    pub fn register_pin<P>(&self, pin_name: &'static str) -> Result<P, PinRegisterError>
174    where
175        P: HalPin,
176    {
177        let full_name = format!("{}.{}", self.name, pin_name);
178
179        let pin = P::register(&full_name, self.id)?;
180
181        Ok(pin)
182    }
183
184    /// Register a read/write parameter with this component.
185    ///
186    /// The parameter name will be prefixed with the component name.
187    ///
188    /// To register a pin that LinuxCNC cannot write to, call [`RegisterResources::register_readonly_parameter`].
189    pub fn register_parameter<P>(
190        &self,
191        parameter_name: &'static str,
192    ) -> Result<P, ParameterRegisterError>
193    where
194        P: HalParameter,
195    {
196        let full_name = format!("{}.{}", self.name, parameter_name);
197
198        let parameter = P::register(&full_name, self.id, ParameterPermissions::ReadWrite)?;
199
200        Ok(parameter)
201    }
202
203    /// Register a read only parameter with this component.
204    ///
205    /// The parameter name will be prefixed with the component name
206    pub fn register_readonly_parameter<P>(
207        &self,
208        parameter_name: &'static str,
209    ) -> Result<P, ParameterRegisterError>
210    where
211        P: HalParameter,
212    {
213        let full_name = format!("{}.{}", self.name, parameter_name,);
214
215        let parameter = P::register(&full_name, self.id, ParameterPermissions::ReadOnly)?;
216
217        Ok(parameter)
218    }
219}