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