Introduction: How to Control Beer Fermentation Temperature and Gravity From Your Smartphone

When beer is fermenting, you should monitor its gravity and temperature daily. It is easy to forget to do so, and impossible if you are away.

After some googling, I found several solutions for automated gravity monitoring ( one, two, three). One of them, with very clever concept, called Tilt. Tilt is floating in your beer and is measuring its own tilt angle. This angle depends on liquid’s density, and therefore can measure gravity of the fermenting beer.

Tilt comes with a mobile app, that connects to it and can post data to any web service. The problem is that you need to be not far from Tilt to be able to do so. There is also a Raspberry Pi program that works with Tilt.

Step 1: Getting Tilt Data in Python

I’m already using Raspberry Pi to monitor cellar temperature, and a cloud control panel service cloud4rpi.io. If Tilt can talk to Raspberry Pi, it should be possible connect cloud4rpi to it. Tilt is using a wireless protocol, so you’ll need Raspberry Pi with a wireless chip ( Rasbperry Pi 3 or Zero W).

Luckily, there is a GitHub repo for Tilt software with some samples. Looking at https://github.com/baronbrew/tilt-scan you can see that Tilt looks to others as BLE iBeacon, with “Color” coded in UUID, and temperature and gravity are in major and minor bytes.

Their sample code is for Node.js, and I have a Python control program based on cloud4rpi template https://github.com/cloud4rpi/cloud4rpi-raspberrypi-python/blob/master/control.py.

So I need to get Tilt data in Python. After some googling, I found https://github.com/switchdoclabs/iBeacon-Scanner- — Python iBeacon scanner. This is a program, not a library, so I modified it to return a dictionary instead of string. And I also wrote Tilt-specific module to get color, temperature and gravity of the first Tilt found (I have only one), and a simple test program to check if it can see my Tilt:

import time
import tilt

while True: res = tilt.getFirstTilt() print res time.sleep(2)

Run and check that it works. Now I can plug it to my control program. I already have a python program connected to cloud4rpi.io, but let me show how to do so from the scratch.

Step 2: Connecting Device to the Cloud

First, sign in to cloud4rpi.io, then create a new device.

You will be given a device token and installation instructions. For Raspberry Pi follow instructions here http://docs.cloud4rpi.io/start/rpi/ — make sure your system is up-to-date:

sudo apt update && sudo apt upgrade

Install prerequisites:

sudo apt install git python python-pip

Install cloud4rpi python packages:

sudo pip install cloud4rpi

then get a sample python app for Raspberry Pi (into control folder):

git clone https://github.com/cloud4rpi/cloud4rpi-raspberryp... control

cd control

modify control.py — specify your device token in the line

DEVICE_TOKEN = ‘__YOUR_DEVICE_TOKEN__’

Remove unnecessary entries from device variable declarations, leave only CPUTemp to test device connection:

# Put variable declarations here
variables = { 'CPU Temp': { 'type': 'numeric', 'bind': rpi.cpu_temp } }

Now do a test run:

sudo python control.py

If everything is ok, your device page will be immediately updated with diagnostic data.

Step 3: Sending Data to the Cloud

Now we need to modify control.py to read and report Tilt’s color, temperature and gravity. Result looks like this:

from os import uname
from socket import gethostname import sys import time import cloud4rpi import rpi import tilt

# Put your device token here. To get the token, # sign up at https://cloud4rpi.io and create a device. DEVICE_TOKEN = '__YOUR_DEVICE_TOKEN__'

# Constants DATA_SENDING_INTERVAL = 60 # secs DIAG_SENDING_INTERVAL = 600 # secs POLL_INTERVAL = 0.5 # 500 ms

beacon = {}

def F2C(degreesF): return (degreesF - 32) / 1.8

def getTemp(): return F2C(int(beacon['Temp'])) if beacon else None

def getGravity(): return beacon['Gravity'] if beacon else None

def main():

# Put variable declarations here variables = { 'Gravity': { 'type': 'numeric', 'bind': getGravity }, 'Beer Temp': { 'type': 'numeric', 'bind': getTemp } }

diagnostics = { 'CPU Temp': rpi.cpu_temp, 'IP Address': rpi.ip_address, 'Host': gethostname(), 'Operating System': " ".join(uname()) }

device = cloud4rpi.connect(DEVICE_TOKEN) device.declare(variables) device.declare_diag(diagnostics)

device.publish_config()

# Adds a 1 second delay to ensure device variables are created time.sleep(1)

try: data_timer = 0 diag_timer = 0 while True: if data_timer <= 0: global beacon beacon = tilt.getFirstTilt() device.publish_data() data_timer = DATA_SENDING_INTERVAL

if diag_timer <= 0: device.publish_diag() diag_timer = DIAG_SENDING_INTERVAL

time.sleep(POLL_INTERVAL) diag_timer -= POLL_INTERVAL data_timer -= POLL_INTERVAL

except KeyboardInterrupt: cloud4rpi.log.info('Keyboard interrupt received. Stopping...')

except Exception as e: error = cloud4rpi.get_error_message(e) cloud4rpi.log.error("ERROR! %s %s", error, sys.exc_info()[0])

finally: sys.exit(0)

if __name__ == '__main__': main()

Now run it manually to see if it works:

sudo python control.py

If everything is good, you will see your variables online.

To run control.py at system startup, install it as a service. Cloud4rpi provides an install script service_install.sh to do so. I’ve included it into my repo. To install control.py as a service, run

sudo bash service_install.sh control.py

Now you can start|stop|restart this service by running command

sudo systemctl start cloud4rpi.service

Service keeps its previous state on power up, so if it was running, it will be running after reboot or power loss.

Step 4: Final Result

This is it, now I have my Tilt parameters being sent to the cloud, so I can set up a nice cloud control panel for it. Go to https://cloud4rpi.io/control-panels and create new control panel, add widget and select <your device>/Gravity and Beer Temp as data source. Now I can monitor what’s going on even if I’m away from home.

The code I copied and wrote is available here: https://github.com/superroma/tilt-cloud4rpi. It is far from perfect, it works only with a single Tilt, it doesn’t care about “Color” of the device, whatever it means, and I’m not a Python guy at all, so fixes, suggestions or forks are welcome!