← Back to all posts

Got Milk

02 June 2014

Written by Russ Hill

You’re lazy, you don’t want to walk all the way to the kitchen to find there’s no milk, right? Well, with this Talis hardware hack, you don’t have to…

Hardware hack day

We last organised a hack day last year where the ideas submitted where predominantly related to software projects. This was awesome fun and we’ve published articles about our adventures before: Hackday: Never Lunch Solo, but this time we wanted to actually build physical things.

The thing about hacking with hardware - things you can hold and modify in some way - is that it’s more inclusive for other non-technical colleagues (which makes it fun for people who can code and those who can’t).

Got Milk

Talis staff from all over the company submitted ideas for the hardware hack day and you’ll find posts about those elsewhere on the engineering blog, but the project I worked on was called “Got Milk”.

The Problem

The idea was to solve a problem as old as time itself: laziness. Well, okay, not ALL laziness but more specifically the bit of it that involves making tea/coffee.

I think we’ve all been there: you’ve just lined up a load of cups, put in the tea-bag/coffee/sugar, added the hot water and then you go to open the fridge door and … BOOM, no milk.

tea ready

no milk

Sadness ensues, the sky goes grey, you cry uncontrollably (well, maybe just a couple of those things happen) and then you put on your coat and trudge to the shop to buy more flippin’ milk.

The Solution

We decided the solution was to put a sensor in the fridge that could tell how much milk was left and then broadcast this status to somewhere that people could see.

milk sensor

milk in place

The Team

We were a mixed bunch:

  • Me - Russ Hill (Developer) : AKA ‘The Bodger’
  • Zena (Customer Support Team) : AKA ‘Mrs Glue Gun’
  • Keji (Product Manager) : AKA ‘The Guvnor’
  • Arunn (Product Manager) : AKA ‘Captain Soldering Iron’

…and we had mixed abilities/experience when it came to messing with hardware or DIY, but we had enthusiasm (and a hot glue gun!).

Note a virtual high-five to our other helpful teammates:

  • Mark (Developer) : AKA ‘The snake-charmer’
  • Nad (Developer) : AKAThe Architect

The Hardware

Here’s a list of the hardware we used:

Bread board circuit

So, the Force sensitive resistor would be the thing that measures the pressure of a milk carton - it’s sensitive enough to measure minor variances in pressure too, though we ended up setting the software we wrote to monitor for “ranges” of values we received from it. The bread board, analog to digital converter, 10k resistor and jumper wire all formed part of the circuit with the force sensitive resistor. The screw terminal was used to connect the force sensitive resistor to some longer wires (soldering directly to it was deemed too dangerous to the future operation of the resistor!).

Note the analog to digital converter is required because the Pi can only measure digital values natively, unlike an Arduino which is happy playing around with analog signals. For more information on this, I found out an awful lot of what was needed for this project from here: Analogue Sensors on the Raspberry Pi.

You can just about see the bread board with the components arranged on it in this photo:

bread board

(don’t ask what the toilet paper was for, I can’t honestly remember!)

Tip keep a tidy workspace - unlike us ;-)

Here’s the circuit Zena created using the bread board and the other components:


Zena got creative with her wiring as the bread board we got only had one power input each side, not two as in the diagram, so she used one side of the board as +ve and the other as -ve … nice!

Note we didn’t actually directly mount the force sensitive resistor on the board as shown, we extended the wires connected to it in order to be able to place the milk sensing pad in the fridge and the circuit board and Pi underneath the fridge out of the way (and not too cold).

Sensor pad

The force sensitive resistor is only about 2in x 2in square, too small to act as an effective area to place a milk container on, so we opted to make a housing for this that would attempt to take the load across a larger area and apply it to the pad.

You can see how we started out with this idea below:

prototype pad

We got creative with a load of old Talis pens, taking the springs out, cutting them down to size and gluing them to a base - in this case, cardboard. You can see a sheet of plastic to the right of the picture: this was later to be used to replace the cardboard and make things more robust. We used another similar piece for the top - creating a “sandwich” with the springs and force sensitive resistor as the filling!

Note the plastic for the sensor sandwich was actually the plastic box that the electrical components we ordered came in - thanks to the online shop that we bought those from.

In the picture below you can see Arunn putting the finishing touches to the “final” plastic base by gluing the springs to that instead - Zena had already transferred the majority of the springs from the cardboard prototype by this time:

prototype pad

(note the plastic milk bottles we used for testing!)


Once the bread board and sensor where complete and connected up, we attached everything to the Pi as per the circuit diagram above, crossed our fingers and powered it up.

The Pi was running a fresh install of Raspbian on a 4GB SD card. After installing some software packages and writing a small Python script it was time to run the code. To our (or rather _my_ surprise) we got data from the sensor pad immediately!

The rest was just “tuning” - we placed various sized milk bottles with varying amounts of water (to simulate milk) on the pad and read the values from the Python script. We adjusted the script to look for specific ranges and then post a message into a HipChat room.


The full Python script is below - note we created a config file to allow us to tune things if we wanted (like the delay to wait) and to store the API tokens and so on:

# This script reads data from a
# MCP3008 ADC device using the SPI bus.
# Author : Russ Hill (based on code by Matt Hawkins)
# Date   : 28/05/2014
# Ref :
# http://www.raspberrypi-spy.co.uk/

import spidev
import time
import os, os.path
import requests
import ConfigParser
import urllib2
import subprocess

config = ConfigParser.ConfigParser()
config.read([os.path.expanduser("~/gotmilk/.gotmilk"), '/etc/gotmilk'])

AUTH_TOKEN = config.get('HipChat', 'token')
HIPCHAT_ROOM_ID = config.get('HipChat', 'roomid')
DELAY = config.getfloat('GotMilk', 'delay')

def internet_on():
        urllib2.urlopen('https://api.hipchat.com', timeout=1)
        return True
    except urllib2.URLError as err:
        return False

def get_ip():
    cmd = "ifconfig wlan0 | awk '/inet addr/ { print $2 } '"
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    return process.stdout.read().split(':')[1]

def send_message(message,color):
    # send a message via HipChat
    hipchat_url = "https://api.hipchat.com/v1/rooms/message?format=json&auth_token=" + AUTH_TOKEN

    payload = {

    r = requests.post(hipchat_url, data=payload)

def wait_for_access():
    while (internet_on() == False):
    ip_address = get_ip()
    send_message('Your friendly Talis Milkmaid is up and monitoring on '+ ip_address+' (checking the fridge every '+str(DELAY)+' seconds)','gray')


# Open SPI bus
spi = spidev.SpiDev()

# Function to read SPI data from MCP3008 chip
# Channel must be an integer 0-7
def ReadChannel(channel):
  adc = spi.xfer2([1,(8+channel)<<4,0])
  data = ((adc[1]&3) << 8) + adc[2]
  return data

# Function to convert data to voltage level,
# rounded to specified number of decimal places.
def ConvertVolts(data,places):
  volts = (data * 3.3) / float(1023)
  volts = round(volts,places)  
  return volts

# Define sensor channels
resistor_channel = 0

# Define previous resistor level
previous_resistor_level = 0
nothing_on_pad_count = 0
milk_gone_warning_shown = False
milk_low_warning_shown = False
milk_ok_warning_shown = False

while True:
    level_message = "Monitoring..."

    # Read the resistor data
    resistor_level = ReadChannel(resistor_channel)
    resistor_volts = ConvertVolts(resistor_level,2)

    if resistor_level != previous_resistor_level:
        if resistor_level > 900:
            # Nothing on the pad
            level_message = "Nothing on the pad"
            nothing_on_pad_count += 1
            if nothing_on_pad_count == 5:
                nothing_on_pad_count = 0
                level_message = "Milk has all gone (or been left out of the fridge!)"
                if milk_gone_warning_shown == False:
                    milk_gone_warning_shown = True
                    milk_ok_warning_shown = False
        elif 650 <= resistor_level <= 900:
            # Milk running low
            level_message = "Milk running low - please buy more"
            nothing_on_pad_count = 0
            if milk_low_warning_shown == False:
                milk_low_warning_shown = True
                milk_ok_warning_shown = False
        elif 400 <= resistor_level <= 650:
            # Milk is healthy
            level_message = "Milk level currently okay"

            # See if we previous mentioned that the milk was low, if so, message that the milk is now okay (if we didn't do that already)
            if (milk_gone_warning_shown == True or milk_low_warning_shown == True):
                if milk_ok_warning_shown == False:
                    milk_ok_warning_shown = True

            # Reset flags
            milk_low_warning_shown = False
            milk_gone_warning_shown = False
            nothing_on_pad_count = 0
            # Loads of milk
            level_message = "Milk is plentiful!"

            # See if we previous mentioned that the milk was low, if so, message that the milk is now okay (if we didn't do that already)
            if (milk_gone_warning_shown == True or milk_low_warning_shown == True):
                if milk_ok_warning_shown == False:
                    milk_ok_warning_shown = True

            # Reset flags
            milk_low_warning_shown = False
            milk_gone_warning_shown = False
            nothing_on_pad_count = 0

    # Print out results
    print "--------------------------------------------"  
    print("Pressure : {} ({}V)".format(resistor_level,resistor_volts))
    print level_message  

    previous_resistor_level = resistor_level

    # Wait before repeating loop

Note it was my first go with Python - you can tell can’t you?? Thanks to Mark for additional contributions…

See the Git repo for the code.

Finishing up

All that was left to do was install the sensor and tidy things away.

We made a box for the circuit board to prevent wires being knocked etc: circuit box

We then put everything in a box to keep it all safe: boxed up

…closed the lid: boxed up

and stuffed it under the cupboard out of the way: under the cupboard

Now we just keep an eye on HipChat and can see at any time - by scrolling back in the chat history - what the last status of the milk was.

Laziness as they say, is the mother of all invention - or something like that.


Comments on HN