Skip to content

Commit 8ba5a7c

Browse files
authored
Merge pull request #9 from thejpster/more_imu_api
More IMU data plus basic text scrolling.
2 parents 875d504 + acebfbb commit 8ba5a7c

File tree

5 files changed

+262
-62
lines changed

5 files changed

+262
-62
lines changed

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sensehat"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
authors = ["Jonathan Pallant <[email protected]>"]
55
license-file = "LICENSE.md"
66
documentation = "https://docs.rs/crate/sensehat"
@@ -22,11 +22,12 @@ gcc = "0.3"
2222
[features]
2323
# The default set of optional packages. Most people will want to use these
2424
# packages, but they are strictly optional.
25-
default = ["rtimu"]
26-
rtimu = [ "libc" ]
25+
default = ["rtimu", "led-matrix"]
26+
# Extra packages required by these features.
27+
rtimu = ["libc"]
2728
led-matrix = ["sensehat-screen"]
2829

2930
[package.metadata.docs.rs]
30-
features = [ ]
31+
features = ["led-matrix"]
3132
all-features = false
3233
no-default-features = true

src/lib.rs

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,31 @@ pub struct Orientation {
5252
pub yaw: Angle,
5353
}
5454

55+
/// Represents a 3D vector
56+
#[derive(Debug, Copy, Clone)]
57+
pub struct Vector3D {
58+
pub x: f64,
59+
pub y: f64,
60+
pub z: f64,
61+
}
62+
63+
/// Represents an RGB colour
64+
#[cfg(feature = "led-matrix")]
65+
pub use sensehat_screen::color::PixelColor as Colour;
66+
67+
/// A collection of all the data from the IMU
68+
#[derive(Debug, Default)]
69+
struct ImuData {
70+
timestamp: u64,
71+
fusion_pose: Option<Orientation>,
72+
gyro: Option<Vector3D>,
73+
accel: Option<Vector3D>,
74+
compass: Option<Vector3D>,
75+
pressure: Option<f64>,
76+
temperature: Option<f64>,
77+
humidity: Option<f64>,
78+
}
79+
5580
/// Represents the Sense HAT itself
5681
pub struct SenseHat<'a> {
5782
/// LPS25H pressure sensor
@@ -61,7 +86,7 @@ pub struct SenseHat<'a> {
6186
/// LSM9DS1 IMU device
6287
accelerometer_chip: lsm9ds1::Lsm9ds1<'a>,
6388
/// Cached data
64-
orientation: Orientation,
89+
data: ImuData,
6590
}
6691

6792
/// Errors that this crate can return
@@ -71,6 +96,8 @@ pub enum SenseHatError {
7196
GenericError,
7297
I2CError(LinuxI2CError),
7398
LSM9DS1Error(lsm9ds1::Error),
99+
ScreenError,
100+
CharacterError(std::string::FromUtf16Error)
74101
}
75102

76103
/// A shortcut for Results that can return `T` or `SenseHatError`
@@ -86,11 +113,7 @@ impl<'a> SenseHat<'a> {
86113
humidity_chip: hts221::Hts221::new(LinuxI2CDevice::new("/dev/i2c-1", 0x5f)?)?,
87114
pressure_chip: lps25h::Lps25h::new(LinuxI2CDevice::new("/dev/i2c-1", 0x5c)?)?,
88115
accelerometer_chip: lsm9ds1::Lsm9ds1::new()?,
89-
orientation: Orientation {
90-
roll: Angle::from_degrees(0.0),
91-
pitch: Angle::from_degrees(0.0),
92-
yaw: Angle::from_degrees(0.0),
93-
},
116+
data: ImuData::default(),
94117
})
95118
}
96119

@@ -148,18 +171,24 @@ impl<'a> SenseHat<'a> {
148171
pub fn get_orientation(&mut self) -> SenseHatResult<Orientation> {
149172
self.accelerometer_chip.set_fusion();
150173
if self.accelerometer_chip.imu_read() {
151-
self.orientation = self.accelerometer_chip.get_imu_data()?;
174+
self.data = self.accelerometer_chip.get_imu_data()?;
175+
}
176+
match self.data.fusion_pose {
177+
Some(o) => Ok(o),
178+
None => Err(SenseHatError::NotReady)
152179
}
153-
Ok(self.orientation)
154180
}
155181

156182
/// Get the compass heading (ignoring gyro and magnetometer)
157183
pub fn get_compass(&mut self) -> SenseHatResult<Angle> {
158184
self.accelerometer_chip.set_compass_only();
159185
if self.accelerometer_chip.imu_read() {
160186
// Don't cache this data
161-
let orientation = self.accelerometer_chip.get_imu_data()?;
162-
Ok(orientation.yaw)
187+
let data = self.accelerometer_chip.get_imu_data()?;
188+
match data.fusion_pose {
189+
Some(o) => Ok(o.yaw),
190+
None => Err(SenseHatError::NotReady)
191+
}
163192
} else {
164193
Err(SenseHatError::NotReady)
165194
}
@@ -170,8 +199,11 @@ impl<'a> SenseHat<'a> {
170199
pub fn get_gyro(&mut self) -> SenseHatResult<Orientation> {
171200
self.accelerometer_chip.set_gyro_only();
172201
if self.accelerometer_chip.imu_read() {
173-
let orientation = self.accelerometer_chip.get_imu_data()?;
174-
Ok(orientation)
202+
let data = self.accelerometer_chip.get_imu_data()?;
203+
match data.fusion_pose {
204+
Some(o) => Ok(o),
205+
None => Err(SenseHatError::NotReady)
206+
}
175207
} else {
176208
Err(SenseHatError::NotReady)
177209
}
@@ -182,12 +214,61 @@ impl<'a> SenseHat<'a> {
182214
pub fn get_accel(&mut self) -> SenseHatResult<Orientation> {
183215
self.accelerometer_chip.set_accel_only();
184216
if self.accelerometer_chip.imu_read() {
185-
let orientation = self.accelerometer_chip.get_imu_data()?;
186-
Ok(orientation)
217+
let data = self.accelerometer_chip.get_imu_data()?;
218+
match data.fusion_pose {
219+
Some(o) => Ok(o),
220+
None => Err(SenseHatError::NotReady)
221+
}
187222
} else {
188223
Err(SenseHatError::NotReady)
189224
}
190225
}
226+
227+
/// Returns a vector representing the current acceleration in Gs.
228+
pub fn get_accel_raw(&mut self) -> SenseHatResult<Vector3D> {
229+
self.accelerometer_chip.set_accel_only();
230+
if self.accelerometer_chip.imu_read() {
231+
self.data = self.accelerometer_chip.get_imu_data()?;
232+
}
233+
match self.data.accel {
234+
Some(a) => Ok(a),
235+
None => Err(SenseHatError::NotReady)
236+
}
237+
}
238+
239+
/// Displays a scrolling message on the LED matrix.
240+
///
241+
/// Blocks until the entire message has scrolled past.
242+
#[cfg(feature = "led-matrix")]
243+
pub fn text(&mut self, message: &str, fg: Colour, bg: Colour) -> SenseHatResult<()> {
244+
// Connect to our LED Matrix screen.
245+
let mut screen = sensehat_screen::Screen::open("/dev/fb1").map_err(|_| SenseHatError::ScreenError)?;
246+
// Get the default `FontCollection`.
247+
let fonts = sensehat_screen::FontCollection::new();
248+
// Create a sanitized `FontString`.
249+
let sanitized = fonts.sanitize_str(message)?;
250+
// Render the `FontString` as a vector of pixel frames.
251+
let pixel_frames = sanitized.pixel_frames(fg, bg);
252+
// Create a `Scroll` from the pixel frame vector.
253+
let scroll = sensehat_screen::Scroll::new(&pixel_frames);
254+
// Consume the `FrameSequence` returned by the `left_to_right` method.
255+
scroll.right_to_left().for_each(|frame| {
256+
screen.write_frame(&frame.frame_line());
257+
::std::thread::sleep(::std::time::Duration::from_millis(100));
258+
});
259+
Ok(())
260+
}
261+
262+
/// Clears the LED matrix
263+
#[cfg(feature = "led-matrix")]
264+
pub fn clear(&mut self) -> SenseHatResult<()> {
265+
// Connect to our LED Matrix screen.
266+
let mut screen = sensehat_screen::Screen::open("/dev/fb1").map_err(|_| SenseHatError::ScreenError)?;
267+
// Send a blank image to clear the screen
268+
const OFF: [u8; 128] = [0x00; 128];
269+
screen.write_frame(&sensehat_screen::FrameLine::from_slice(&OFF));
270+
Ok(())
271+
}
191272
}
192273

193274
impl From<LinuxI2CError> for SenseHatError {
@@ -202,4 +283,11 @@ impl From<lsm9ds1::Error> for SenseHatError {
202283
}
203284
}
204285

286+
impl From<std::string::FromUtf16Error> for SenseHatError {
287+
fn from(err: std::string::FromUtf16Error) -> SenseHatError {
288+
SenseHatError::CharacterError(err)
289+
}
290+
}
291+
292+
205293
// End of file

src/lsm9ds1.rs

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! a C wrapper of the `RTIMULib` C++ API. We then call that unsafe C wrapper
1010
//! here, ensuring that any memory allocations were undone on drop.
1111
12-
use super::{Angle, Orientation};
12+
use super::{Angle, Orientation, Vector3D, ImuData};
1313
use libc;
1414

1515
enum RTIMULibContext {}
@@ -26,12 +26,33 @@ extern "C" {
2626
fn rtimulib_wrapper_imu_read(p_context: *mut RTIMULibContext) -> libc::c_int;
2727
fn rtimulib_wrapper_get_imu_data(
2828
p_context: *mut RTIMULibContext,
29-
orientation: *mut COrientation,
29+
orientation: *mut CAllData,
3030
) -> libc::c_int;
3131
}
3232

3333
#[repr(C)]
34-
struct COrientation {
34+
#[derive(Default)]
35+
struct CAllData {
36+
timestamp: libc::uint64_t,
37+
fusion_pose_valid: libc::c_int,
38+
fusion_pose: CVector3D,
39+
gyro_valid: libc::c_int,
40+
gyro: CVector3D,
41+
accel_valid: libc::c_int,
42+
accel: CVector3D,
43+
compass_valid: libc::c_int,
44+
compass: CVector3D,
45+
pressure_valid: libc::c_int,
46+
pressure: libc::c_double,
47+
temperature_valid: libc::c_int,
48+
temperature: libc::c_double,
49+
humidity_valid: libc::c_int,
50+
humidity: libc::c_double,
51+
}
52+
53+
#[repr(C)]
54+
#[derive(Default)]
55+
struct CVector3D {
3556
x: libc::c_double,
3657
y: libc::c_double,
3758
z: libc::c_double,
@@ -42,13 +63,13 @@ pub enum Error {
4263
RTIMULibError,
4364
}
4465

45-
pub struct Lsm9ds1<'a> {
66+
pub(crate) struct Lsm9ds1<'a> {
4667
rtimulib_ref: &'a mut RTIMULibContext,
4768
}
4869

4970
impl<'a> Lsm9ds1<'a> {
5071
/// Uses the `RTIMULib` library.
51-
pub fn new() -> Result<Lsm9ds1<'a>, Error> {
72+
pub(crate) fn new() -> Result<Lsm9ds1<'a>, Error> {
5273
let ctx_ref = unsafe {
5374
let ctx_p = rtimulib_wrapper_create();
5475
if ctx_p.is_null() {
@@ -64,47 +85,92 @@ impl<'a> Lsm9ds1<'a> {
6485

6586
/// Make the IMU do some work. When this function returns true, the IMU
6687
/// has data we can fetch with `get_imu_data()`.
67-
pub fn imu_read(&mut self) -> bool {
88+
pub(crate) fn imu_read(&mut self) -> bool {
6889
let result = unsafe { rtimulib_wrapper_imu_read(self.rtimulib_ref) };
6990
result != 0
7091
}
7192

72-
pub fn set_fusion(&mut self) {
93+
pub(crate) fn set_fusion(&mut self) {
7394
unsafe {
7495
rtimulib_set_sensors(self.rtimulib_ref, 1, 1, 1);
7596
}
7697
}
7798

78-
pub fn set_compass_only(&mut self) {
99+
pub(crate) fn set_compass_only(&mut self) {
79100
unsafe {
80101
rtimulib_set_sensors(self.rtimulib_ref, 0, 0, 1);
81102
}
82103
}
83104

84-
pub fn set_gyro_only(&mut self) {
105+
pub(crate) fn set_gyro_only(&mut self) {
85106
unsafe {
86107
rtimulib_set_sensors(self.rtimulib_ref, 1, 0, 0);
87108
}
88109
}
89110

90-
pub fn set_accel_only(&mut self) {
111+
pub(crate) fn set_accel_only(&mut self) {
91112
unsafe {
92113
rtimulib_set_sensors(self.rtimulib_ref, 0, 1, 0);
93114
}
94115
}
95116

96-
pub fn get_imu_data(&mut self) -> Result<Orientation, Error> {
97-
let mut temp = COrientation {
98-
x: 0.0,
99-
y: 0.0,
100-
z: 0.0,
101-
};
117+
pub(crate) fn get_imu_data(&mut self) -> Result<ImuData, Error> {
118+
let mut temp = CAllData::default();
102119
let result = unsafe { rtimulib_wrapper_get_imu_data(self.rtimulib_ref, &mut temp) };
103120
if result != 0 {
104-
Ok(Orientation {
105-
roll: Angle::from_radians(temp.x),
106-
pitch: Angle::from_radians(temp.y),
107-
yaw: Angle::from_radians(temp.z),
121+
Ok(ImuData {
122+
timestamp: temp.timestamp,
123+
fusion_pose: if temp.fusion_pose_valid != 0 {
124+
Some(Orientation {
125+
roll: Angle::from_radians(temp.fusion_pose.x),
126+
pitch: Angle::from_radians(temp.fusion_pose.y),
127+
yaw: Angle::from_radians(temp.fusion_pose.z),
128+
})
129+
} else {
130+
None
131+
},
132+
gyro: if temp.gyro_valid != 0 {
133+
Some(Vector3D {
134+
x: temp.gyro.x,
135+
y: temp.gyro.y,
136+
z: temp.gyro.z,
137+
})
138+
} else {
139+
None
140+
},
141+
accel: if temp.accel_valid != 0 {
142+
Some(Vector3D {
143+
x: temp.accel.x,
144+
y: temp.accel.y,
145+
z: temp.accel.z,
146+
})
147+
} else {
148+
None
149+
},
150+
compass: if temp.compass_valid != 0{
151+
Some(Vector3D {
152+
x: temp.compass.x,
153+
y: temp.compass.y,
154+
z: temp.compass.z,
155+
})
156+
} else {
157+
None
158+
},
159+
pressure: if temp.pressure_valid != 0 {
160+
Some(temp.pressure)
161+
} else {
162+
None
163+
},
164+
temperature: if temp.temperature_valid != 0 {
165+
Some(temp.temperature)
166+
} else {
167+
None
168+
},
169+
humidity: if temp.humidity_valid != 0 {
170+
Some(temp.humidity)
171+
} else {
172+
None
173+
},
108174
})
109175
} else {
110176
Err(Error::RTIMULibError)

0 commit comments

Comments
 (0)