-
Notifications
You must be signed in to change notification settings - Fork 55
/
nunchuck.rs
226 lines (195 loc) · 7.3 KB
/
nunchuck.rs
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Copyright 2015, Paul Osborne <[email protected]>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Reads data from Wii Nunchuck
//
// NOTE: This code is provided as an example. Driver developers are encouraged
// to use the embedded-hal traits if possible rather than coupling directly
// to this library. An implementation of the embedded-hal I2C traits based
// on this library may be found in the embedded-hal-linux project.
extern crate docopt;
extern crate i2cdev;
#[cfg(any(target_os = "linux", target_os = "android"))]
use i2cdev::linux::*;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod nunchuck {
use std::error::Error;
use std::fmt;
use std::thread;
use std::time::Duration;
use i2cdev::core::I2CDevice;
pub const NUNCHUCK_SLAVE_ADDR: u16 = 0x52;
#[derive(Debug)]
pub enum NunchuckError<E> {
Error(E),
ParseError,
}
impl<E: Error> fmt::Display for NunchuckError<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
NunchuckError::Error(ref e) => fmt::Display::fmt(e, f),
NunchuckError::ParseError => write!(f, "Could not parse data"),
}
}
}
impl<E: Error> Error for NunchuckError<E> {
fn cause(&self) -> Option<&dyn Error> {
match *self {
NunchuckError::Error(ref e) => Some(e),
NunchuckError::ParseError => None,
}
}
}
// TODO: Move Nunchuck code out to be an actual sensor and add tests
#[derive(Debug)]
pub struct NunchuckReading {
pub joystick_x: u8,
pub joystick_y: u8,
pub accel_x: u16, // 10-bit
pub accel_y: u16, // 10-bit
pub accel_z: u16, // 10-bit
pub c_button_pressed: bool,
pub z_button_pressed: bool,
}
impl NunchuckReading {
pub fn from_data(data: &[u8]) -> Option<NunchuckReading> {
if data.len() < 6 {
None
} else {
Some(NunchuckReading {
joystick_x: data[0],
joystick_y: data[1],
accel_x: (u16::from(data[2]) << 2) | ((u16::from(data[5]) >> 6) & 0b11),
accel_y: (u16::from(data[3]) << 2) | ((u16::from(data[5]) >> 4) & 0b11),
accel_z: (u16::from(data[4]) << 2) | ((u16::from(data[5]) >> 2) & 0b11),
c_button_pressed: (data[5] & 0b10) == 0,
z_button_pressed: (data[5] & 0b01) == 0,
})
}
}
}
pub struct Nunchuck<T: I2CDevice> {
i2cdev: T,
}
impl<T> Nunchuck<T>
where
T: I2CDevice,
{
/// Create a new Wii Nunchuck
///
/// This method will open the provide i2c device file and will
/// send the required init sequence in order to read data in
/// the future.
pub fn new(i2cdev: T) -> Result<Nunchuck<T>, T::Error> {
let mut nunchuck = Nunchuck { i2cdev };
nunchuck.init()?;
Ok(nunchuck)
}
#[cfg(test)]
pub fn get_i2cdev(&mut self) -> &mut T {
&mut self.i2cdev
}
/// Send the init sequence to the Wii Nunchuck
pub fn init(&mut self) -> Result<(), T::Error> {
// These registers must be written; the documentation is a bit
// lacking but it appears this is some kind of handshake to
// perform unencrypted data tranfers
self.i2cdev.smbus_write_byte_data(0xF0, 0x55)?;
self.i2cdev.smbus_write_byte_data(0xFB, 0x00)
}
pub fn read(&mut self) -> Result<NunchuckReading, NunchuckError<T::Error>> {
let mut buf: [u8; 6] = [0; 6];
// tell the nunchuck to prepare a sample
self.i2cdev
.smbus_write_byte(0x00)
.map_err(NunchuckError::Error)?;
// now, read it!
thread::sleep(Duration::from_millis(10));
self.i2cdev.read(&mut buf).map_err(NunchuckError::Error)?;
NunchuckReading::from_data(&buf).ok_or(NunchuckError::ParseError)
}
}
#[cfg(test)]
mod test {
use super::*;
use i2cdev::core::I2CDevice;
use i2cdev::mock::MockI2CDevice;
#[test]
fn test_intialization() {
// write out some "bad" values to start out with so we know the
// write happens
let mut i2cdev = MockI2CDevice::new();
i2cdev.smbus_write_byte_data(0xF0, 0xFF).unwrap();
i2cdev.smbus_write_byte_data(0xFB, 0xFF).unwrap();
// these values must be written out for things to work
let mut dev = Nunchuck::new(i2cdev).unwrap();
assert_eq!(dev.get_i2cdev().smbus_read_byte_data(0xF0).unwrap(), 0x55);
assert_eq!(dev.get_i2cdev().smbus_read_byte_data(0xFB).unwrap(), 0x00);
}
#[test]
fn test_read_zeroed_out() {
let mut dev = Nunchuck::new(MockI2CDevice::new()).unwrap();
let reading = dev.read().unwrap();
assert_eq!(reading.joystick_x, 0);
assert_eq!(reading.joystick_y, 0);
assert_eq!(reading.accel_x, 0);
assert_eq!(reading.accel_y, 0);
assert_eq!(reading.accel_z, 0);
assert_eq!(reading.c_button_pressed, true);
assert_eq!(reading.z_button_pressed, true);
}
#[test]
fn test_read_sample_data() {
let mut i2cdev = MockI2CDevice::new();
i2cdev.write(&[0, 127, 128, 191, 129, 144, 71]).unwrap();
let mut dev = Nunchuck::new(i2cdev).unwrap();
let reading = dev.read().unwrap();
assert_eq!(reading.joystick_x, 127);
assert_eq!(reading.joystick_y, 128);
assert_eq!(reading.accel_x, 765);
assert_eq!(reading.accel_y, 516);
assert_eq!(reading.accel_z, 577);
assert_eq!(reading.c_button_pressed, false);
assert_eq!(reading.z_button_pressed, false);
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
use nunchuck::*;
use docopt::Docopt;
use std::env::args;
const USAGE: &str = "
Reading Wii Nunchuck data via Linux i2cdev.
Usage:
nunchuck <device>
nunchuck (-h | --help)
nunchuck --version
Options:
-h --help Show this help text.
--version Show version.
";
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn main() {}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn main() {
let args = Docopt::new(USAGE)
.and_then(|d| d.argv(args()).parse())
.unwrap_or_else(|e| e.exit());
let device = args.get_str("<device>");
let i2cdev = LinuxI2CDevice::new(device, NUNCHUCK_SLAVE_ADDR).unwrap();
match Nunchuck::new(i2cdev) {
Err(err) => {
println!("Unable to open {:?}, {:?}", device, err);
}
Ok(mut nunchuck) => loop {
match nunchuck.read() {
Ok(reading) => println!("{:?}", reading),
Err(err) => println!("Error: {:?}", err),
};
},
}
}