Josh Adams is a developer and architect with over eleven years of professional experience building production-quality software and managing projects. Josh is isotope|eleven's lead architect, and is responsible for overseeing architectural decisions and translating customer requirements into working software. Josh graduated from the University of Alabama at Birmingham (UAB) with Bachelor of Science degrees in both Mathematics and Philosophy. He also occasionally provides Technical Review for Apress Publishing, specifically regarding Arduino microprocessors. When he's not working, Josh enjoys spending time with his family.
Linkdump image courtesy of the always-redditing David Chapman. Thanks David :)
I missed a week with the linkdumping, and for that I am truly ashamed. To make up for it, I shall provide two weeks worth of links today. Not terribly surprising I guess. To the links!
Sandi Metz's Magic Tricks of Testing offers guidelines for choosing what tests to write. Testing the interface is just a thing I love and everyone ought to be doing that's writing in a dynamic language imo.
To continue our fun with embedded hardware, check out the BeagleBone Black
xray-rails shows you what partial was rendered for a given portion of your page. It's very neat. It does more than I just said. Check it out if it sounds interesting.
Turbulenz open sourced their HTML5 game engine under the MIT license.
Alright, so there's this thing called the Crazyflie that is a tiny little nano quadcopter. I posted about it in a linkdump back in February, and subsequently purchased one for us isotopes to play with. Again, that was back in Februrary - a really long time ago.
Anyway, it finally came in today (I had pre-ordered it, it's available for general consumption now but they're already almost sold out again I believe). I thought I took some excellent pictures of it as we unboxed it, but I seem to have fat fingered the button on it a few times. Anyway, here are some photos and a video of us building and playing with it anyway:
Building Hypermedia APIs in Ruby/Rails - Reverb discusses using HAL, Sire, RABL, ROAR, and Garner. I have to give crazy love to hypermedia, only used it in detail once but the frontend code ended up delightful.
vim-detailed looks like a nice vim color theme. Makes ugly code ugly.
Linepipe is a gem for building data pipelines. I've built something similar (but not yet extracted the gem), and so I completely appreciate how awesome this one is :)
There was a great article suggesting the use of a shame.css file for placing quick hacks to your stylesheets, so that everyone understands that while you've used some hacks they are Not Kosher and so should not be replicated elsewhere.
So last week's post was similar to today. However, today I play with a bit more interesting RasPi libraries.
The big difference is that we're using software PWM via the wonderful pi-blaster library. It basically latches onto the DMA for the single PWM pin the RasPi offers, and gives you eight pins. It's derived from Richard Hirst's ServoBlaster but it allows a 100% duty cycle. We'll ultimately use this same library / concept to drive a remote controlled vehicle via drb with the Pi, but first things first!
Let's show some code.
The pi-blaster library expects that you send something like this to a device it creates for you:
echo "1=1" > /dev/pi-blaster
That would turn GPIO pin 1 on 100% of the time. The second argument is just a floating point number from 0 to 1.
I'm all about TDD, so I whipped up this spec for how my RGB LED object should work in Ruby:
describe RGBLed do
it "can be instantiated with three GPIO pin numbers" do
led = RGBLed.new(red_pin: 0, green_pin: 1, blue_pin: 2)
end
describe "setting color intensity" do
before do
@led = RGBLed.new(red_pin: 0, green_pin: 1, blue_pin: 2)
end
it "allows you to set red anywhere from 0 to 1" do
@led.red = 1
@led.red.must_equal 1
end
it "allows you to set green anywhere from 0 to 1" do
@led.green = 1
@led.green.must_equal 1
end
it "allows you to set blue anywhere from 0 to 1" do
@led.blue = 1
@led.blue.must_equal 1
end
it "does not allow you to set intensity above 1 or below 0" do
lambda{ @led.red = 2 }.must_raise OutOfRangeException
lambda{ @led.red = -1 }.must_raise OutOfRangeException
end
end
describe "outputting pi-blaster commands" do
before do
@led = RGBLed.new(red_pin: 0, green_pin: 1, blue_pin: 2)
@led.red = 0.5
@led.green = 0.75
@led.blue = 1
end
it "handles red" do
@led.pi_blast_command(:red).must_equal "echo 0=0.5 > /dev/pi-blaster"
end
it "handles green" do
@led.pi_blast_command(:green).must_equal "echo 1=0.75 > /dev/pi-blaster"
end
it "handles blue" do
@led.pi_blast_command(:blue).must_equal "echo 2=1 > /dev/pi-blaster"
end
end
end
Then it was just a matter of making the spec work, so this class satisfies that spec:
class OutOfRangeException < StandardError; end
class RGBLed
def initialize(options={})
@options = options
@pins = {
red: fetch_option(:red_pin),
green: fetch_option(:green_pin),
blue: fetch_option(:blue_pin)
}
@intensity = {
red: 0,
green: 0,
blue: 0
}
end
def red=(value)
set_intensity(:red, value)
end
def red
get_intensity(:red)
end
def green=(value)
set_intensity(:green, value)
end
def green
get_intensity(:green)
end
def blue=(value)
set_intensity(:blue, value)
end
def blue
get_intensity(:blue)
end
def pi_blast_command(channel)
"echo #{get_pin(channel)}=#{get_intensity(channel)} > /dev/pi-blaster"
end
def pi_blast
%w(red green blue).each do |c|
`#{pi_blast_command(c)}`
end
end
private
def fetch_option(channel)
@options.fetch(channel) { raise "Must specify `#{channel.to_s}` option." }
end
def set_intensity(channel, value)
validate_channel(channel)
validate_intensity(value)
@intensity[channel.to_sym] = value
end
def get_intensity(channel)
validate_channel(channel)
@intensity[channel.to_sym]
end
def get_pin(channel)
validate_channel(channel)
@pins[channel.to_sym]
end
def validate_channel(channel)
return true if valid_channels.include?(channel.to_s)
raise "Invalid attribute channel: #{channel}"
end
def validate_intensity(intensity)
return true if valid_intensities.cover?(intensity)
raise OutOfRangeException, "Invalid intensity: #{intensity}"
end
def valid_channels
%w(red green blue)
end
def valid_intensities
(0..1)
end
end
As I mentioned in the video, the RGB LED we had on hand actually took ground to the channels, and positive voltage to the common pin, which went against my assumptions. Basically, setting red to 1 would mean off, and 0 would mean on. OOP is super-sweet, so this class fixes that up for me:
class InverseRGBLed < RGBLed
def get_intensity(channel)
1-super
end
end
Finally (on the ruby side), we needed to expose him to the network. This script does that for me, as well as initializes him to blue.
require_relative '../lib/inverse_rgb_led.rb'
led = InverseRGBLed.new(red_pin: 0, green_pin: 1, blue_pin: 4)
led.pi_blast
led.red=0
led.green=0
led.blue=1
led.pi_blast
require 'drb/drb'
require 'pi_piper'
# The URI for the server to connect to
URI="druby://192.168.1.76:8787"
# The object that handles requests on the server
FRONT_OBJECT=led
$SAFE = 1 # disable eval() and friends
DRb.start_service(URI, FRONT_OBJECT)
# Wait for the drb server thread to finish before exiting.
DRb.thread.join
Then on the android side, this main activity will handle pretty much everything.
package com.isotope11.rgbledapp;
import java.util.ArrayList;
import java.util.List;
import org.jruby.embed.ScriptingContainer;
import android.app.Activity;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import com.commonsware.cwac.colormixer.ColorMixer;
import com.commonsware.cwac.colormixer.ColorMixer.OnColorChangedAndStoppedListener;
public class MainActivity extends Activity {
protected final String TAG = MainActivity.class.toString();
protected ScriptingContainer mRubyContainer;
protected String mDrbResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.setProperty("jruby.bytecode.version", "1.5");
mRubyContainer = new ScriptingContainer();
List<String> loadPaths = new ArrayList<String>();
loadPaths.add("jruby.home/lib/ruby/shared");
loadPaths.add("jruby.home/lib/ruby/1.8");
mRubyContainer.setLoadPaths(loadPaths);
final Activity lol = this;
ColorMixer colors = (ColorMixer) findViewById(R.id.mixer);
colors.setOnColorChangedAndStoppedListener(new OnColorChangedAndStoppedListener() {
@Override
public void onColorChange(int argb) {
int r = Color.red(argb);
int g = Color.green(argb);
int b = Color.blue(argb);
RGBLedSetter task = new RGBLedSetter();
task.setRed(r);
task.setGreen(g);
task.setBlue(b);
task.execute();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class RGBLedSetter extends AsyncTask<Object, Void, String> {
int mRed = 0;
int mGreen = 0;
int mBlue = 0;
@Override
protected String doInBackground(Object... arg0) {
String rubyDrbClient = "require 'drb/drb';SERVER_URI='druby://192.168.1.76:8787';DRb.start_service;led = DRbObject.new_with_uri(SERVER_URI);led.red=" + getMapped(mRed) + ";led.green=" + getMapped(mGreen) + ";led.blue=" + getMapped(mBlue) + ";led.pi_blast;";
mRubyContainer.runScriptlet(rubyDrbClient);
return "nope";
}
@Override
protected void onPostExecute(String result){
mDrbResult = result;
}
public void setBlue(int val){
mBlue = val;
}
public void setGreen(int val){
mGreen = val;
}
public void setRed(int val){
mRed = val;
}
public String getMapped(int val){
float newVal = Float.valueOf(val) / Float.valueOf(255);
Log.d(TAG, Float.toString(newVal));
return Float.toString(newVal);
}
}
}