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", ¤tReading, 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!