My Imp Chef.

When I was a kid, we’d cook our holiday turkey or roast with one of those little pop-up thermometers that you just stick into the bird, that pops up (presumably) when the bird is done, or at least safe to eat. We had a lot of dry turkeys and roasts. These days, you can buy a thermometer that tells you the actual temperature, which is nice. Better still, you can get a wireless one! But at that point, things get a little disappointing. Most of the wireless sensors have a pretty low range — a hundred feet or so. No good if you’re out on the deck or in the backyard, and the part you have to bring with you is a big heavy thing that clips to your belt and makes you look silly. And the worst part: no graphs.

Everything should make graphs. Obviously.

I’ve already built some internet-connected thermometers with Electric Imp, so why not do the same thing with a meat thermometer probe and send the data off to a web site I can view on my phone? As it turns out, it’s easy. Build one of these and impress your dinner guests.

This project was a finalist in Make: magazine’s Connected Home contest. –Make Editors

VTracking the Temperature with my Imp Chef

What will the next generation of Make: look like? We’re inviting you to shape the future by investing in Make:. By becoming an investor, you help decide what’s next. The future of Make: is in your hands. Learn More.

Project Steps

Connect the Button and Sub-mini Jack

Connect the button. This button is going to serve as an on/off button for your device. The firmware included automatically sends the device off to sleep after a period of inactivity. This button will be used to wake the device up, or to force it to sleep immediately to save battery. The button will set the imp’s Pin1 high when pressed, which can be used to wake the imp from deep sleep.

Line the button up so the little feet are pointing toward the shorter ends of your breakout board, and line the bottom-left foot up with the Pin1 pad on the imp breakout board. This will line another of the button’s feet up with Pin5 on the imp breakout board. Tape the button in place, then heat the pad and the button’s foot with your soldering iron and apply a little solder. Solder the other foot to Pin5 the same way. Pin5 isn’t used for anything in this device, so this is just a good way to make your button more mechanically sturdy.

Cut a short piece of wire, strip both ends, and point-to-point solder the other end to one of the two feet on the far side of the button. Tape the wire in place so it is touching the foot of the button, then heat both the wire and the foot with the iron. Flow a little solder onto the heated parts.

Solder the other end of the wire to the “3V3” pad on the breakout board. You may want to wait to apply the solder to this terminal, because we need to wire another wire to this pad.

Solder leads to your sub-mini audio jack. This jack is where you’ll plug your temperature probe in. Because the temperature probe doesn’t have any polarity, it doesn’t matter which way around the jack is connected.

If you’re using a switched jack, like I did, there will be a third terminal; this is a switch terminal, and is used to detect when there is a plug plugged into the jack. You don’t want to use this, so make sure you’re using only the terminals that are supposed to connect to the tip and the shield of the plug. If you’re not sure which you’ve got, you can use a multimeter to check for continuity. If you do this, make sure you install a plug in the jack before testing, because otherwise the tip contact and switch contact will be electrically connected.

Cut and strip two medium (3″) pieces of wire. Run one end through each of the terminals you wish to connect. It’s very helpful to tape the jack and the wire down to your work surface at this point to hold them steady.

Heat the terminal and wire with the soldering iron, then flow some solder onto the parts. Make sure you heat the wire as well as the terminal, or the solder joint will break.

Connect the jack between 3V3 and Pin9 on the breakout board. Don’t solder the Pin9 pad yet; we still need to attach a resistor to that pad.

Run the free end of the wire from the jack through the appropriate terminal on your breakout board, then heat the wire and pad with the iron and apply some solder.

Make sure both the wire from the jack and the wire from the button are through the 3V3 pad before soldering.

Connect the 47kΩ Resistor and Battery Contacts

Connect the 47kΩ resistor between Pin9 and Pin8.

The Pin9 pad on the breakout should still be unsoldered. Make sure both the jack and one side of the resistor are through the pad, then solder both in place.

The other side of the resistor should be soldered to the Pin8 pad on the breakout. You can go ahead and solder it right away; we don’t need to connect anything else to this pin.

Connect the Battery Contacts to the “P+”/”P-” pads on the Electric Imp Breakout

Since these aren’t through-hole pads, the technique is a little different. Secure the board in place with some tape, then place the red wire from the battery contact on top of the P+ pad. You may want to secure this with some tape, as well. Heat the pad and the wire together, then apply some solder. Repeat with the black wire on the P- pad.

Secure the Battery Holder and Sub-mini Jack

Connect the battery holder. Apply a small square of double-sided foam tape to the bottom of your breakout board and remove the backing on the other side. Apply the battery clip here, so that the battery will sit nicely along the bottom of the breakout board. Hold the battery clip and breakout board together for 10s.

Your BBQ thermometer will be more mechanically robust if you mount the submini jack to the PCB. I used a bit of hot glue to get the job done. It’s important not to short the jack to the battery clip, or to cover the contacts with glue. For best results, plug the probe into the jack before gluing, and carefully secure only the threaded portion of the jack to the board.

You are now done building your internet-connected BBQ thermometer! Or at least, done putting the hardware together. Let’s get started programming the device.

Program the Imp

To get started programming your device, you need to get it connected to the internet through wi-fi. The imp supports just about every type of wi-fi encryption, so all you need is your SSID and Password, an iOS or Android device, and an Electric Imp account.

To get the imp connected to wi-fi, you’ll use the free Electric Imp app to send something called “BlinkUp.” BlinkUp is just an optical signal, sent by flashing the screen of the iOS or Android device. The imp has a tiny light sensor built in, and it decodes the blinking pattern to get the SSID and password of your wi-fi network. Once it has the credentials, the imp will connect to the internet the same way your phone or computer would, and will check in with the Electric Imp cloud. The new device will show up in your IDE, which you can work with in your browser, and you’ll be able to program and monitor your device from anywhere in the world with an internet connection.

If you don’t have an Electric Imp account, it’s time to register for one. It’s free. Head to ide.electricimp.com and sign up there.

Once you’ve signed up for an Electric Imp account, download the free Electric Imp app and sign in with the same user name and password you used to sign up for your account. In the app, add a new network: this is where you enter your SSID and password for the wi-fi network.

Power on your device by connecting the battery to the battery contact. Make sure the jumper near the bottom edge of the breakout board is set to “BAT” to select battery power. Insert the imp card into the socket, and it should begin to blink.

Press “Send BlinkUp” in the app, and hold the screen of your phone against the end of the imp with the blinking light. The screen of your phone will flash for about 30s, and then the imp will begin blinking different colors as it goes through the process of connecting to wi-fi.

When the imp finishes connecting to the Imp Cloud, it will appear at ide.electricimp.com in the left-hand navigation pane under “new devices.” You may need to refresh the page to get the device to appear. Click “new devices” to expand the list of new devices, and you’ll see a long, random-looking string of characters. This is the default name of your new device, its “impee ID.” Click on the name to open up the device options. Here, you can give the device a new name and assign it a “model.” A model is simply a group of firmware; many devices can be members of the same model, and will run the same code. To create a new model, type a new model name in the model name box and hit “save changes.”

You’ll notice now that there are not one, but two code windows for your device, marked “Agent Code” and “Device Code.” Your device code runs on the electric imp, inside a VM, so that if errors occur the device will not become unreachable. An agent is a second VM which runs inside the Imp Cloud. Every device has an agent as a partner. The agent handles things like defining an HTTP interface and doing data-intensive work like file processing. The agent and device can send data back and forth easily.

There’s code already written up and ready for you for this project, so you can go ahead and pick it up from github. Note that there are two files:

turkeyprobe.agent.nut is the firmware that runs on the agent, and turkeyprobe.device.nut is the firmware that runs on the device. Paste each file into the appropriate window. The IDE automatically saves whenever you make changes, and you can also save the code in the model by building and running the code.

You can run the code now, but expect some errors: we’re not quite done yet. The imp will log temperature data to a time-series data store called Xively, and you’ll need to set up a feed there to receive the data. Let’s do that next.

Configure a Xively Feed

To get started with Xively, head to xively.com and register for a free account. Once you’re registered and logged in, you’ll be taken to your dashboard, where you can see a list of devices. Somewhat strangely, you need to “add a device” to create a feed. Click the giant plus button to do this. Name your device whatever you like, and choose whether the data will be private or public. Either way will work fine.

Once your device is created, you’ll be taken to a page where you’ll be able to gather two important pieces of information: the feed ID and the API key for your feed. The feed ID is in the top-right corner of the dashboard, and the API key is below it, on the right side of the page (the API key is long). Both need to be copied into your agent code, on lines 38 and 39:

const XIVELY_API_KEY = "YOUR KEY HERE";

const XIVELY_FEED_ID = "YOUR FEED ID HERE";

Once you’ve added these credentials to your agent code, you should be able to build and run the code without error. The device will begin taking readings, and you’ll see them appear in the log in the IDE. Back in the Xively dashboard, you should see two new channels automatically created, one starting with “lowbatt” and one starting with “temperature.” Both channel names append the device ID of your new thermometer, so you can use the same feed over and over again with many thermometers and each device will know to only update and use its own data.

Use your Thermometer

Here’s the completely non-technical user’s manual for your new internet-connected BBQ thermometer:

To turn the BBQ thermometer on, press and hold the button for three seconds. You’ll see the imp wake up, begin blinking, and connect to the internet just like you did when you first connected it earlier.

The thermometer will automatically turn off after a period of inactivity. The device automatically detects if it is being used for actual cooking purposes by looking at the current temperature and the rate of change in the temperature, and stays awake when in use. Once you’re done using it, it will automatically turn itself off. You can also turn it off manually by pressing and holding the button for 3 seconds.

To view the current temperature and temperature graphs, point a browser at the agent URL. The agent URL is visible at the top of the agent window in the IDE. You can select the length of data you wish to show in the graph and toggle between ºC and ºF for the current temperature displayed at the top of the page. To use the page as an app, open the page in safari and select “save to home screen” from the share menu.

To change the wi-fi credentials, either cycle power or send the device to sleep and wake it up again. The device listens for a new BlinkUp packet for 1 minute after it boots up. During this period, you can send it new network credentials the same way you did to configure it the first time.

A Closer Look at the Device Code

In case you’re curious: let’s take a closer look at what makes the BBQ thermometer work!

Let’s start with the device code. It’s short and simple. It has two jobs: collect temperature readings, and respond to button presses.

Take a look at the function definition for getTemp().

function getTemp() { imp.wakeup(INTERVAL, getTemp);

vtherm_en_l.write(0);

imp.sleep(0.01);

local rawval = vtherm.read();

local temp = (TEMP_COEFF_2*(math.pow(rawval,2)))+(TEMP_COEFF_1*rawval)+TEMP_OFFSET;

temp = (temp * 1.8) + 32.0;

vtherm_en_l.write(1);

agent.send("temp",{"temp":temp,"vbat":hardware.voltage()});

}

This function collects temperature readings at a regular interval. The interval is defined by a constant, INTERVAL, at the top of the file. The first thing getTemp does is schedule itself to run again in INTERVAL seconds, by calling imp.wakeup(INTERVAL, getTemp). This schedules a callback; the imp can carry on doing other things, and in INTERVAL seconds, the operating system will call back and ask us to run getTemp again.

After scheduling itself to run again, getTemp reads the voltage in the middle of the thermistor divider, then uses a simple 2nd-order curve fit to estimate the temperature. The coefficients for the curve fit are stored as constants at the top of the file (if you look, you’ll notice we’re actually just using a linear curve fit! But the 2nd-order fit is there if you want to try it out).

Next, we have a function that tells the imp what to do when it’s time to go to sleep:

function goToSleep() {wake.configure(DIGITAL_IN_WAKEUP);

// go to sleepfor max sleep time (1 day minus 5 seconds)

server.sleepfor(MAXSLEEP);

}

This function does two things: first, it configures Pin1 as a wakeup pin, so that if the button is pressed it will wake the imp from deep sleep. Second, it tells the imp to go to deep sleep for as long as it is allowed (MAXSLEEP is defined as 86396 seconds at the top of the file; this is 1 day minus 4 seconds. The call to server.sleepfor() alerts the server that the device will be going to sleep, so the agent won’t have to wait for the imp to go missing before it realizes what has happened.

Below that, there’s a handler function for button press events:

function btnPressed() {

// wait to see if this is a long press, and go to sleep if it is

local start = hardware.millis();

while ((hardware.millis() - start) < LONGPRESS_TIME*1000) {

if (!hardware.pin1.read()) {return;}

}

goToSleep();

}

This is an interesting function. As it turns out, when you press the button at all, the imp calls this function right away. The imp doesn’t wait several seconds to go to sleep, as you might have thought from how the thermometer works. Instead, the imp calls this function immediately, and waits to see if the user holds the button down for three seconds. If you do, it goes to sleep. If you don’t, it leaves the function and goes back to doing what it was doing before.

Next, we see some callbacks being registered for agent events:

agent.on("sleep", function(val) {

imp.onidle(function() {

goToSleep();

});

});

agent.on("needDeviceId", function(val) {

agent.send("deviceId",hardware.getdeviceid());

});

The first handler here allows the agent to tell the device to go to sleep. While the device is running, the agent keeps an eye on how much temperature change it is seeing and adjusts the amount of time it will let the device stay awake before calling this event and telling the device to go to sleep to save battery.

The second handler is only used in special occasions. The agent doesn’t automatically know the device’s deviceID, but it needs it in order to set the channel names to send data to Xively. Ordinarily, the agent and device boot up together when the agent boots up for the first time, after which the agent stays on. However, sometimes the agent will restart by itself, such as if you push new code to it. In this case, the agent needs a way to ask the device what its deviceID is — this function gives it a way to do that.

After that, we’re done with definitions, and we reach the point where actual run-time operation will start when the device boots. The first thing the imp does when it boots is figure out why it booted. If it was because of a Pin1 wakeup, the imp does the same thing it does if you hold the button to send it to sleep — it stays right here and waits to see if you hold the button. If you let go before the 3 second wait time, the imp will go directly back to sleep before it even connects to the internet.

// check wakereason and make this a shallow wake if necessary

if ((hardware.wakereason() == WAKEREASON_PIN1) || (hardware.wakereason() == WAKEREASON_TIMER)) {

local start = hardware.millis();

while ((hardware.millis() - start) < LONGPRESS_TIME*1000) {

if (!hardware.pin1.read()) {goToSleep();}

}

// if we made it here, somebody's just long-pressed the power button to wake the imp

// go ahead and boot right up.

}

// not a shallow wake; fire up the radio and let's cook a turkey

imp.setpowersave(true); // save juice, as this application is not latency-critical

Lastly, we instantiate the objects we need to do our job and check in with the agent, then start reading the temperature to get started.

agent.send("justwokeup",hardware.getdeviceid());

getTemp()

Let’s take a look at the agent code in the next step, if you’re interested.

A Closer Look at the Agent Code

The agent firmware is a lot longer, but as you’ll see in a second, that’s because it contains two large chunks of code that do more general jobs.

The first thing the agent does when it boots up is to check and see if it’s just rebooted and already knows the device ID. Whenever the agent has to go and get the deviceID from the device, it saves it in the imp cloud with server.save() as soon as it gets the update. This way, if the agent ever restarts, it can grab the ID right away without even having to check in with the device by calling server.load():

// Device ID used to create new channels in this feed for each new turkey probe
config <- server.load();

if (!("myDeviceId" in config)) {

// grab the pre-saved device ID from the server if it's there

// if it isn't, we've never seen this device before (or the server forgot - unlikely!)

// we will request a device ID from the device if we make it past class declarations without

// the device doing an "I just woke up" check-in.

config.myDeviceId <- null;

}

Next, we run into a giant function with a very big multi-line string in it. This function is called prepWebpage, and all it does is concatenate a few strings together. These strings just so happen to be a web site. This web site is the web UI for the BBQ thermometer, and it’s what you see when you request the agentURL in a browser. Because the agent has the ability to set up its own HTTP handler, it can respond to certain requests by serving up this very long string — basically, the agent acts like a tiny web server. The web site even includes some simple javascript that runs on the client machine.

After the website, the agent has a function that keeps track of activity on the device and uses a timer and some simple heuristics to figure out if the device should go to sleep to save battery.

function checkSleepTimer() {imp.wakeup(TIMER_DEC_INTERVAL, checkSleepTimer);

sleepTimer -= TIMER_DEC_INTERVAL;

if (sleepTimer 30) {

sleepTimer += 60;

} else {

sleepTimer += delta * 2;

}

}

// don't let the sleep timer exceed the preset max.

if (sleepTimer > MAX_SLEEP_TIMER) {sleepTimer = MAX_SLEEP_TIMER};

local tempStr = format("%.1f",data.temp);

server.log("Temp: "+tempStr+" F");

// post the datapoint to the Xively feed

postToXively(tempStr, "temperature");

// check for low-battery issues

server.log("Battery: "+data.vbat+" V");

if (!lowBattAlarm && (data.vbat LOW_BATT_THRESH)) {

// clear the low batt alarm and post it to xively

lowBattAlarm = 0;

postToXively(lowBattAlarm, "lowbatt")

server.log("Low battery alert cleared.");

}

});

Because of how this device is wired up, it actually won’t ever trigger the low battery alarm; this was included for a similar device that was powered off of a pair of AA lithium batteries without a regulator between the batteries and the imp, so the imp could look at the battery voltage directly. The code was left in just in case anyone gets intrepid and builds one inside the original housing!

Near the bottom of the agent firmware, we see one of the most important parts of the agent: the HTTP request handler. This handler parses incoming HTTP requests and defines how the agent should respond.

http.onrequest(function(request, res) { server.log("Agent got new HTTP Request");

// we need to set headers and respond to empty requests as they are usually preflight checks

res.header("Access-Control-Allow-Origin", "*");

res.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");

res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS");

if (request.path == "/sleep" || request.path == "/sleep/") {

device.send("sleep",0);

res.send(200, "Going to Sleep");

} else {

server.log("Agent got unknown request");

res.send(200, WEBPAGE);

}

});

Most of the requests to the agent are going to be requests just for the web page, so requests without additional parameters just get the web page as a response. There’s also a “hook” here for external services to tell the imp to go to sleep, which the web page doesn’t use.

Lastly, the agent instantiates a Xively Client object which it will use during runtime to post data to the Xively stream, requests the deviceID if necessary, and starts running the sleep timer:

server.log("Turkey Probe Agent Started.");

// instantiate our Xively client

xivelyClient <- Xively.Client(XIVELY_API_KEY);

// in case we've just restarted the agent, but not the device, call the device for

// the device ID in 1 second if it doesn't ping us with an "I just booted" message

imp.wakeup(1, function() {

if (config.myDeviceId == null) { device.send("needDeviceId",0); } else { prepWebpage(); };

});

// start running the auto-sleep watchdog timer

checkSleepTimer();

And that’s all there is to it!

Good luck and bon appetit :)