The Pi was really just a deployment platform that would make it easy to get a low power Linux box running that I could easily configure. I wanted it to be headless, so I chose to go the Arch Linux route since that would allow me to have full control over the system and what parts were installed.
As I mentioned in the previous post, I'm using the serialport gem to do all the interaction with my Arduino. The gem takes care of all the business that is required in properly connecting to the device, and helps me reach my goal – to read and write data. The only awkward part of the library is establishing a connection to the device, after that it's just IO.
# Assuming bundler require 'serialport' baud = 9600 data_bits = 8 stop_bits = 1 arduino = '/dev/arduino.tty' # totally incorrect! serial = SerialPort.new(arduino, baud, data_bits, stop_bits) serial.puts "help" puts serial.gets # blocking call!!
The biggest lesson I learned was that gets calls to the device are blocking, which threw a wrench in my programs for a bit. One approach I took was to build up a message queue and just read from that, but for whatever reason I messed up my Mutex and the Thread responsible for stuffing message into the actual queue could never get priority. It also made things a bit complicated because of the reboot on connection issue I mentioned in the last post.
As for packages I really only needed a handful: Ruby 2.0, SQLite + SQLite development dependencies and Nginx. I took the approach I've seen in several other places and just have a thin server running my web app, and use Nginx as a reverse proxy. The solution works out fairly well and required little additional setup since I'm using plain old Nginx. I thought of using passenger, but lack of experience and no desire to compile things on the Pi made me change my mind.
I didn't want to have to rely on having my web server running in order to collect data from my Arduino, so I set up a Cron job that would run my data collection ruby script. The script did the fewest things possible in order to connect to the Arduino, grab some data and store it into my SQLite database. I'm using SQLite and there is a bit of a race-condition that if I'm writing Arduino data, I can cause my Sinatra app to fail since it cannot establish a connection to the database. For now, I'm not going to worry about it since my scripts only run every hour and I don't see the contention being an issue for myself.
I had my base system running and collecting data, but without any way of interacting or seeing the data it's just a bunch of numbers in a table. This is really where Sinatra came in. Its primary goal was to make it easy to do some basic CRUD such as: creating new brews, setting them as active, configuring the TTY to connect to and so on. Another thing I used it for was to provide a very simple JSON API for getting access to a brew's readings during fermentation.
The reason I needed a JSON endpoint is because I'm using the d3 graphing library for visualization and I based most of my charting code off of Mike Bostock's line chart example that was linked from the d3 gallery page. I added a few minor changes, such as some zones to easily see whether a beer is within its normal fermentation range or not. I'll admit that the charts are far from pretty, but they met my requirements and once I go back to it I'll look into making them a bit sexier. One really nice aspect of d3 is it's just building SVGs which can have CSS classes attached to them, so they are fully styleable.
A somewhat tricky part of the application was coming up with a way of making it easy to change what device I was connected to without having to change code. The one thing I hate always doing is having configuration in my code that makes it complicated to change things – especially when I'll be deploying to a different or unpredictable environment. I got around this by making a few slight changes to the system. I assumed that the user the application would be running as would have read and write access to the tty group on the system. With that assumption made, all I had to do was issue a directory listing query to /dev and then let the user choose which device to connect to.
Now, if we run a ls /dev on any UNIX-like platform, we're going to see a boatload of items, and this wasn't going to be acceptable. Based on a few observations I was able to slim down that list to a handful of devices. Because of prior knowledge I somewhat knew which device to connect to, but I'm forgetful so I had to add some tools to help with that. This was solved by adding a little "test connection" button in my Sinatra app that would try to open the connection and if it was successful know that I'm probably connected to the right device. I am using sort of bad habits since the exceptions kind of control my program flow, but I'm still a little green when it comes to the right way to do these kinds of things in UNIX.
Two weeks ago I had brewed my first batch of beer in the new apartment and the system was ready to roll! I was pretty excited to have my little hardware project actually be put to the test, and set everything up – while being extremely sanitary and ensuring everything was sprayed or soaked in Starsan! Though, I noticed after a day or two that my airlock wasn't bubbling, despite obviously active fermentation and got a little bit concerned. After looking around a bit I noticed that the wire from the temperature probe was preventing my bung from getting a perfect seal. Defeated, I removed the sensor and turned everything off before returning the bung back to it's home. About fourteen hours later, the airlock was bubbling like a champ. With that in mind, I'm planning on taking one of my rubber corks and drilling another hole in it that'll be large enough to get the probe through, but will keep my beers environment safe and tasty.