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.

Teacher Notes

Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.

Step 1: What You Need

  1. Tilt Hydrometer - available from
  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 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


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 and 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 using a text editor like nano or gedit. Execute the code with

sudo python 

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)
	return float(delta.days) + (float(delta.seconds) / 86400)#scan BLE advertisements until we see one matching our tilt uuid
def getdata():
		sock = bluez.hci_open_dev(dev_id)
		print "error accessing bluetooth device..."


	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(
				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': ""
	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 ='', 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

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


(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 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 module into the same directory as and execute program with:

sudo python

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.



  • Indoor Lighting Contest

    Indoor Lighting Contest
  • Make It Fly Challenge

    Make It Fly Challenge
  • Growing Beyond Earth Maker Contest

    Growing Beyond Earth Maker Contest

20 Discussions


2 years ago

Can't get this code to run in Python on my new Raspberry Pi3. Super new to python, so I'm not really sure what I'm doing wrong. I get the error


File "/home/pi/Tilt/", line 134

i =0


SyntaxError: inconsistent use of tabs and spaces in indentation

Any idea what I might be doing wrong?

2 replies

Reply 2 years ago

I think I fixed the spaces/indentations issue, but now I get
File "/home/pi/tilt/", line 151

print "-------------"


SyntaxError: Missing parentheses in call to 'print'


Reply 12 months ago

I imagine you are well sorted by now, but anyone comming accross this,

if you have python version 3 you will need to make a few changes!

either install 2.7 and use that to run it or update the script.


Tip 1 year ago on Step 4

Watch the titles of each of the columns in the spreadsheet. If they don't match the dictionary variables the data will show up as "undefined"


2 years ago

got this to read tilt info, but can't get it to post to correctly - can't seem to get the timestamp right. Any ideas on how to make it work for that site?


2 years ago

able to get this to read the tilt, but when it posts to brewstat it doesn't seem to get the timestamp right. Any ideas what might be going wrong?


Reply 2 years ago

Ok, so it became relevant again :) Are you using Python 3 or 2?


Reply 2 years ago

Sorry didn't see the other post - Python 2.7 iirc. I can check later today when I'm at home.


Reply 2 years ago

Thanks for your reply. That probably explains my problems. I'm running 3.4.


Reply 2 years ago

Nvm this did the trick for me (couldn't get the code to run with python 3) :)

sudo apt-get install bluetooth libbluetooth-dev

sudo python3 -m pip install pybluez


2 years ago

Any update on this? I see the same issue as Roland, always getting undefined for the timestamp.

Also, any idea if this is outputting the same data that Tilt does when sending to My thought is that if I substituted the Brewstat Cloud URL in place of the google sheet URL, it may pass the data to the website like the Tilt app does.

3 replies

Reply 2 years ago

Hi Patrick. Did you get the url ironed out? I have mine posting there and to Google Sheets.


Reply 2 years ago

What additional steps did you need to take to send to Brewstat? Was it just a matter of adding that URL?


Reply 2 years ago

Yep. At, after adding a Tilt Device ( you can copy the "Tiltcloud url" - ( into the code: r ='', data)

Full script is here:


2 years ago

Thanks for making this instructable!

It looks like V1.6 of the app & sheet have changed things a bit. I've gotten the app and sheet to talk, and even the python program can post data to the sheet (once I got it set up and posting in the app, but the time format (or some such) must be different, because it shows up on sheet1 as "undefined" rather than a timestamp. I'll try and dig as I can, but I'm pretty new to python :-)

2 replies

Reply 2 years ago

I believe I found the issue. In the code, the data is being written to the field 'Time' where in the google sheet it is 'Timepoint'. I changed the header in the google sheet to 'Time' and have been able to receive the proper data since then.


Reply 2 years ago

Hi Roland,
I've got a bit on at the moment. I'll try to have a look at this later on today.


2 years ago

Looks good :)