Every Apple Macbook and Macbook Pro and some of the last Powerbooks have shipped with something called the “Sudden Motion Sensor” which is basically a chip that detects when your laptop is about to crash, literally: It’s a chip that can sense sudden changes in orientation or velocity and then trigger the heads on the hard disk to “park” (move to a safe area) as rapidly as possible to prevent then from crashing in to the disk surface and destroying data — or the drive.

In basic terms it means when your laptop falls off your desk, this chip ensures the data on the disk is preserved even though the rest of the machine may be a total write-off.

The chip itself is a gyroscope+accelerometer package that can do a lot more than just see when you’re about to have a large repair bill — you can tap in to it to see how your laptop is oriented in space and any motion it is currently undergoing.

TEACHING YOUR CODE WHICH WAY IS UP

Formerly, the easiest way to tap in to the feed from the SMS hardware was to read the output of a command-line program called “AMSTracker“. It’s still a great program but using it (reading it’s output like a filehandle) is very un-Ruby-like.

The Rubyista option is my new gem sudden_motion_sensor, available now on rubygems.org. It packages some Objective-C code from Fazibear into a ruby gem you can use with minimal effort. Simply:

sudo gem install sudden_motion_sensor
..to start using it.

It reads from the SMS and returns the X and Y orientation of the computer along with acceleration (gravity actually) as a vertical axis Z. That means you you can tell the angle your Macbook is presently at and how fast it’s moving.

So when I use the demo code:

require 'rubygems'
require 'sudden_motion_sensor'

loop do
x,y,z = SuddenMotionSensor.values
puts "X: #{x}, Y: #{y}, Z: #{z}"
sleep 0.1
end

..I get back three signed integers:

* X: -255 to +255 as the left-to-right angle.
* Y: -255 to +255 as the front-to-back angle.
* Z: current gravity along the vertical (90 degree X) axis

The values for X and Y read “0″ when they are flat across that plane, be it right-size-up or upside-down and count up to 255 at 90-degrees on one side and -255 on the other.

As for Z, on my machine it seems to sit at around 265 when stationary, but I can make it go as high as 400 or as low as 100 if I move the laptop up and down quickly.

WHAT DO I WITH IT?

Lots of things! The canonical example is the great “smackbook” toy or tools like Seismac .

For the rubyist, you can have all kinds of fun. A basic toy is this:

#!/usr/bin/env ruby

require 'rubygems'
require 'sudden_motion_sensor'

x_threshold = 10

px = 0; py = 0; pz = 0
loop do
x,y,z = SuddenMotionSensor.values

if (x.to_i > (px.to_i + x_threshold.to_i).to_i)
system("say Stop it. &")
end

px = x; py = y; pz = z
sleep(0.1)
end

..which, when the SMS sensor value of X changes more than x_threshold in a single sample causes the program to tell you to knock it off because you’re obviously hitting your poor laptop!

But we can be more creative than that.

Did you have an Etch-a-Sketch(TM) when you were a kid? Neither did I, but I was envious of everyone who did. And I’m still bitter about it, so here is some code that makes up for my years of terrible childhood repression.

This code taps in to the sensor and draws lines on the screen based on the X/Y Tilt of the laptop and the colour is based on the Z axis. It uses the ‘Gosu’ game engine for screen drawing. Install it with

sudo gem install gosu

#!/usr/bin/env ruby
require 'rubygems'
require 'sudden_motion_sensor'
require 'gosu'
include Gosu

class GameWindow < Gosu::Window
def initialize
@window_x = 1024; @window_y = 768
@x_scale = ((@window_x/254)/2).ceil; @y_scale = ((@window_y/254)/2).ceil

@draw_dots=true
@dotsize = 2
@draw_lines=true

super(@window_x, @window_y, false)
self.caption = "Sketch-n-etch"

@center_x = (@window_x/2).ceil
@center_y = (@window_y/2).ceil

@positions = []
end

def update
x,y,z = SuddenMotionSensor.values
@cursor_x = @center_x+(x*@x_scale)
@cursor_y = @center_y-(y*@y_scale)
@cursor_z = z

@positions.shift if @positions.size > 100
@positions << {:x => @cursor_x, :y => @cursor_y, :color => Color.new(0xFF0088FF + rand(z.abs ** 2))}
end

def draw
@positions.each_with_index do |p, i|
if @draw_dots
self.draw_line(p[:x]-@dotsize, p[:y]-@dotsize, p[:color], p[:x]-@dotsize, p[:y]+@dotsize, p[:color])
self.draw_line(p[:x]-@dotsize, p[:y]+@dotsize, p[:color], p[:x]+@dotsize, p[:y]+@dotsize, p[:color])
self.draw_line(p[:x]+@dotsize, p[:y]+@dotsize, p[:color], p[:x]+@dotsize, p[:y]-@dotsize, p[:color])
self.draw_line(p[:x]+@dotsize, p[:y]-@dotsize, p[:color], p[:x]-@dotsize, p[:y]-@dotsize, p[:color])
end
if @draw_lines
self.draw_line(p[:x], p[:y], p[:color], @positions[i-1][:x], @positions[i-1][:y], p[:color]) unless i==0
end
end
end

def button_down(id)
if id == Gosu::Button::KbEscape
close
end
end
end

window = GameWindow.new
window.show

Screenshot:

That’s basically all there is to tell.

  • http://soleone.tumblr.com Soleone

    Cool, that looks very intriguing, I had no clue my normal MacBook has an accelerometer.
    But somehow I can’t install the gem here (running Snow Leopard) under Ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0], I always get:
    “ERROR: could not find gem sudden_motion_sensor locally or in a repository”. Any idea what the problem might be?

    • xunker

      Make sure you are running the newest version of rubygems, since this stored at rubygems.irg and wasn’t that in the standard repository list until recently.

      Also make sure http://gemcutter.org/ or http://rubyforge.org are in your sources list (gem sources). If not, add it with “sudo gem sources -a http://gemcutter.org“.

      If this gem works for you, or doesn’t, I’d like to hear about it, I have not yet tried it on Mac OS 10.6, only 10.5.

  • http://soleone.tumblr.com Soleone

    I’m not sure what was up, but it’s working fine now. I suspect a problem with RVM.
    Really cool, now I need to come up with a good use case… :)