Coffee Break Anybody? With Maker Kit + Publish

Getting Started

Let’s say you have one of these and one of these (the Spark Maker Kit):

The maker kit includes one of these:

What is that, you ask? That is a force sensitive resistor which is a device that translates pressure into resistance. The more pressure you apply to the round part, the lower the resistance of the sensor. We are going to use it to sense when there is coffee in the carafe and when it is empty. It is not a particularly good sensor since the change in pressure on the surface is not linearly proportional to the change in resistance, so it would be hard to build an accurate scale using this sensor, but we can still use it for this application.

Sensor Surgery

We want the sensor to still be bread-board friendly but we could use some extra length on the leads. So we find one of the long jumper wires in the maker kit and cut it half–presto! Now we have two wires to solder the sensor. I also used some heat-shrink tubing to cover the exposed connections and make it look nice and neat. A key step here is to “tin” the connections before you bring them together. That means adding a little bit of solder the each wire first, letting it cool for a few seconds, and only then making the connection and soldering it. The little third-hand holder helps a lot too.

Build the Board

So building our board will be pretty easy, we have the sensor and now we need just one more component–a fixed resistor, 1k ohm in value from the maker kit. That will have color bands of brown-black-red-gold–you can use an ohm meter if you are not sure.

Here’s our board:

Power comes in on the left from an old cell phone +5V DC wall-wart type supply. I cut the old connector off and soldered some small pins to them, wrapping them heat-shrink tubing in red and black for the positive and negative connections. We put the positive power connection on the top red rail across the bread-board. We put the negative or ground connection on the blue rail across the bottom of the bread-board.

Now we route power to the Spark Core by using a red jumper from the positive supply down to the VIN pin on the core. Be careful! You don’t want to connect this 5V supply to any of the 3.3V connections, only connect it to the VIN pin on the core. The VIN pin goes through the voltage regulator to make 3.3V for the core. Add a black jumper wire from the GND pin next door to the bottom blue power supply rail for the ground connection.

Now we take our 1k ohm resistor and connect one end to the 3.3V* pin on the core. This is a filtered 3.3V supply that is used for the ADC reference voltage and is perfect for our sensor. The other end of the 1k resistor goes to an empty column (in this case column 5) on the board.

Now we add the sensor with soldered on leads, one lead goes to column 5 and connects to the resistor, while the other lead goes to the blue ground rail. We add a white jumper from the junction at column 5 to core pin A0, our analog input.

So now we have a voltage divider connected from 3v3* through a 1k ohm and then through the sensor to ground. The sensor resistance goes from very high down to below 1k ohm depending on the force applied to it. Remember in a voltage divider the larger voltage is always across the larger resistor (the sensor in this case) but the sensor is going to work in reverse with more pressure causing lower resistances causing lower ADC values. The resistance and the ADC values are lowest when the coffee carafe is out for pouring. You will get the highest values when the coffee carafe is full and on the heating element.

That’s it! The wiring is done!

Tinker, Tailor, Soldier, Tape?

Now we need to get our sensor in place and here is where you need to learn the secret. Are you ready for it? Here goes: you can’t just put the sensor under the little foot of the coffee maker and get good results–you need to tape it in place to get consistent readings and meaningful data. The sensor actually has permanent tape on it, but I don’t recommend that. Permanently taping sensors to the household appliances has a very low wife-acceptace factor, so use removable double-sticky tape like this:

Try to center the sensor on the little foot bump on the bottom of the coffee maker.

Measure Up

Ok, so now what? We have our board and sensor but we need to understand what the data means in the real-world. Time to load up Tinker, the universal core app! With Tinker we can get core ADC readings, which is what we need for our program. Note that we can ignore the actual resistance here–we don’t care what it is as long as it is fairly repeatable. What we want are the ADC values to use as break points or thresholds in our program.

Here’s how we can do it: Fire up Tinker and set core pin A0 to analogRead. Tap on the pin to read the values. Take at least three measurements and write them down. You can decide if you want to pick the middle one (median) or average them (mean) or throw out bad readings (outliers). Start with just the coffee maker–take the carafe out–that should be the minimum force (weight) on the sensor and therefore the largest ADC value. For me, that was 1890. Then add the empty carafe back in and take three more readings. I got 1730 on my coffee maker. Here’s a slightly higher measurement being taken:

Now fill the carafe with water up to the maximum (12 cups for me) and put the carafe in the holder and take measurements. Then just pour off 2 cups at a time and take measurements for 10, 8, 6, 4, and 2 cups.

I choose 2 cup intervals because the sensor is just not that accurate and on mine, 2 cups changed the ADC value by between about 30 and 50.

After you decide the best value for each range, you should have 8 numbers, including the empty and no carafe values.

Note that I thought about writing a separate app to just do this measuring, but with Tinker right at hand, why not just use it!

Ready for Firmware!

So for the firmware, we can set limits above and below the valid range of data that you collected above. For my data, I set the lower error limit at 1100 and the upper error limit at 2200. Anything outside that range, I decided would not be valid and would just be ignored.

So we still have one problem to solve–we have the nominal values for the various coffee levels, but what if the carafe has 5 cups instead of 4 or 6? We need to create upper and lower limits around the good values so that if the reading is between 4 and 6 cups, we can decide which bin it should go in.

We do this by setting limits halfway between the data points we collected (and the error limits). So the lower limit for the 6 cup bin is halfway between the 4 and the 6 cup bin. Since the upper limit becomes the lower limit for the next bin, we could optimize this a bit and carry it forward, but we just recalculate it to make the code easier to read. Similarly we could worry about the exact end points and doing < versus <= but it doesn’t really matter in this application, so the lower bin wins.

Once we get a reading between the lower and upper limits, we look to see if the state changed from the last time we read it and light up the LED if it did. We also capture the state of the coffee pot in the global lastState variable, being careful to note that the lower error limit creates an offset, so a -1 is needed.

We then publish the results and wait 2 seconds. We then turn off the LED (if it was on) and the loop continues.

There are two good debugging features here: one is the LED that tells us instantly that the if-condition in our loop was satisfied and we are updating the state. That is very reassuring while you are testing it. The other debugging feature is a Spark.variable for the current reading. Let’s say you have 6 cups in the pot but the Spark core is reading 8. With the variable you can see what the actual value was and decide if you want to adjust your levels.

Here’s the code:

// Coffee Machine Sensor

uint8_t lastState = 6;  // empty is the default state

#define ERR_LL 1100
#define ERR_UL 2200
//  Sensor readings for...               Full,  10c,   8c,   6c,   4c,   2c, empty, carafe out,
const uint16_t threshold[10] = { ERR_LL, 1435, 1470, 1500, 1550, 1600, 1628,  1730,  1890, ERR_UL };
const char stateStr[8][16] =  {"Full carafe","10 Cups","8 Cups","6 Cups","4 Cups","2 Cups","Empty","Carafe out"};

int currentReading;

void setup() {
    pinMode(A0, INPUT);
    pinMode(D7, OUTPUT); //LED
    Spark.variable("CoffeeReading", &currentReading, INT);
}

void loop() {
    currentReading = analogRead(A0);
    if ((currentReading > ERR_UL)||(currentReading < ERR_LL)) {
        // invalid reading, just ignore it
        return;
    }
    for(uint8_t i=1;i<9;i++) { // start at Full
        // Create band around central values
        uint16_t lowLimit  = threshold[i]-(threshold[i]-threshold[i-1])/2; 
        uint16_t highLimit = threshold[i]+(threshold[i+1]-threshold[i])/2;

        if ( (currentReading >= lowLimit) && (currentReading <= highLimit)) {
            if (i != lastState) {
                digitalWrite(D7, HIGH);  //blink the LED
                lastState = i-1;
                break;
            }
        }
    }
    Spark.publish("CoffeeUpdate",stateStr[lastState]);
    delay(2000);  //two second delay
    digitalWrite(D7, LOW);
}

Note that we use a 2-d string called stateStr to hold the published values. The order of this string is important since we can leave off the second index, so while stateStr[0][0] would be the “F” in “Full Carafe”, stateStr[0] without the second index refers to entire string “Full Carafe”. This is very handy to know!

The Web Side

Now for the web side. Just like my other publishing tutorials, this one has a Javascript HTML page that you need to keep private. Be Safe! We are going to put our access tokens in this page so you don’t want to put it on the general internet. If you need to have that, you will need you own web host and a server proxy such as the one in the thread on “Simple Spark PHP Proxy”.

This code also has a HTML5 canvas graphing feature. Since we are publishing every 2 seconds, and the web page collects 120 data points, that represents 4 minutes of data.

Here’s the code–don’t forget to put in your device ID and access token:

<!DOCTYPE HTML>
<html>
<body>
    <span id="myStatus"></span><br>
    <br><br>
    <canvas id="myCanvas" width="400" height="100" style="border:2px solid #d3d3d3;">
      Your browser does not support the HTML5 canvas tag.</canvas>
    <br>

    <button id="connectbutton" onclick="start()">Connect</button>
 
    <script type="text/javascript">
      var TempData = new Array();
      var NSamples = 120;
      for (i=0;i<NSamples;i++){ TempData[i]=0; }

    function start(objButton) {

        document.getElementById("myStatus").innerHTML = "Waiting for data...";
        document.getElementById("connectbutton").value = "Reconnect";

        var deviceID = "<< device id>>";
        var accessToken = "<< access token >>";
        var eventSource = new EventSource("https://api.spark.io/v1/devices/" + deviceID + "/events/?access_token=" + accessToken);

        eventSource.addEventListener('open', function(e) {
            console.log("Opened!"); },false);
         
        eventSource.addEventListener('error', function(e) {
            console.log("Errored!"); },false);
         
        eventSource.addEventListener('CoffeeUpdate', function(e) {
            var parsedData = JSON.parse(e.data);
            var theSpan = document.getElementById("myStatus");
            theSpan.innerHTML = "Coffee status as of "+ parsedData.published_at + " is " + parsedData.data
            theSpan.style.fontSize = "28px";

	    var graphValue = 0;
	    switch (parsedData.data) {
	       case "Full carafe":
		   graphValue = 12;
		   break;
	       case "10 Cups":
		   graphValue = 10;
		   break;
	       case "8 Cups":
		   graphValue = 8;
		   break;
	       case "6 Cups":
		   graphValue = 6;
		   break;
               case "4 Cups":
		   graphValue = 4;
		   break;
               case "2 Cups":
		   graphValue = 4;
		   break;
	       case "Empty":
		   graphValue = 0;
		   break;
               case "Carafe out":
		   graphValue = -1;
		   break;
               default:
		   graphValue = -1;
		   break;
	   }

            var c=document.getElementById("myCanvas");
            var w=c.width;
            var h=c.height;
            var ctx=c.getContext("2d");

            var SampleDist = w/NSamples;
	    ctx.strokeStyle = "#4572a7";
	    ctx.lineWidth = 2;
            for (i=0;i<(NSamples-1);i++) { TempData[i] = TempData[i+1]; }
            TempData[NSamples-1] = graphValue;
            var maxval = 12
            var minval =  -1;
            ctx.clearRect(0,0,w, h);
            ctx.beginPath();
            for (i=0;i<NSamples;i++) 
                         {
                         ctx.arc(SampleDist*i+(SampleDist/2),(h*0.8)-(h*0.6)*(TempData[i]-minval)/(maxval-minval),1,0,2*Math.PI);                     
                         }
            ctx.stroke();
            ctx.font = "9px Arial";
	    ctx.fillStyle="#b5c7dc";
            ctx.fillText(maxval,w-50,(h*0.8)-(h*0.6));
            ctx.fillText(minval,w-50,(h*0.8));


        }, false);
    }
    </script>
</body>
</html>

The graphing code can autoscale to the data if you find the min and max values, but here we know that it is a 12-cup coffee maker and we use the magic value of -1 to indicate that the carafe is out for pouring or refilling, so min and max are hard-coded here.

Test It Out

You get to your page by using a file url like this:
file:///my/path/to/a/local/file/CoffeeStatus.html

Here’s what that page looks in Chrome:

You can see the coffee values are steady and then someone takes the carafe out to pour a cup and puts it back with a lower amount, until finally the last cup is taken the carafe is not put back right away.

I like to put these private HTML files in a private directory on Dropbox. That way, I can open them as a web page in the Dropbox app on my phone and get anywhere/anytime access without compromising my access token.

Wrapping It Up

OK, so now we know how use the maker kit force resistance sensor to help us with our coffee habit. We tried out a simple voltage divider and measured real-world values with the core, publishing the result to our web page.

You could do a lot more! You could try to make a more accurate mapping of ADC values to coffee levels. You could try only publishing when there is a state change. You could add a temperature sensor the make sure the coffee is piping hot! You could even measure the average pouring time. There are lots of ways to go from here–this is just the first step.

I don’t know about you, but I could use a coffee break after all that! Looks like the pot is empty so it’s my turn to make a new pot!

13 Likes

Great write-up!

I need to mod my coffee roaster, now!

2 Likes

Can we tell if it’s a good brew or not?

IF (poor brew) {RGB blinks red} :smiley:

Nice project!

1 Like

bko, great Maker Kit project! Great instructions, easily made and more importantly, easily removed when the significant other threatens divorce or physical punishment :open_mouth:

I would like to do a vulcan mind-meld with you cause I am envious of your knowledge of html! Now, if I could only find a Vulcan… :smile:

3 Likes

This is a super great write up @bko! Love the use of the force sensor.

Looking at how the Spark Core and Web App work, I’m having a couple thoughts that I’ll share. The Spark Core seems to not do much, and the web app seems like it does a lot more. I was wondering where the data gets stored, and it seems that it’s stored in the browser cache. This means you have to keep your app open all of the time to see the full history of what’s happening. It would be nice to be able to open the web app at any time to check the status, and get the full picture.

The Spark Core has a fair amount of RAM and also non-volatile FLASH memory that we could use to store some data. By changing the way the Spark Core looks at the sensor, we can also trim down the memory usage and only add new entries every time the pot changes value from FULL. At the same time we can also ignore the short times when the carafe is removed to pour a cup. But if the carafe is removed (or empty) for too long, that data point can be stored. Each data point would have a time stamp associated with it which could just be the millis() counter or a true time stamp. With the timestamp and carafe value, you could simplify your web app to just open, retrieve the small number of data points, and build a graph. You could also make the Spark Core send you a push notifcation directly if the carafe is determined to be empty (if you care). You would likely press the reset button on the spark core after you started a new pot of coffee, or rig this into the brew cycle somehow.

1 Like

I think someone wrote an NTP library that might do something like that. Now, if I could just remember who did it... :wink:

1 Like

Hi @BDub

I think these are great ideas!

One of my unstated goals for tutorials is that they should be simple enough that all the code can fit on one screen. For a time, we were looking at cyclomatic complexity at work to try to simplify code and remove bugs.

But I broke that code length rule slightly to get plotting code out for the HTML5 canvas, because I thought it would be good to get it out there. I have been using similar graphing code for temperature monitoring for many weeks now and even have a decimating version that plots over longer periods. With temperature, the auto-scaling is very nice. I think of these little web pages as dashboard widgets without the dashboard and I put them in shrunk down chrome windows and just leave them open.

In the first version that I wrote, the code only published on changes and that was strange since you could join the stream on the web page at any point and not see an event for an unknown amount of time. I think a better approach might be to publish on changes and publish at least every 30 seconds or so, so you can join in at any time. Another approach is to have a Spark.function() that triggers a publish event, so you can essentially poll if you want to.

Maybe all I really need is a boolean flag: Is there coffee available right now? :smiley:

So I’m working on a nearly identical project (with a few extra bells and whistles), but it appears that I’ve broken my FSR. From what I’ve read on Adafruit and the Interlink website, the FSR sensors are somewhat brittle.

Mine started acting up after I got it wet in a small puddle near my coffeemaker. The voltage readings dropped to nearly half of their normal values (2000 instead of 4096 with no force applied).

Then after soldering on longer leads (even with the ‘tinning’ approach), the FSR stopped completely. It reads ‘93’ always.

Anyway, I ordered a new FSR, as I’ve come too far with this to give up now. My question is, how do I make the FSR somewhat water resistant, and possibly extend the leads without soldering?

Thanks!

Hi @eterps

I did not have these problems but I am sorry you are having them! Since whole surface on both sides is plastic coated, moisture must be effecting it coming in from the thin edge. You might be able to try it out using a hair dryer (not too hot). Here’s a link to paper showing their construction (and saying they are moisture proof, hah!).

http://www.interlinkelectronics.com/pdf/whitepaper1.pdf

I don’t know how to seal it completely but I think anything that still allows the two sides to move together will be OK. Perhaps you could use a tape of some kind wrapped completely around the sensor.

The parts are made to be soldered but you have to be quick and not leave the heat on too long. An alternative would be a crimp-on connector.

Haha funny you should mention a hair dryer, as I actually tried that!
Anyway, this documentation tells a very different story in terms of durability and usage:

http://www.ladyada.net/media/sensors/fsrguide.pdf

When I get my new sensor, I’ll probably just go with alligator clips and a plastic baggie at first, and gradually ramp up from there.

1 Like

Adafruit seems to be rather content with stuffing clear nail polish on several of their wearable projects to seal of a solder connection. Might be worthwhile checking out. Should be kind of waterproof. Just an idea though…

I really like this!
Great project.

2 Likes

Great project!

1 Like