Moving Forward

Homepage of Andrew Robinson

Hacking the Keurig B40 Coffee Maker – Part 2 – Software

with 5 comments

Continuing our long journey, the next step in hacking the Keurig B40 is writing the software that will drive this secure coffee maker of the future. I’ll start with the Arduino code, and build our way up the stack.

Writing the Arduino code required overcoming a few annoyances. I needed to simulate a user-button press, which is an event that typically lasts for a few hundred milliseconds, and detect if the system was ready to brew, which isn’t indicated by a single LED, but rather the blinking of an LED. These are pretty simple challenges to overcome.

Sending Compact Status Data

The Arduino code uses a bit-struct to pack the values, which will be unpacked by Python on the host side of things. Here’s what that bit structure looks like:

union send_t {
  struct {
   byte  isOn : 1;
   byte  isEightOzButtonPressed : 1;
   byte  isTenOzButtonPressed : 1;
   byte  isWarmedUp : 1;
   byte  isNeedWater : 1;
   byte  isReadyToBrew : 1;
  };
  byte value;
};

This is one of my favorite bit tricks in C. Unions can be used all over the place to avoid unnecessary casting. In this case, I’ve use a bit-packed anonymous struct, which are really useful when writing types to set bit-masks, and unioned it with a byte, to allow us to get and set the entire raw value at once. While we could of used bit-shifting to get the same result, this has the advantage of being 100% readable.

Reading Coffee Maker Status

Next we create a function to handle reading the inputs, and outputting this data over the UART channel:

send_t toSend;

int outputCounter = 0;

// Counts toggles, > 3 = READY TO BREW.
int toggleCounter = 0;

// We could use outputCounter to keep track of this value,
// but breaking it out into a separate variable makes it
// a little clearer what exactly is going on.
int toggleTimeCounter = 0;

// The last value, used to determine toggle.
byte lastValue = 0;

void handleOutput() {
  // We want to catch these events no matter what, so we simply
  // AND them with every poll, that way if a button press was detected
  // at any point it's recorded.
  toSend.isEightOzButtonPressed =
    toSend.isEightOzButtonPressed | ((PIND >> 4) & 1);
  toSend.isTenOzButtonPressed =
    toSend.isTenOzButtonPressed | ((PIND >> 5) & 1);

  // We've detected a toggle.
  if(lastValue != ((PIND >> 6) & 1)) {
    // If it's been 500ms - 550ms it's probably the light blinking
    // to symbolize that brewing is ready.
    toggleCounter++;
    toggleTimeCounter = 0;
  }

  // If we've been waiting more than 600ms for a toggle let's give up.
  if(toggleTimeCounter > 600) {
     toggleCounter = 0;
  }

  lastValue = ((PIND >> 6) & 1);

  if(outputCounter % 500 == 0) {
    toSend.isOn = ((PIND >> 3) & 1) ^ 1;
    toSend.isNeedWater = ((PIND >> 7) & 1) ^ 1;
    if(toggleCounter > 3) {
      toSend.isReadyToBrew = 1;
    }

    toSend.isWarmedUp = (lastValue == 0) || toSend.isReadyToBrew || toggleCounter > 0;
    Serial.write(toSend.value);
    toSend.value = 0;
  }
  outputCounter++;
  toggleTimeCounter++;
}

I’ve gone with an event-loop style loop here, so everything happens on counter intervals. In this block we measure the time between transitions of the brew button, read the state of the buttons on the front to record any user actions, and finally read the other fairly static parameters and send them along the wire.

I read user input in a sticky manor, every 500ms a UART packet is generated and if the user pressed one of the brew buttons at any point in time it will record it for that frame.

In this code I don’t use the digitalRead and Write functions. I don’t feel that the thin wrappers the Arduino code base provides are really that useful. Having direct access to the ports on the AVR really isn’t a grand leap.

Handling Inbound Commands

Left to do is handle commands sent from the host pc to the Arduino. This is also a pretty simple process.

unsigned int commandCounter = 0;

void handleInput() {
  // Here we handle button presses-
  // We simulate a ~200ms press asyncronously
  // to simulate user input. 

  // If commandCounter == 0 we look for UART data,
  // else we block on this specific command and increment
  // up to 200 (~200ms)

  // If a lot of commands were sent at once this would
  // potentially overflow and drop some commands, but
  // I don't think that's a realistic situation.
  if(commandCounter > 0) {
    commandCounter++;
    if(commandCounter == 200) {
      // Set PORTB into high-z state, since the buttons
      // use an analog resistor network we don't want
      // stray current messing with the PIC on the coffee
      // maker
      DDRB = 0;
      // Disable internal pullup resistors
      PORTB = 0;
      // Reset commandCounter
      commandCounter = 0;
    }
  } else if(Serial.available() > 0) {
    switch(Serial.read()) {
      case 'p':
        // Press the power button (Pin 8)
        PORTB = (1<<0);
        DDRB = (1<<0);
        commandCounter = 1;
      break;
      case 't':
        // Press the 10oz brew button (Pin 9)
        PORTB = (1<<1);
        DDRB = (1<<1);
        commandCounter = 1;
      break;
      case 'e':
        // Press the 8ox brew button (Pin 10)
        PORTB = (1<<2);
        DDRB = (1<<2);
        commandCounter = 1;
      break;
    }
  }
}

We read bytes as they come down the wire, relying on the serial buffer to hold any pending button press commands while the current ones are executing. The code will toggle the button for approximately 200ms.

We’ve designated three commands, ‘p’ to power the unit on or off, ‘t’ to perform a 10oz brew, and ‘e’ to perform a 8oz brew. The commands are single ASCII characters so no delimiting or framing was required.

Of interest is the fact that we deliberately keep all the buttons in a high impedance state. Because the Keurig reads buttons using a resistive network, pushing or pulling stray current into it could easily cause the coffee maker to fail to register button presses. We do this by setting the pins to inputs when they are not being actively driven, and disabling the internal pull ups.

Finally we have the often-found Arduino sketch components, loop and main:

void setup() {
  Serial.begin(9600);
}

void loop() {
  handleInput();
  handleOutput();
  delay(1);
}

That covers the Arduino code, next we hook this up to a Python interface to handle reading status, writing commands, taking a photo of the user, and handling RFID events.

Reading data from the RFID Reader and Coffee Maker

Reading serial data in python is actually surprisingly easy, with the pySerial module it really is as simple as it can get. First we open up two serial ports simultaneously:

rfidSer = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
coffeeSer = serial.Serial('/dev/ttyUSB1', 9600, timeout=1)

Next we parse data from both the ports.

For the RFID Reader:

	if rfidSer.inWaiting() > 1:
		data = rfidSer.readline().strip()[1:-2]
		if data in authorizedUsers:
			handleUserAuth(authorizedUsers[data])
		else:
			handleAccessDenied()
		time.sleep(1)
		rfidSer.flushInput()

I wait a second and flush the buffer after reading the line. The reader uses ASCII data but puts some start text/stop text characters at the beginning and end that python doesn’t parse naturally. Just clearing the buffer is sufficient to get rid of them.

For reference the RFID reader is the ID-12 sold by Sparkfun, available here.

For the coffee maker

	if coffeeSer.inWaiting() > 0:
		# Read the new status, detect differences, and fire
		# events accordingly.
		incomingByte = ord(coffeeSer.read())
		newCoffeeMakerStatus = {
			'isOn' : ((incomingByte >> 0) & 1) == 1,
			'isEightOzButtonPressed' : ((incomingByte >> 1) & 1) == 1,
			'isTenOzButtonPressed' : ((incomingByte >> 2) & 1) == 1,
			'isWarmedUp' : ((incomingByte >> 3) & 1) == 1,
			'isNeedWater' : ((incomingByte >> 4) & 1) == 1,
			'isReadyToBrew' : ((incomingByte >> 5) & 1) == 1
		}

This simply looks like a reversal of the C++ Arduino code I wrote earlier. I use a Python dict to keep track of the coffee maker state, and then test for differences after receiving each byte.

Taking a Photo

Last, but certainly not least, I wanted the python script to take a photo of the coffee user. Luckily there’s a command line tool available. We simply take a photo, save it to a directory, and append it to a running HTML document.

def takePhoto():
	fileName = '-'.join((str(time.time()), currentUser))
	os.system('streamer -q -o cphotos/%s.jpeg &> /dev/null' % fileName)
	f = open('userListAppend.html', 'w')
	f.write("""<table><tr><td align="center"><img width="330" src='cphotos/""" + fileName + """.jpeg'></td></tr><tr><td><b>User: </b> """ + currentUser + """<br><b>Date:</b> """ + str(datetime.datetime.now()) + """</td></tr></table>""")
	f.close()
	os.system('cat userList.html >>userListAppend.html & cp userListAppend.html userList.html')

That’s pretty much it for the software section of this hack. There’s some glue code here and there I can make available upon request, but all the hard parts are laid out here.

In my implementation I went ahead and integrated our coffee maker with an existing system designed to track sensor events. The coffee maker posts to our event server, and the events are displayed on a large screen outside our lab.

Written by Andrew Robinson

December 31st, 2011 at 6:30 am

Posted in Uncategorized

5 Responses to 'Hacking the Keurig B40 Coffee Maker – Part 2 – Software'

Subscribe to comments with RSS or TrackBack to 'Hacking the Keurig B40 Coffee Maker – Part 2 – Software'.

  1. [...] giving [Andrew] control over the brewing process. He wired in an RFID reader from SparkFun, then got busy coding his security/inventory system. Now, when someone wants coffee, they merely need to swipe their [...]

  2. [...] giving [Andrew] control over the brewing process. He wired in an RFID reader from SparkFun, then got busy coding his security/inventory system. Now, when someone wants coffee, they merely need to swipe their [...]

  3. Throughout it, a software is really a computer program built to help people carry out a task. A credit card applicatoin hence differs from a good operating system (which usually works the computer), a computer program (which usually performs upkeep or perhaps all-purpose chores), along with a programming language (offers computer plans are created). With regards to the exercise which is why it was developed, an application can easily change textual content, numbers, graphics, or a combination of these components. Meaning of application A few program bundles offer significant computing energy through concentrating on a single task, such as word processing; others, called incorporated software, offer somewhat a lesser amount of energy yet consist of a number of applications.[1] User-written software tailors techniques to meet the user’s particular requirements. User-written software include spread sheet themes, application macros, scientific techniques, artwork as well as computer cartoon pieces of software. Rootkits generally change specific regions of memory the actual operating system (Operating system) operating to create the hijack dell’execution control. This forces the OS to provide incorrect latest latest shopping outcomes for the actual detection software program (computer virus, anti-rootkit). Consumers have already entered the actual Pin number conned after they understand that the keyboard is actually closed. Clients ignorant, or perhaps have no idea they can utilize the touchscreen with the ATM to complete the actual transaction, or perhaps grow to be stressed if the keyboard just isn’t working and react leaving alone Atm machine

    Patricia Peloso

    10 Feb 12 at 9:11 am

  4. @Patricia – were you high when you wrote that?

    Josh

    7 Apr 13 at 2:05 am

  5. Hello, i believe i found you actually seen our web-site thus i got here to go back the desire? . Now i’m trying to locating problems to improve my own web site! Perhaps it has the acceptable to utilize number of your own principles!

Leave a Reply