Introduction: Reading a Tilt Hydrometer With a Raspberry Pi

The Tilt Hydrometer is a great little gadget for home brewers that allows the automated monitoring of the specific gravity of a brew as it ferments out. The only downside is that a late model Android or IOS device is tied up nearby the brew during the entire fermentation. I even went and bought a cheap tablet hoping to use that only to find that it would not consistantly connect to the tilt (later found that this model of tablet was not on the supported devices list).

I decided to research using a Raspberry Pi to collect the data. I found one solution which involved setting up the RPi as a complete LAMP (Linux, Apache, MySQL, PHP) server and keeping all the data within the RPi. I preferred the solution that the makers of the Tilt used, updating a Google Sheet. That way there was an easy way to remotely check the brew's progress without having to muck around opening ports on a local gateway, etc.

The Tilt Hydrometer uses an iBeacon to advertise the SG value and the temperature. By using the BLE scanner from Switchdoc Labs I was able to fudge out some workable Python code to do everything I wanted. It may not be very elegant, but it works.

Step 1: What You Need

  1. Tilt Hydrometer - available from https://tilthydrometer.com/
  2. Rapberry Pi 3 - available from RS Components.
    1. The RPi3 has a bluetooth radio built in. You may be able to get this to work with earlier versions of the RPi and a bluetooth dongle but I haven't tried it.
  3. A microSD card for the RPi
  4. A monitor, keyboard and mouse (just for the initial setup of the RPi)
  5. A Google account.
  6. A small display like this one. (optional)
  7. 10 core ribbon cable, 40 pin and 10 pin header sockets, etc for connecting the display to the RPi GPIO header(optional)

Step 2: Set Up the Raspberry Pi OS

I won't go into the specifics of setting up a raspberry pi SD card as there are already plenty of howtos on https://www.raspberrypi.org. Download the latest version of Raspbian and use a package like Win32 Disk Imager(Windows) or dd(linux) to image your SD card. Plug it in, attach a monitor, keyboard and mouse to the RPi and power it up. Once it has booted up to the desktop, connect it to your local network using WiFi or by directly plugging it into your network hub.

Open a terminal shell and update the software repository information on the pi by using the following command:

sudo apt-get update -y

Then upgrade all the software on the pi to the latest version with

sudo apt-get upgrade -y

and

sudo apt-get dist-upgrade -y

Finally, make sure all the required bluetooth and python software is installed

sudo apt-get install bluez python-bluez python-requests python-pygame python-rpi.gpio -y

Step 3: Check That the Pi Can See the Tilt IBeacon

Drop your Tilt Hydrometer in a glass of water or just lean it on an angle to turn it on and issue the following command in a terminal.

sudo hcitool lescan

When you see the tilt address and name pop up hit ctrl + c to stop the scan. The 12 digit hexadecimal number is the bluetooth address of the tilt. Its like a MAC address for network devices but for bluetooth.

If it does not appear, something's not right. Check your Tilt's battery status and that it is actually turning on (LED should flash when tilt is moved from vertical position to angled position). The Tilt needs 20-30 seconds when moved to vertical position before it goes into sleep mode. Try connecting to the Tilt with an Android or IOS device. If the Tilt is OK, there must be something wrong with the RPi. Double check all the required bluetooth software is installed.

Step 4: Set Up the Python Code

This is the first time I've done anything with Python beyond running the odd script someone else has developed. It's a great high level interpreter language that seems pretty intuitive. Not saying I'm immediately some uber hacker, but I can get it to do what I want it to do relatively easily. Hopefully noobs will find my code pretty easy to navigate and the guys who know what they're doing won't be laughing too hard.

For this code to work you will need to have a Google Sheet setup and deployed as a web app as described by the guys at Tilt Hydrometer in this blog post. Copy blescan.py and tiltV1.py to the same directory on the RPi (I went with /home/pi/tilt) navigate to that directory and copy the url of the sheet web deployed app into the tiltV1.py using a text editor like nano or gedit. Execute the code with

sudo python tiltV1.py 

The Code:

import blescan
import sys
import requests
import datetime
import time
import bluetooth._bluetooth as bluez
import pygame
import os

#Assign uuid's of various colour tilt hydrometers. BLE devices like the tilt work primarily using advertisements. 
#The first section of any advertisement is the universally unique identifier. Tilt uses a particular identifier based on the colour of the device
red    	= 'a495bb10c5b14b44b5121370f02d74de'
green  	= 'a495bb20c5b14b44b5121370f02d74de'
black  	= 'a495bb30c5b14b44b5121370f02d74de'
purple 	= 'a495bb40c5b14b44b5121370f02d74de'
orange 	= 'a495bb50c5b14b44b5121370f02d74de'
blue   	= 'a495bb60c5b14b44b5121370f02d74de'
yellow 	= 'a495bb70c5b14b44b5121370f02d74de'
pink   	= 'a495bb80c5b14b44b5121370f02d74de'</p><p>#The default device for bluetooth scan. If you're using a bluetooth dongle you may have to change this.
dev_id = 0

#function to calculate the number of days since epoch (used by google sheets)
#In python time.time() gives number of seconds since epoch (Jan 1 1970).
#Google Sheets datetime as a number is the number of days since the epoch except their epoch date is Jan 1 1900
def sheetsDate(date1):
	temp = datetime.datetime(1899, 12, 30)
	delta=date1-temp
	return float(delta.days) + (float(delta.seconds) / 86400)#scan BLE advertisements until we see one matching our tilt uuid
def getdata():
	try:
		sock = bluez.hci_open_dev(dev_id)
	except:
		print "error accessing bluetooth device..."
		sys.exit(1)

	blescan.hci_le_set_scan_parameters(sock)
	blescan.hci_enable_le_scan(sock)

	gotData = 0
	while (gotData == 0):

		returnedList = blescan.parse_events(sock, 10)
		for beacon in returnedList: #returnedList is a list datatype of string datatypes seperated by commas (,)
			output = beacon.split(',') #split the list into individual strings in an array
			if output[1] == black: #Change this to the colour of you tilt
				tempf = float(output[2]) #convert the string for the temperature to a float type</p><p>				gotData = 1</p><p>				tiltTime = sheetsDate(datetime.datetime.now())
				tiltSG = float(output[3])/1000
				tiltTemp = tempf
				tiltColour = 'BLACK'
				tiltBeer = 'test' #Change to an identifier of a particular brew</p><p>	#assign values to a dictionary variable for the http POST to google sheet
	data= 	{
			'Time': tiltTime,
			'SG': tiltSG,
			'Temp': tiltTemp,
			'Color': tiltColour,
			'Beer': tiltBeer,
			'Comment': ""
			}
	blescan.hci_disable_le_scan(sock)
	return data
def main():

	global screen
	updateSecs = 600 #time in seconds between updating the google sheet
	
	timestamp = time.time() #Set time for beginning of loop
	updateTime = timestamp + updateSecs #Set the time for the next update to google sheets

	while True:
		data = getdata()
		
		if time.time() > updateTime: #if we've reached the update time then do a POST to the google sheet and reset the updateTime
			r = requests.post('https://the.address.of.your.google.sheets.script/exec', data) #Change this to the address of your google sheet script
			#print r.text
			updateTime = updateTime + updateSecs


if __name__ == "__main__": #dont run this as a module
	main()

I suggest you reduce the time of the 'updateSecs' variable when testing just so you're not waiting around for ages for the sheet to update. Try putting the odd print(variable) in the code too so you can see whats happening

Step 5: Add a Local Display

"That's great", I hear you say, "But it's a fat lot of good if I haven't got a network to connect to!". Well yes that is true. It would be a good reason to go with the LAMP server arrangement I mentioned in the introduction. Another possibility is adding a little TFT display to let you know what is going on. I went with a Freetronics 128x128 pixel OLED display that I got from my local Jaycar for about $AU20. I followed the instructions here to connect it to the GPIO header on the Raspberry Pi and then followed the instructions for getting the fbtft module developed by Notro installed here.

In my case I had to add a file called fbtft.conf to the directory /etc/modules-load.d/ :

sudo nano /etc/modules-load.d/fbtft.conf

and add the following lines to it

spi-bcm2835
fbtft_device

(press CTRL+x in nano to save and exit)

And then add a file called fbtft.conf to /etc/modprobe.d/ :

sudo nano /etc/modprobe.d/fbtft.conf

and add the following lines to it

options fbtft_device name=freetronicsoled128

Finally most importantly in Main Menu -> Preferences -> Raspberry Pi Configuration on the 'Interfaces' tab, enable the SPI interface. Reboot and if everything has worked there will be a second framebuffer, fb1. Check by issueing the following command:

ls /dev | grep fb

The response should be:

fb0
fb1

fb0 is the HDMI output of the pi.

These little screens are quite susceptible to burn in so I wanted to build in a function to put the screen to sleep and have a button to wake it up. This display came with two buttons on the 'wings' of the display. I wired them up in a pull down configuration to GPIO17 and GPIO27. The circuit diagram is above.

Step 6: Develop a Display Using Pygame

Once the hardware is in place and the OS is recognising the display we can modify the Python code to incorporate the screen using Pygame. In this case I'm modifying the SDL Environment variables to write directly to the framebuffer of the freetronics display. Hopefully the line comments in the code explain what is going on.

Same as last time, copy the tiltV2.py module into the same directory as blescan.py and execute program with:

sudo python tiltV2.py

A couple of things to watch out for. If you haven't wired in a couple of buttons there is no way of exiting the program short of pulling the plug on the RPi or ssh-ing into a separate console and killing the process or rebooting. By the same token, there would be no way to wake up the screen once it goes to sleep. You could use Pygame to watch for a keyboard event. I'm sure the code is out there.

Anywho, that's it. Perhaps not a super elegant solution and I've yet to code in an allowance for calibration points but it works and is a pretty cheap alternative to a new android device.