Blog

Ship IoT with Kinoma Create and PubNub

September 30, 2015 / Posted By: wotio team

For the Love of Coffee

French Press

Coffee is amazing stuff, and when brewed just right, tastes incredible! I'm a coffee aficionado, and I'm always pursuing The Perfect Cup™. Preparation technique is critical! The Specialty Coffee Association of America has very rigid standards for how to prepare coffee, designed to ensure consistent and peak quality flavor in the resulting drink. Water temperature is one of the major factors, because as any good chemist knows, various compounds dissolve at different rates in different temperatures of water. The flavor of your cup of coffee is greatly determined by the temperature of water used, and consequently, the varying fractions of coffee compounds thereby extracted.

From the SCAA standard:

Cupping water temperature shall be 200°F ± 2°F (92.2 – 94.4°C) when poured on grounds.

We are engineers. We appreciate the scientific method, and data-driven decisions. The quest for The Perfect Cup must therefore entail data collection for later analysis. This collection should be automated, because life is too short for repetitive manual processes. So let's start out by checking our existing daily brewing process' water temperature, and logging the long-term variance.

We're going to do this with a Kinoma Create, which packs an 800 MHz ARM v5t processor, WiFi, Bluetooth, a color touchscreen, sound, I/O pins, and all kinds of other goodies. It's a comprehensive development kit that lets you code with JavaScript and XML, so it's a great choice, and even more so if JavaScript is one of your competencies. This will make our temperature logging simple, and the data services available through wot.io give us easy insights into our data because the integration is already done and working. Expanding beyond our first-steps of temperature logging will be a snap, as the Kinoma Create has more I/O than we can shake a stick at. Let's get to it!

Getting Started

For this project, I used:

Kinoma Create, coffee beans, and LM-35 sensors

The Probe

First thing I did was make a probe suitable for testing something I was going to drink. It needed to be precise, non-toxic, and tolerant of rapid temperature changes.

Temperature probe built into a Pyrex test tube

The temperature sensor is the LM35 from Texas Instruments, a military-grade precision Centigrade sensor with analog output, accurate to ±0.5ºC. That's well-within the specified ±2ºF spec from the SCAA for brewing water.

TI LM-35 Sensors in TO-92 Packages

I attached the sensor inside a Pyrex borosilicate glass test tube, which will withstand the thermal shock inherent in measuring boiling water. We certainly don't want shattered glass shards or contaminants in our coffee! To ensure good heat transfer, I used some thermal epoxy to affix the sensor at the bottom of the tube.

LM-35 epoxied into the end of a test tube

The cable is Belden 9841, typically used for RS-485 industrial controls and DMX 512 systems. While we don't need precision 120Ω data cable for this, it has 100% foil+braid shield and will keep our analog signals nice and clean. Plus, I had a spool of it on the rack - always an advantage ;)

About that LED... It functions as a power indicator, and makes the probe look good for showing off at World Maker Faire. Normally I wouldn't stick an LED next to a precision temperature sensor. The power dissipated by the LED and current-limiting resistor will cause a slight temperature rise and throw off the measurement. But it only dissipates maybe 10 milliwatts, and coffee is really hot, so I stuck an LED in there! No worries.

Testing the Sensor

Before writing the code, I needed to be sure the sensor output matched what's claimed on the datasheet (always check your assumptions!). A quick setup on a breadboard proved the datasheet to be correct.

LM-35 in a solderless breadboard

The temperature of the sensor itself measured ~24.1ºC with a calibrated FLIR thermal camera (with an assumed emissivity of ε0.90 for the plastic TO-92 case):

Thermal image of LM-35 reading 24.1ºC

...and the output of the device was 245mV, right on target!

Multimeter reading 245.59mV

Now we know we don't need much correction factor in software, if any.

The Code

I'll lead you through a very brief walkthrough of the code. You can grab the code from the repo on github.

First thing you'll want to do is put your PubNub publish and subscribe keys into the code, and your channel name.

PubNub Dashboard

Grab the keys and put them in a the top of main.xml:

<variable id="PUBNUB_PUBLISH_KEY" value="'YOUR_PUB_KEY_HERE'"   />  
<variable id="PUBNUB_SUBSCRIBE_KEY" value="'YOUR_SUB_KEY_HERE'" />  
<variable id="PUBNUB_CHANNEL" value="'YOUR_CHANNEL_NAME_HERE'" />;  

PubNub Library Integration

One of the key bits to using this PubNub library is you need to override the default application behavior. Their example came as straight JS, but I converted it to XML here, so you get to see both methods and learn some new tricks.

At the top, we include the pubnub.js library file, and then define a behavior that uses the PubNubBehavior prototype. While I won't claim to be an expert on PubNub's library, I believe we do things this way so that the PubNub library can handle the asynchronous events coming in from the message bus.

We also start into the main startup code, which resides in the onLaunch method.

<program xmlns="http://www.kinoma.com/kpr/1">  
    <include path="pubnub.js"/>
    <behavior id="ApplicationBehavior" like="PubNubBehavior">
        <method id="constructor" params="content,data"><![CDATA[
            PubNubBehavior.call(this, content, data);
        ]]></method>
        <method id="onLaunch" params="application"><![CDATA[
           ...

...and we see the rest down at the bottom, where we instantiate the new ApplicationBehavior and stick it into our main applicaiton.behavior thusly:

    <script>
        <![CDATA[
        application.behavior = new ApplicationBehavior(application, {});
        application.add( maincontainer = new MainContainer() );
        ]]>
    </script>

onLaunch Initialization

First thing we do is set up the pubnub object with our publish and subscribe keys. Note that you don't need to use keys from the same exchange - you can write to one, and read from an entirely different one. That's part of the amazing flexibility of message bus architectures like PubNub and wot.io.

After init, we subscribe to the specified channel, and set up callbacks for receiving messages (the message key) and connection events (connect). Upon connection we just fire off a quick Hello message so we can tell it's working. For receiving, we stick the message contents into a UI label element, and increment a counter, again doing both so we can tell what's going on for demonstration purposes.

You could certainly parse the incoming messages and do whatever you want with them!

        pubnub = PUBNUB.init({
            publish_key: PUBNUB_PUBLISH_KEY,
            subscribe_key: PUBNUB_SUBSCRIBE_KEY
        });
        pubnub.subscribe({
            channel : PUBNUB_CHANNEL,
            message : function(message, env, channel) {
                maincontainer.receivedMessage.string = JSON.stringify(message);
                maincontainer.receivedLabel.string = "Last received (" + ++receivedCount + "):";
            },
            connect: function pub() {
                /*
                    We're connected! Send a message.
                */
                pubnub.publish({
                    channel : PUBNUB_CHANNEL,
                    message : "Hello from wotio kinoma pubnub temperature demo!"
                });
            }
         });

Next we set up our input pins for the temp sensor:

        application.invoke( new MessageWithObject( "pins:configure", {
            analogSensor: {
                require: "analog",
                pins: {
                    analogTemp: { pin: 52 }
                }
            }
        } ) );

This uses Kinoma's BLL files which define the pin layout for hardware modules. I created a simple one for our temp sensor. I did not have the system configure the power and ground pins. At the time I coded this, Kinoma doesn't document an official way to do it (although it does exist if you dig into their codebase).

exports.pins = {  
    analogTemp: { type: "A2D" }
};

exports.configure = function() {  
    this.analogTemp.init();
}

exports.read = function() {  
    return this.analogTemp.read();
}

exports.close = function() {  
    this.analogTemp.close();
}

Lastly, we set up what is effectively the main loop. This fires off a message that will be processed by the analogSensor read method defined in the BLL file. It also sets it up to repeat with an interval of 500 milliseconds. The results are sent via a callback, /gotAnalogResult:

        /* Use the initialized analogSensor object and repeatedly
           call its read method with a given interval.  */
        application.invoke( new MessageWithObject( "pins:/analogSensor/read?" +
            serializeQuery( {
                repeat: "on",
                interval: 500,
                callback: "/gotAnalogResult"
        } ) ) );

The Results Callback

This is a message handler behavior which processes the analog value results from our periodic sensor read. It converts the reading to degrees Celsius, and fires off the data with an onAnalogValueChanged and onTempValueChanged message to whomever is listening. (We'll see who's listening down below...)

The sensor outputs 10 millivolts per degree Celsius, so 22ºC would be 220mV. This goes into our analog pin, which when read, gives a floating-point value from 0 to 1, representing 0V up to whatever the I/O voltage is set to, 3.3V or 5V. We do some conversion to get our temperature back.

You may notice that we only use a small range of the A/D converter's potential for typical temperatures, and this results in lower resolution readings. Ideally we'd pre-scale things using a DC amplifier with a gain of, say, 2 or 4, so the temperature signal uses more of the available input range.

    <handler path="/gotAnalogResult">
        <behavior>
            <method id="onInvoke" params="handler, message"><![CDATA[
                var result = message.requestObject;
                // Convert voltage result to temperature
                // LM35 is 10mV/ªC output; analog input is 0-1 for 0-3.3v (or 5 if set)
                // Subtract 1 degree for self-heating
                var temp = (result * 3.3 * 100) - 1;
                application.distribute( "onTempValueChanged", temp.toFixed(2) );
                application.distribute( "onAnalogValueChanged", result );
                pubnub.publish({channel:PUBNUB_CHANNEL, message:
                    {"k1-fd3b584da918": {"meta": "dont care", "tlv": [ {"name": "temperature", "value": temp.toFixed(2), "units": "C"} ] }}
                });
            ]]></method>
        </behavior>
    </handler>

The UI

Here we define the main container for the user interface. You'll see entries for the various text labels. Some of them have event listeners for the onAnalogValueChanged and onTempValueChanged events, and that's how they update the display.

    <container id="MainContainer" top="0" left="0" bottom="0" right="0">
        <skin color="white"/>

        <label left="5" top="0" string="'PubNub Temperature Telemetry Demo'">
            <style font="24px" color="red"/>
        </label>

        <label left="5" top="23" string="'Last Received (0):'" name="receivedLabel">
            <style font="20px" color="blue"/>
        </label>

        <label left="5" top="39" string="'--no message received yet--'" name="receivedMessage">
            <style font="14px" color="black"/>
        </label>

        <label left="0" right="0" top="80" string="'- - -'">
            <style font="60px" color="black"/>
            <behavior>
                <method id="onTempValueChanged" params="content,result"><![CDATA[
                    content.string = "Temp: " + result + " ºC";
                ]]></method>
            </behavior>
        </label>

        <label left="0" right="0" top="65" string="'- - -'">
            <style font="24px" color="green"/>
            <behavior>
                <method id="onAnalogValueChanged" params="content,result"><![CDATA[
                    content.string = result.toFixed(6) + " raw analog pin value";
                ]]></method>
            </behavior>
        </label>

        <picture url="'./assets/wotio_logo_500x120.png'" top="210" left="10" height="24" width="100" />
    </container>

Results

It worked well! After perfecting my water boiling technique (who would have thought that was a thing), I got a great cup with the data to prove it. Dark chocolate, caramel, hints of cherry and vanilla; earthy and full.

The messages flowed to PubNub from the Kinoma Create, and anything published to PubNub from elsewhere would show up nearly instantly on the Kinoma Create's screen. Keep reading to see how we used some data services via wot.io.

Finished demo with cup of coffee

World Maker Faire 2015

This setup was demonstrated at World Maker Faire 2015 in the Kinoma booth, where we also had a number of data services connected, scriptr.io, bip.io, and Circonus to start.

wot.io Ship IoT Data Service Exchange Diagram

These fed into Twitter and Gmail also. You can see the message flow graph created with bip.io, showing the message processing and fan-out:

bip.io workflow graph

We've written about creating these graphs before, just look through the other posts on the wot.io labs blog for several examples.

In Closing

Kinoma's Create platform pairs effortlessly with the data services available via wot.io, and the power to leverage existing expertise in JavaScript is a huge advantage when it comes time to develop and ship your product. That power extends further with wot.io partners like scriptr, where you can integrate further cloud-based JavaScript processing into your data service exchange workflow. To get started, grab a Kinoma Create and take a look at shipiot.net today!