1use glob::glob;
10use serde_derive::Deserialize;
11use std::collections::{BTreeSet, HashMap};
12use std::fmt;
13use std::fs::{self, File};
14use std::io;
15use std::io::prelude::*;
16use std::path::Path;
17use std::str::FromStr;
18use sysfs_gpio;
19use toml;
20
21const DEFAULT_SYMLINK_ROOT: &str = "/var/run/gpio";
22
23#[derive(Debug, PartialEq, Clone)]
24pub struct Direction(pub sysfs_gpio::Direction);
25
26#[derive(Deserialize, Debug)]
27#[serde(remote = "sysfs_gpio::Direction")]
28pub enum DirectionDef {
29 #[serde(rename = "in")]
30 In,
31 #[serde(rename = "out")]
32 Out,
33 #[serde(rename = "high")]
34 High,
35 #[serde(rename = "low")]
36 Low,
37}
38
39impl From<sysfs_gpio::Direction> for Direction {
40 fn from(e: sysfs_gpio::Direction) -> Self {
41 Direction(e)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Deserialize)]
46pub struct PinConfig {
47 pub num: u64,
48 #[serde(default = "default_direction")]
49 #[serde(with = "DirectionDef")]
50 pub direction: sysfs_gpio::Direction,
51 #[serde(default)]
52 pub names: BTreeSet<String>,
53 #[serde(default = "bool_true")]
54 pub export: bool,
55 #[serde(default)]
56 pub active_low: bool,
57 pub user: Option<String>,
58 pub group: Option<String>,
59 pub mode: Option<u32>,
60}
61
62fn default_direction() -> sysfs_gpio::Direction {
63 sysfs_gpio::Direction::In
64}
65
66fn bool_true() -> bool {
67 true
68}
69
70#[derive(Clone, Debug, Default, Deserialize)]
71pub struct GpioConfig {
72 pub pins: Vec<PinConfig>,
73 #[serde(default)]
74 pub config: SysConfig,
75}
76
77#[derive(Clone, Debug, Default, Deserialize)]
78pub struct SysConfig {
79 pub symlink_root: Option<String>,
80}
81
82#[derive(Debug)]
83pub enum Error {
84 IoError(io::Error),
85 ParserErrors(toml::de::Error),
86 NoConfigFound,
87 DuplicateNames(String),
88}
89
90impl fmt::Display for Error {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 match *self {
93 Error::IoError(ref e) => e.fmt(f),
94 Error::ParserErrors(ref e) => e.fmt(f),
95 Error::NoConfigFound => write!(f, "No Config Found"),
96 Error::DuplicateNames(ref e) => e.fmt(f),
97 }
98 }
99}
100
101impl From<io::Error> for Error {
102 fn from(e: io::Error) -> Self {
103 Error::IoError(e)
104 }
105}
106
107impl PinConfig {
108 pub fn get_pin(&self) -> sysfs_gpio::Pin {
110 sysfs_gpio::Pin::new(self.num)
111 }
112}
113
114impl FromStr for GpioConfig {
115 type Err = Error;
116 fn from_str(config: &str) -> Result<Self, Error> {
118 let cfg = toml::from_str(config);
119 match cfg {
120 Ok(cfg) => {
121 let val_config: GpioConfig = toml::from_str(config).unwrap();
122 val_config.validate()?;
123 Ok(cfg)
124 }
125 Err(e) => Err(Error::ParserErrors(e)),
126 }
127 }
128}
129
130impl GpioConfig {
131 fn validate(&self) -> Result<(), Error> {
136 let mut all_names: HashMap<&str, &PinConfig> = HashMap::new();
137 for pin in &self.pins {
138 for name in &pin.names {
139 if let Some(other_pin) = all_names.get(&name[..]) {
140 return Err(Error::DuplicateNames(format!(
141 "Pins {} and {} share duplicate \
142 name '{}'",
143 pin.num, other_pin.num, name
144 )));
145 }
146 all_names.insert(&name[..], pin);
147 }
148 }
149
150 Ok(())
151 }
152
153 pub fn load(configs: &[String]) -> Result<GpioConfig, Error> {
171 let mut config_instances: Vec<GpioConfig> = Vec::new();
172
173 if fs::metadata("/etc/gpio.toml").is_ok() {
175 config_instances.push(Self::from_file("/etc/gpio.toml")?);
176 }
177 for fragment in glob("/etc/gpio.d/*.toml").unwrap().filter_map(Result::ok) {
179 config_instances.push(Self::from_file(fragment)?);
180 }
181
182 for fragment in configs {
184 config_instances.push(Self::from_file(fragment)?);
185 }
186
187 if config_instances.is_empty() {
188 Err(Error::NoConfigFound)
189 } else {
190 let mut cfg = config_instances.remove(0);
191 for higher_priority_cfg in config_instances {
192 cfg.update(higher_priority_cfg)?;
193 }
194 Ok(cfg)
195 }
196 }
197
198 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<GpioConfig, Error> {
200 let mut contents = String::new();
201 let mut f = File::open(path)?;
202 f.read_to_string(&mut contents)?;
203 let config = GpioConfig::from_str(&contents[..])?;
204 config.validate()?;
205
206 Ok(config)
207 }
208
209 pub fn get_pin(&self, name: &str) -> Option<&PinConfig> {
211 if let Some(pin) = self.pins.iter().find(|p| p.names.contains(name)) {
213 return Some(pin);
214 }
215
216 match name.parse::<u64>() {
218 Ok(pin_num) => self.pins.iter().find(|p| p.num == pin_num),
219 Err(_) => None,
220 }
221 }
222
223 pub fn get_pins(&self) -> &[PinConfig] {
225 &self.pins[..]
226 }
227
228 pub fn get_symlink_root(&self) -> &str {
230 match self.config.symlink_root {
231 Some(ref root) => root,
232 None => DEFAULT_SYMLINK_ROOT,
233 }
234 }
235
236 pub fn update(&mut self, other: GpioConfig) -> Result<(), Error> {
240 if let Some(symlink_root) = other.config.symlink_root {
241 self.config.symlink_root = Some(symlink_root);
242 }
243 for other_pin in other.pins {
244 let existing = match self.pins.iter_mut().find(|p| p.num == other_pin.num) {
246 Some(pin) => {
247 pin.names.extend(other_pin.names.clone());
248 pin.direction = other_pin.direction;
249 pin.export = other_pin.export;
250 pin.active_low = other_pin.active_low;
251 true
252 }
253 None => false,
254 };
255
256 if !existing {
257 self.pins.push(other_pin);
258 }
259 }
260
261 self.validate()
263 }
264}
265
266#[cfg(test)]
267mod test {
268 use super::*;
269 use std::collections::BTreeSet;
270 use std::iter::FromIterator;
271 use std::str::FromStr;
272 use sysfs_gpio::Direction as D;
273
274 const BASIC_CFG: &'static str = r#"
275[[pins]]
276num = 73
277names = ["reset_button"]
278direction = "in" # default: in
279active_low = true # default: false
280export = true # default: true
281
282[[pins]]
283num = 37
284names = ["status_led", "A27", "green_led"]
285direction = "out"
286"#;
287
288 const COMPACT_CFG: &'static str = r#"
289pins = [
290 { num = 73, names = ["reset_button"], direction = "in", active_low = true, export = true},
291 { num = 37, names = ["status_led", "A27", "green_led"], direction = "out"},
292]
293
294[config]
295symlink_root = "/tmp/gpio"
296"#;
297
298 const MISSING_PINNUM_CFG: &'static str = r#"
299[[pins]]
300export = true
301"#;
302
303 const DUPLICATED_NAMES_CFG: &'static str = r#"
304[[pins]]
305num = 25
306names = ["foo", "bar"]
307
308[[pins]]
309num = 26
310names = ["baz", "foo"] # foo is repeated!
311"#;
312
313 const PARTIALLY_OVERLAPS_BASIC_CFG: &'static str = r#"
314[config]
315symlink_root = "/foo/bar/baz"
316
317# Add a new alias to pin 73
318[[pins]]
319num = 73
320names = ["new_name"]
321
322
323# Change pin 37 to be an input (not output)
324[[pins]]
325num = 37
326direction = "in"
327
328# New pin 88
329[[pins]]
330num = 88
331names = ["wildcard"]
332"#;
333
334 #[test]
335 fn test_parse_basic() {
336 let config = GpioConfig::from_str(BASIC_CFG).unwrap();
337 let status_led = config.pins.get(1).unwrap();
338 let names = BTreeSet::from_iter(vec![
339 String::from("status_led"),
340 String::from("A27"),
341 String::from("green_led"),
342 ]);
343
344 assert_eq!(config.get_symlink_root(), "/var/run/gpio");
345
346 let reset_button = config.pins.get(0).unwrap();
347 assert_eq!(reset_button.num, 73);
348 assert_eq!(
349 reset_button.names,
350 BTreeSet::from_iter(vec![String::from("reset_button")])
351 );
352 assert_eq!(reset_button.direction, D::In);
353 assert_eq!(reset_button.active_low, true);
354 assert_eq!(reset_button.export, true);
355
356 assert_eq!(status_led.names, names);
357 assert_eq!(status_led.direction, D::Out);
358 assert_eq!(status_led.active_low, false);
359 assert_eq!(status_led.export, true);
360 }
361
362 #[test]
363 fn test_get_pin_present() {
364 let config = GpioConfig::from_str(BASIC_CFG).unwrap();
365 let status_led = config.get_pin("status_led").unwrap();
366 assert_eq!(status_led.num, 37);
367 }
368
369 #[test]
370 fn test_get_pin_not_present() {
371 let config = GpioConfig::from_str(BASIC_CFG).unwrap();
372 assert_eq!(config.get_pin("missing"), None);
373 }
374
375 #[test]
376 fn test_get_pin_by_number() {
377 let config = GpioConfig::from_str(BASIC_CFG).unwrap();
378 let status_led = config.get_pin("37").unwrap();
379 assert_eq!(status_led.num, 37);
380 }
381
382 #[test]
383 fn test_get_pin_by_number_not_found() {
384 let config = GpioConfig::from_str(BASIC_CFG).unwrap();
385 assert_eq!(config.get_pin("64"), None);
386 }
387
388 #[test]
389 fn test_parser_compact() {
390 let config = GpioConfig::from_str(COMPACT_CFG).unwrap();
391 let status_led = config.pins.get(1).unwrap();
392 let names = BTreeSet::from_iter(vec![
393 String::from("status_led"),
394 String::from("A27"),
395 String::from("green_led"),
396 ]);
397 assert_eq!(status_led.names, names);
398 assert_eq!(status_led.direction, D::Out);
399 assert_eq!(status_led.active_low, false);
400 assert_eq!(status_led.export, true);
401 assert_eq!(config.get_symlink_root(), "/tmp/gpio")
402 }
403
404 #[test]
405 fn test_parser_empty_toml() {
406 let configstr = "";
407 match GpioConfig::from_str(configstr) {
408 Ok(pins) => assert_eq!(pins.pins, vec![]),
409 Err(Error::ParserErrors(_)) => {}
410 _ => panic!("Expected a parsing error"),
411 }
412 }
413
414 #[test]
415 fn test_parser_missing_pinnum() {
416 match GpioConfig::from_str(MISSING_PINNUM_CFG) {
417 Err(Error::ParserErrors(_)) => {}
418 _ => panic!("Expected a parsing error"),
419 }
420 }
421
422 #[test]
423 fn test_parse_error_bad_toml() {
424 let configstr = r"[] -*-..asdf=-=-@#$%^&*()";
426 match GpioConfig::from_str(configstr) {
427 Err(Error::ParserErrors(_)) => {}
428 _ => panic!("Did not receive parse error when expected"),
429 }
430 }
431
432 #[test]
433 fn test_error_on_duplicated_names() {
434 match GpioConfig::from_str(DUPLICATED_NAMES_CFG) {
435 Err(Error::DuplicateNames(_)) => (),
436 r => panic!("Expected DuplicateNames Error, got {:?}", r),
437 }
438 }
439
440 #[test]
441 fn test_merge_configs() {
442 let mut config = GpioConfig::from_str(BASIC_CFG).unwrap();
443 let cfg2 = GpioConfig::from_str(PARTIALLY_OVERLAPS_BASIC_CFG).unwrap();
444
445 config.update(cfg2).unwrap();
447
448 assert_eq!(config.get_symlink_root(), "/foo/bar/baz");
449
450 let reset_button = config.pins.get(0).unwrap();
451 assert_eq!(reset_button.num, 73);
452 assert_eq!(
453 reset_button.names,
454 BTreeSet::from_iter(vec![String::from("reset_button"), String::from("new_name")])
455 );
456 assert_eq!(reset_button.direction, D::In);
457 assert_eq!(reset_button.active_low, false);
458 assert_eq!(reset_button.export, true);
459
460 let status_led = config.pins.get(1).unwrap();
461 let names = BTreeSet::from_iter(vec![
462 String::from("status_led"),
463 String::from("A27"),
464 String::from("green_led"),
465 ]);
466 assert_eq!(status_led.names, names);
467 assert_eq!(status_led.direction, D::In);
468 assert_eq!(status_led.active_low, false);
469 assert_eq!(status_led.export, true);
470
471 let wildcard = config.pins.get(2).unwrap();
472 assert_eq!(wildcard.num, 88);
473 assert_eq!(
474 wildcard.names,
475 BTreeSet::from_iter(vec![String::from("wildcard")])
476 );
477 }
478}