Das ist demnach nicht mehr als Dateien öffnen, lesen, schreiben und wieder schließen.
Ich hatte für Azubinen und Azubis eine kleine Aufgabe erstellt, die vorsah eine entsprechende Library zu programmieren und diese dann in einem kleinen Konsolenprogramm zu verwenden. Dabei sollten sie die Lösung in C entwickeln.
Meine Musterlösung habe ich in Rust übersetzt und diese möchte ich hier teilen.
Die Library
Wir fangen mit der Implementation der Bibliothek an, die wir später in einem Beispielprogramm verwenden möchten. Hierzu erstellen wir erst den Projektordner und danach das Projekt mit dem Rust Packet Manager Cargo .
~$ mkdir rgpio
~$ cd rgpio
~$ cargo new --lib rgpiolib
In der Datei ./src/lib.rs
werden wir nun einige Funktionen schreiben, die zum Konfigurieren, dem Lesen und dem Schreiben der einzelnen Ports dienen sollen.
./src/lib.rs
pub mod gpio {
use std::{fmt, fs};
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
/// GPIO paths
///
/// This enum represents the paths to the GPIO files.
enum GpioPaths {
/// Export GPIO pin
EXPORT,
/// Unexport GPIO pin
UNEXPORT,
/// Value of GPIO pin
VALUE(i32),
/// Direction of GPIO pin
DIRECTION(i32),
}
/// Implement the `as_str` method for the `GpioPaths` enum.
impl GpioPaths {
/// Returns the path as a string slice.
pub fn as_str(&self) -> &'static str {
match *self {
/// Path to export GPIO pin
GpioPaths::EXPORT => "/sys/class/gpio/export",
/// Path to unexport GPIO pin
GpioPaths::UNEXPORT => "/sys/class/gpio/unexport",
/// Path to value of GPIO pin
GpioPaths::VALUE(num) => {
let path = format!("/sys/class/gpio/gpio{}/value", num);
Box::leak(path.into_boxed_str())
},
/// Path to direction of GPIO pin
GpioPaths::DIRECTION(num) => {
let path = format!("/sys/class/gpio/gpio{}/direction", num);
Box::leak(path.into_boxed_str())
},
}
}
}
/// GPIO directions
///
/// This enum represents the directions of a GPIO pin.
///
/// - Input
/// - Output
pub type Directions = directions::Directions;
/// Implement the `as_str` method for the `Directions` enum.
pub mod directions {
/// GPIO directions
///
/// This enum represents the directions of a GPIO pin.
pub enum Directions {
Input,
Output,
}
/// Implement the `as_str` method for the `Directions` enum.
impl Directions {
/// Returns the direction as a string slice.
pub fn as_str(&self) -> &'static str {
match *self {
Directions::Input => "in",
Directions::Output => "out",
}
}
/// Returns the direction as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
}
/// GPIO errors
///
/// This enum represents the errors that can occur when interacting with GPIO pins.
///
/// - IO error
/// - ParseInt error
#[derive(Debug)]
pub enum GpioError {
Io(std::io::Error),
ParseInt(std::num::ParseIntError),
}
/// Implement the `Display` trait for the `GpioError` enum.
///
/// This trait allows the `GpioError` enum to be formatted using the `format!` macro.
impl fmt::Display for GpioError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GpioError::Io(ref err) => write!(f, "IO error: {}", err),
GpioError::ParseInt(ref err) => write!(f, "ParseInt error: {}", err),
}
}
}
/// Implement the `From<std::io::Error>` trait for the `GpioError` enum.
impl From<std::io::Error> for GpioError {
fn from(err: std::io::Error) -> GpioError {
GpioError::Io(err)
}
}
/// Implement the `From<std::num::ParseIntError>` trait for the `GpioError` enum.
impl From<std::num::ParseIntError> for GpioError {
fn from(err: std::num::ParseIntError) -> GpioError {
GpioError::ParseInt(err)
}
}
/// GPIO result
///
/// This type alias represents the result of a GPIO operation.
type GpioResult<T> = Result<T, GpioError>;
/// Open file
///
/// This function opens a file and returns a file handle.
///
/// # Arguments
///
/// - `filepath` - A string slice that represents the path to the file.
///
/// # Returns
///
/// A `GpioResult` that contains a file handle.
fn open_file(filepath: &'static str) -> GpioResult<File> {
let path = Path::new(&filepath);
Ok(fs::OpenOptions::new().write(true).open(path)?)
}
/// Export GPIO pin
///
/// This function exports (enables) a GPIO pin. The GPIO pin is
/// passed as an argument. The function writes the GPIO pin to
/// the `/sys/class/gpio/export` file.
///
///
/// # Arguments
///
/// - `gpio_num` - An integer that represents the GPIO pin number.
///
/// # Returns
///
/// A `GpioResult` that contains the result of the operation.
///
/// # Example
///
/// ```rust
/// use rgpiolib::gpio;
///
/// fn main() {
/// let gpio_num = 4;
/// gpio::export(gpio_num);
/// }
/// ```
///
/// # Note
///
/// This function should be called before setting the direction of the GPIO pin.
pub fn export(gpio_num: i32) -> GpioResult<()> {
open_file(GpioPaths::EXPORT.as_str()).and_then(|mut file| {
file.write_all(gpio_num.to_string().as_bytes()).map_err(|why| {
GpioError::Io(why)
})
})
}
/// Unexport GPIO pin
///
/// This function unexports (disables) a GPIO pin. The GPIO pin is
/// passed as an argument. The function writes the GPIO pin to the
/// `/sys/class/gpio/unexport` file.
///
/// # Arguments
///
/// - `gpio_num` - An integer that represents the GPIO pin number.
///
/// # Returns
///
/// A `GpioResult` that contains the result of the operation.
///
/// # Example
///
/// ```rust
/// use rgpiolib::gpio;
///
/// fn main() {
/// let gpio_num = 4;
/// gpio::export(gpio_num);
/// gpio::unexport(gpio_num);
/// }
/// ```
///
/// # Note
///
/// This function should be called after exporting the GPIO pin.
pub fn unexport(gpio_num: i32) -> GpioResult<()> {
open_file(GpioPaths::UNEXPORT.as_str()).and_then(|mut file| {
file.write_all(gpio_num.to_string().as_bytes()).map_err(|why| {
GpioError::Io(why)
})
})
}
/// Write to GPIO pin
///
/// This function writes a signal to a GPIO pin. The GPIO pin and signal are
/// passed as arguments. The function writes the signal to the
/// `/sys/class/gpio/gpio{num}/value` file.
///
/// # Arguments
///
/// - `gpio_num` - An integer that represents the GPIO pin number.
/// - `signal` - A boolean that represents the signal to write to the GPIO pin.
///
/// # Returns
///
/// A `GpioResult` that contains the result of the operation.
pub fn write(gpio_num: i32, signal: bool) -> GpioResult<()> {
Ok(
open_file(GpioPaths::VALUE(gpio_num).as_str()).and_then(|mut file| {
file.write_all(signal.to_string().as_bytes()).map_err(GpioError::from)
})?
)
}
/// Read from GPIO pin
///
/// This function reads a signal from a GPIO pin. The GPIO pin is passed as an argument.
/// The function reads the signal from the `/sys/class/gpio/gpio{num}/value` file.
///
/// # Arguments
///
/// - `gpio_num` - An integer that represents the GPIO pin number.
///
/// # Returns
///
/// A `GpioResult` that contains the signal read from the GPIO pin.
pub fn read(gpio_num: i32) -> GpioResult<bool> {
let value = fs::read_to_string(GpioPaths::VALUE(gpio_num).as_str()).and_then(|contents| {
match contents.parse::<i32>() {
Ok(val) => Ok(val),
Err(why) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, why)),
}
}).map_err(GpioError::from);
value.map(|val| val > 0)
}
/// Set GPIO pin direction
///
/// This function sets the direction of a GPIO pin. The GPIO pin and direction are passed as arguments.
/// The function writes the direction to the `/sys/class/gpio/gpio{num}/direction` file.
///
/// # Arguments
///
/// - `gpio_num` - An integer that represents the GPIO pin number.
/// - `direction` - A `Directions` enum that represents the direction of the GPIO pin.
///
/// # Returns
///
/// A `GpioResult` that contains the result of the operation.
///
/// # Example
///
/// ```rust
/// use rgpiolib::gpio;
///
/// fn main() {
/// let gpio_num = 4;
/// gpio::export(gpio_num);
/// gpio::set_direction(gpio_num, gpio::Directions::Output);
/// }
/// ```
///
/// # Note
///
/// This function should be called after exporting the GPIO pin.
pub fn set_direction(gpio_num: i32, direction: Directions) -> GpioResult<()> {
open_file(GpioPaths::DIRECTION(gpio_num).as_str()).and_then(|mut file| {
file.write_all(direction.as_bytes()).map_err(GpioError::from)
})
}
}
Die Konsolenanwendung
Danach können wir die Library in einer Anwendung einbinden. Als Beispiel soll ein CLI Anwendung dienen die alle GPIO, die mit den Startparametern übergeben wurden, nacheinander einschaltet, 500ms wartet und dann wieder ausschaltet.
Dafür wechseln wir in das Projekt - Hauptverzeichnis und führen wieder Cargo aus.
~$ cd ..
~$ cargo new rgpio
In der Datei ./cargo.toml
fügen wir nun die vorab erstellte Bibliothek rgpiolib
ein.
./cargo.toml
[package]
name = "rgpio"
version = "0.1.0"
edition = "2021"
authors = [Manfred Michaelis <mm@michm.de>]
[dependencies]
rgpiolib = { path = "../rgpiolib" }
Danach können wir die Library in unserem Source nutzen. Was wir auch direkt in der Datei ./src/main.rs
ausprobieren werden.
./src/main.rs
mod tests;
use std::env;
use std::{thread, time};
use std::error::Error;
use rgpiolib::gpio;
fn main() -> Result<(), dyn Error> {
// Collect command-line arguments into a vector of strings
let args: Vec<String> = env::args().collect();
// Check if no arguments are provided
if args.len() == 1 {
print!("usage: rgpio ...[num]");
} else {
// Iterate over the provided arguments
for i in 1..args.len() {
// Parse the argument to an integer
let gpio_num = match args[i].parse() {
Err(why) => panic!("an error has occurred: {}", why),
Ok(num) => num,
};
// Export the GPIO pin and set its direction to output
if (gpio::export(gpio_num).is_ok()) {
gpio::set_direction(gpio_num, gpio::Directions::Output)?;
gpio::write(gpio_num, true)?;
// Read the GPIO pin value and print it
let mut val = gpio::read(gpio_num)?;
println!("{}", val);
// Sleep for 500 milliseconds
thread::sleep(time::Duration::from_millis(500));
// Write false to the GPIO pin and sleep again
gpio::write(gpio_num, false)?;
thread::sleep(time::Duration::from_millis(500));
// Read the GPIO pin value again and print it
val = gpio::read(gpio_num)?;
println!("{}", val);
// Unexport the GPIO pin
gpio::unexport(gpio_num)?;
} else {
println!("failed to export gpio {}", gpio_num);
}
}
}
Ok(())
}
Repository
GitHub - jibbex/rgpio
Contribute to jibbex/rgpio development by creating an account on GitHub.