1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! A Rust library for the Raspberry Pi Sense HAT Joystick.
//! =======================================================
//!
//! This library supports the joystick incorporated with the Sense HAT.
//!
//! The Joystick provides a driver for `evdev`.
#[cfg(feature = "linux-evdev")]
extern crate evdev;
#[macro_use]
extern crate failure;
extern crate glob;
#[cfg(feature = "poll")]
extern crate mio;

#[cfg(feature = "poll")]
mod poll;

use evdev::Device;
use failure::Error;
use glob::glob;

use std::io;
use std::os::unix::io::RawFd;
use std::time::Duration;

// Device name provided by the hardware. We match against it.
const SENSE_HAT_EVDEV_NAME: &[u8; 31] = b"Raspberry Pi Sense HAT Joystick";

/// Direction in which the JoyStick is moved.
///
/// Internally, it matches the key-press events:
///
/// * `Direction::Enter = 28`
/// * `Direction::Up = 103`
/// * `Direction::Down = 108`
/// * `Direction::Left = 105`
/// * `Direction::Up = 106`
#[derive(Debug)]
pub enum Direction {
    Enter = 28,
    Up = 103,
    Down = 108,
    Left = 105,
    Right = 106,
}

impl Direction {
    fn try_from(code: usize) -> Result<Self, Error> {
        match code {
            c if c == Direction::Enter as usize => Ok(Direction::Enter),
            c if c == Direction::Up as usize => Ok(Direction::Up),
            c if c == Direction::Down as usize => Ok(Direction::Down),
            c if c == Direction::Left as usize => Ok(Direction::Left),
            c if c == Direction::Right as usize => Ok(Direction::Right),
            c => bail!("unrecognized joystick code: {}", c),
        }
    }
}

/// The action that was executed with the given `Direction`.
#[derive(Debug)]
pub enum Action {
    Release = 0,
    Press = 1,
    Hold = 2,
}

impl Action {
    fn try_from(code: usize) -> Result<Self, Error> {
        match code {
            c if c == Action::Press as usize => Ok(Action::Press),
            c if c == Action::Release as usize => Ok(Action::Release),
            c if c == Action::Hold as usize => Ok(Action::Hold),
            c => bail!("unrecognized joystick action: {}", c),
        }
    }
}

/// An event issued by the `JoyStick`. Provides a UNIX-timestamp in the form of
/// `std::time::Duration`, the `Direction`, and the `Action` that were issued by the `JoyStick`.
#[derive(Debug)]
pub struct JoyStickEvent {
    timestamp: Duration,
    direction: Direction,
    action: Action,
}

impl JoyStickEvent {
    fn new(timestamp: Duration, direction: Direction, action: Action) -> Self {
        JoyStickEvent {
            timestamp,
            direction,
            action,
        }
    }
}

/// A type representing the Sense HAT joystick device.
#[derive(Debug)]
pub struct JoyStick {
    device: Device,
}

impl JoyStick {
    /// Open the joystick device by name in the `/dev/input/event*` path on the filesystem.
    pub fn open() -> Result<Self, Error> {
        for entry in glob("/dev/input/event*")? {
            match entry {
                Ok(path) => {
                    let device = Device::open(&path)?;
                    if device.name().as_bytes() == SENSE_HAT_EVDEV_NAME {
                        return Ok(JoyStick { device });
                    }
                }
                Err(e) => return Err(e.into()),
            }
        }
        bail!("No Joystick found")
    }

    /// Returns a result with a `Vec<JoyStickEvent>`. This function will
    /// block the current thread until events are issued by the `JoyStick` device.
    pub fn events(&mut self) -> io::Result<Vec<JoyStickEvent>> {
        let events: Vec<JoyStickEvent> = self.device
            .events_no_sync()
            .map_err(|e| io::Error::from(e))?
            .filter(|ev| ev._type == 1)
            .map(|ev| {
                let secs = ev.time.tv_sec as u64;
                let nsecs = ev.time.tv_usec as u32 * 1_000;
                let time = Duration::new(secs, nsecs);

                let direction = Direction::try_from(ev.code as usize).unwrap();
                let action = Action::try_from(ev.value as usize).unwrap();
                JoyStickEvent::new(time, direction, action)
            })
            .collect();
        Ok(events)
    }

    /// Returns the raw file-descriptor, `RawFd`, for the the Joystick.
    pub fn fd(&self) -> RawFd {
        self.device.fd()
    }
}