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}