kpal_gpio_cdev/
lib.rs

1//! KPAL plugin to control the output of a single GPIO pin using the GPIO char device.
2mod errors;
3
4use std::{cell::RefCell, convert::TryInto, ffi::CString};
5
6use gpio_cdev::{Chip, LineHandle, LineRequestFlags};
7use libc::c_int;
8use log;
9
10use kpal_plugin::{error_codes::*, *};
11
12use crate::errors::*;
13
14const DEFAULT_DEVICE_FILE: &str = "/dev/gpiochip0";
15
16/// The GPIO pin number.
17const DEFAULT_OFFSET: u32 = 4;
18
19/// Holds the state of the plugin, including the chip and line handles.
20#[derive(Debug)]
21#[repr(C)]
22struct GPIOPlugin {
23    /// The collection of attributes that describe this plugin.
24    attributes: Attributes<Self, GPIOPluginError>,
25
26    /// A handle to the chip that represents the character device.
27    chip: Option<RefCell<Chip>>,
28
29    /// A handle to the particular GPIO line that is controlled by this plugin.
30    line_handle: Option<LineHandle>,
31}
32
33impl PluginAPI<GPIOPluginError> for GPIOPlugin {
34    /// Returns a new instance of a GPIO plugin.
35    fn new() -> Result<GPIOPlugin, GPIOPluginError> {
36        let attributes = RefCell::new(multimap! {
37            0, "device file" => Attribute {
38                    name: CString::new("Device file").unwrap(),
39                    value: Value::String(CString::new(DEFAULT_DEVICE_FILE).unwrap()),
40                    callbacks_init: Callbacks::Update,
41                    callbacks_run: Callbacks::Constant,
42            },
43            1, "offset" => Attribute {
44                    name: CString::new("Offset").unwrap(),
45                    value: Value::Uint(DEFAULT_OFFSET),
46                    callbacks_init: Callbacks::Update,
47                    callbacks_run: Callbacks::Constant,
48            },
49            2, "pin state" => Attribute {
50                    name: CString::new("Pin state").unwrap(),
51                    value: Value::Int(0),
52                    callbacks_init: Callbacks::Constant,
53                    callbacks_run: Callbacks::GetAndSet(on_get_pin_state, on_set_pin_state),
54            },
55        });
56
57        Ok(GPIOPlugin {
58            attributes,
59            chip: None,
60            line_handle: None,
61        })
62    }
63
64    /// Initializes the GPIO hardware device.
65    fn init(&mut self) -> Result<(), GPIOPluginError> {
66        let device_file = if let Value::String(device_file) = &self
67            .attributes
68            .borrow()
69            .get_alt(&"device file")
70            .ok_or(GPIOPluginError {
71                error_code: ATTRIBUTE_DOES_NOT_EXIST,
72                side: None,
73            })?
74            .value
75        {
76            device_file.clone().into_string()?
77        } else {
78            unreachable!()
79        };
80        let mut chip = Chip::new(device_file)?;
81
82        let offset = if let Value::Uint(offset) = self
83            .attributes
84            .borrow()
85            .get_alt(&"offset")
86            .ok_or(GPIOPluginError {
87                error_code: ATTRIBUTE_DOES_NOT_EXIST,
88                side: None,
89            })?
90            .value
91        {
92            offset
93        } else {
94            unreachable!()
95        };
96
97        let handle = chip
98            .get_line(offset)?
99            .request(LineRequestFlags::OUTPUT, 0, "set-output")?;
100
101        self.chip = Some(RefCell::new(chip));
102        self.line_handle = Some(handle);
103
104        Ok(())
105    }
106
107    fn attributes(&self) -> &Attributes<GPIOPlugin, GPIOPluginError> {
108        &self.attributes
109    }
110}
111
112/// The callback function that is fired when the pin state is read during the run phase.
113///
114/// # Arguments
115///
116/// * `plugin` - A reference to the struct that contains the plugin's state.
117/// * `_cached` - The most recently read or modified value of the attribute.
118fn on_get_pin_state(plugin: &GPIOPlugin, _cached: &Value) -> Result<Value, GPIOPluginError> {
119    let pin_value = plugin
120        .line_handle
121        .as_ref()
122        .ok_or_else(|| PluginUninitializedError {})?
123        .get_value()?;
124    let value = Value::Int(pin_value.try_into()?);
125
126    Ok(value)
127}
128
129/// The callback function that is fired when the pin state is set.
130///
131/// # Arguments
132///
133/// * `plugin` - A reference to the struct that contains the plugin's state.
134/// * `_cached` - The most recently read or modified value of the attribute.
135/// * `val` -  The new value of the attribute.
136fn on_set_pin_state(
137    plugin: &GPIOPlugin,
138    _cached: &Value,
139    val: &Val,
140) -> Result<(), GPIOPluginError> {
141    let pin_value = if let Val::Int(pin_value) = val {
142        pin_value.to_owned().try_into()?
143    } else {
144        unreachable!()
145    };
146
147    plugin
148        .line_handle
149        .as_ref()
150        .ok_or_else(|| PluginUninitializedError {})?
151        .set_value(pin_value)?;
152
153    Ok(())
154}
155
156declare_plugin!(GPIOPlugin, GPIOPluginError);