Dynamic data to Highcharts

Hey everyone,

I'm attempting to pass live temperature data from a thermistor to a highcharts plot on my agent URL. I was able to use the  dynamic web page tutorial (https://electricimp.com/docs/examples/webserver/) to post the current temperature on my site, which I was super pleased with.

I’ve since tried to incorporate the dynamically updated tutorial on Highcharts (http://www.highcharts.com/stock/demo/dynamic-update), which also shows up on my page as long as the data is just the random numbers used in the tutorial. I tried to replace the y-value with savedData.temp, which is what is successfully being pushed and displayed on the site, but no luck.

This is the deep end of the pool for me, so any help would be appreciated.

Here’s my Agent and Device code.

Thanks in advance!

Ben


AGENT CODE


`
#require “Rocky.class.nut:1.3.0”

// GLOBALS
local api = null;
local savedData = null;

// CONSTANTS
const htmlString = @"

Things .center { margin-left: auto; margin-right: auto; margin-bottom: auto; margin-top: auto; }

Environment Data

Temperature: °C

Location:


Update Location:  Set Location

From: %s
    <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>
    <script>
        var agenturl = '%s';
        getState(updateReadout);
        $('.update-button button').on('click', getStateInput);

        function getStateInput(e){
            e.preventDefault();
            var place = document.getElementById('location').value;
            setLocation(place);
            $('#name-form').trigger('reset');
        }

        function updateReadout(data) {
            $('.temp-status span').text(data.temp);
            $('.locale-status span').text(data.locale);
            setTimeout(function() {
                getState(updateReadout);
            }, 15000);
        }

        function getState(callback) {
            $.ajax({
                url : agenturl + '/state',
                type: 'GET',
                success : function(response) {
                    if (callback && ('temp' in response)) {
                        callback(response);
                    }
                }
            });
        }

        function setLocation(place) {
            $.ajax({
                url : agenturl + '/location',
                type: 'POST',
                data: JSON.stringify({ 'location' : place }),
                success : function(response) {
                    if ('locale' in response) {
                        $('.locale-status span').text(response.locale);
                    }
                }
            });
        }
        $(function () {

Highcharts.setOptions({
    global: {
        useUTC: false
    }
});

// Create the chart
Highcharts.stockChart('container', {
    chart: {
        events: {
            load: function () {

                // set up the updating of the chart each second
                var series = this.series[0];
                setInterval(function () {
                    var x = (new Date()).getTime(), // current time
                        y = Math.round(Math.random() * 100);
                    series.addPoint([x, y], true, true);
                }, 15000);
            }
        }
    },

    rangeSelector: {
        buttons: [{
            count: 1,
            type: 'minute',
            text: '1M'
        }, {
            count: 5,
            type: 'minute',
            text: '5M'
        }, {
            type: 'all',
            text: 'All'
        }],
        inputEnabled: false,
        selected: 0
    },

    title: {
        text: 'Live Temperature Data'
    },

    exporting: {
        enabled: false
    },

    series: [{
        name: 'Random data',
        data: (function () {
            // generate an array of random data
            var data = [],
                time = (new Date()).getTime(),
                i;

            for (i = -999; i <= 0; i += 1) {
                data.push([
                    time + i * 15000,
                    Math.round(Math.random() * 100)
                ]);
            }
            return data;
        }())
    }]
});

});

    </script>
</body>
";

// FUNCTIONS
function postReading(reading) {
savedData.temp = format("%.2f", reading.temp);
local result = server.save(savedData);
if (result != 0) server.error(“Could not back up data”);
}

// START OF PROGRAM

// Instantiate objects
api = Rocky();

// Set up the app’s API
api.get("/", function(context) {
// Root request: just return standard web page HTML string
context.send(200, format(htmlString, http.agenturl(), http.agenturl()));
});

api.get("/state", function(context) {
// Request for data from /state endpoint
context.send(200, { temp = savedData.temp, locale = savedData.locale });
});

api.post("/location", function(context) {
// Sensor location string submission at the /location endpoint
local data = http.jsondecode(context.req.rawbody);
if (“location” in data) {
if (data.location != “”) {
savedData.locale = data.location;
context.send(200, { locale = savedData.locale });
local result = server.save(savedData);
if (result != 0) server.error(“Could not back up data”);
return;
}
}

context.send(200, "OK");

});

// Set up the backup data
savedData = {};
savedData.temp <- “TBD”;
savedData.locale <- “Unknown”;

local backup = server.load();
if (backup.len() != 0) {
savedData = backup;
} else {
local result = server.save(savedData);
if (result != 0) server.error(“Could not back up data”);
}

// Register the function to handle data messages from the device
device.on(“reading”, postReading);
server.log(savedData.temp);

`


DEVICE CODE


`
#require “Thermistor.class.nut:1.0.0”
// Assign hardware.pin5 to a global variable, therm
therm <- hardware.pin7;
led <- hardware.pin8;

// Configure pin5/therm for analog input
therm.configure(ANALOG_IN);

// These constants are particular to the thermistor we’re using.
// Check your datasheet for what values you should be using
const B_THERM = 3443.0;
const T0_THERM = 298.15;

// The resistor in the circuit (10KΩ)
const R2 = 10000.0;

// FUNCTIONS
function getData() {
// Get the temperature using the Si7020 object’s read() method
getTemp();
flashLed();
imp.wakeup(15.0, getData);
}

function getTemp() {
local vin = hardware.voltage();
local vout = vin * therm.read() / 65535.0;
local rTherm = (R2 * vin / vout) - R2;
local lnTherm = math.log(10000.0 / rTherm);
local tempK = (T0_THERM * B_THERM) / (B_THERM - T0_THERM * lnTherm);

local tempC = tempK - 273.15;
local tempF = tempC * 9.0 / 5.0 + 32.0;

local sendData = {};


sendData.temp <- tempC;
sendData.id <- hardware.getdeviceid();

agent.send("reading", sendData);

return tempC;
server.log(temp.celsius)

}
function flashLed() {
led.write(1);
imp.sleep(0.5);
led.write(0);
}

led = hardware.pin7;
led.configure(DIGITAL_OUT, 0);

therm = hardware.pin5;
therm.configure(ANALOG_IN);

getData();

`

pretty challenging question. i’m not an expert but I have achieved some similar things in Highcharts so let me try to point you toward the right direction.

First, to take a partial step you could create a global variable in your javascript by adding some lines like this

`
var agenturl = ‘%s’;

var temperature = 25;
		
function get_temperature(){
    temperature = temperature + 1;
    return temperature;
}

`

The variable and function are new, the agenturl statement is yours

Next modify the callback function you created with setInterval so that it runs the get_temperature function and retrieves the value for you.

setInterval(function () { var x = (new Date()).getTime(), // current time y = get_temperature(); series.addPoint([x, y], true, true); }, 15000);

This should create a chart that grows by 1C every 15 seconds according to your interval time. your next step would be to set the value ‘temperature’ with the data coming back from the Agent.

This is a little bit awkward because the updates are not synchronous. Another way would be to call your get request from the function you set up with the setInterval statement.

It is pretty hard to learn using the agent code as the container for your html - ideally you would have a webhost account and serve html from it…but that is another story.
good luck!

Hey mjkuwp94 thanks for responding. First of all, I’ve seen your work, and considering everything else I’ve seen online, you are arguably the world’s foremost expert at integrating highcharts with the imp.

I took the steps you suggested, as well as added the get_temperature() function to the series section of the highcharts code, and it does in fact work. Though, the temp starts at 1025, I suspect because of the way the iterator is set up in the series section.

I think I need to grab the temp in a similar way that getState(callback) does, but I’m not sure.

function getState(callback) { $.ajax({ url : agenturl + '/state', type: 'GET', success : function(response) { if (callback && ('temp' in response)) { callback(response); } } }); }

I think it’s related to the api.get() code…

api.get("/state", function(context) { // Request for data from /state endpoint context.send(200, { temp = savedData.temp, locale = savedData.locale }); });

It looks to me that api.get grabs the temperature value from the imp, while getState sends it to the agent site. Does that seem right to you? Haha, is this helping?

I tried using api.get() within the get_temperature() function like so to no avail.

`
function get_temperature(){
temperature = api.get(temp)
temperature = temperature + 1;
return temperature;
}

`

Am I way off here? Can I call values from the Rocky api like this?

I have not used the Rocky API at all, sorry.

Based on my previous suggestions I have two more notes.

1. Delete the line 'temperature = temperature + 1;

that was only there for debugging - so you could see the temperature change at each interval.

2. Add the line ‘temperature = data.temp;’ as shown in this code snippet

function updateReadout(data) { $('.temp-status span').text(data.temp); temperature = data.temp; /*so your chart callback can retrieve it */ $('.locale-status span').text(data.locale); setTimeout(function() { getState(updateReadout); }, 15000); }

No dice. Highcharts is still getting the temperature value from
`
var temperature = 20; //Initial temperature

function get_temperature(){
//temperature = temperature + 1;
return temperature;
}
`

I also tried to grab the temp by altering the getState(callback) function like this

function get_temperature(callback) { $.ajax({ url : agenturl + '/state', type: 'GET', success : function(response) { if (callback && ('temp' in response)) { callback(response); return temp; } } }); }

Also, no dice.

According to the “How to Make an Agent Serve a Dynamic Web Page” tutorial:


When we make an Ajax request with jQuery we specify a URL consisting of a base and a path; a parameter called type which defines what kind of server action we want to take (get data, send data, etc.); and a callback which will be executed when the request is complete — this goes into the success parameter. Rocky allows us to specify callbacks based on the type (or verb) and path. We are sending/receiving data pertaining to our sensor’s state, so we’ll name our path “/state”. Here is how we create our first endpoint in the agent code, using Rocky’s get() method:

api.get("/state", function(context) { context.send(200, { temp = savedData.temp, humid = savedData.humid, locale = savedData.locale }); });

If we run this agent code then use a browser to visit the /state endpoint, ie. to the URL http://agent.electricimp.com//state, we should get a response in the form of JSON that provides the latest temperature and humidity reading from the Environment Tail. This is because when you load a web page, your browser makes a GET request to the specified URL and displays the response.

We now need to code the web page to make that request repeatedly. To do this we need the URL of the server, which we know is the agent’s URL. This will be used as the base URL for all JavaScript communication between the browser and the agent. Added to the base URL is our path, which is /state. Next you need the type of request you are making. The most common HTTP request types are GET, used for requesting data from the server, and POST, which is used for sending data to the server. Lastly, since Ajax is asynchronous, you’ll need to set a callback function to handle the response from the server when it arrives.

Here is a JavaScript function to make an Ajax call to the GET /state endpoint we created in Rocky:

`

function getState(callback) {
$.ajax({
url : agenturl + ‘/state’,
type : ‘GET’,
success : function(response) {
if (callback && (‘temp’ in response)) {
callback(response.state)};
}
});
}

`


My thinking is that if getState(callback) function grabs the value from the ‘/state’, then get_temperature() can act in a similar way and pass that value to the chart.

Small update. Still not getting data to the graph, but solved one thing, the “temp” on my /state page is of course a string, and needs to be cast as a float for highcharts. The snip I’ve attached is my /state page.

New get_temperature()
`
function get_temperature(){
$.ajax({
url : agenturl + ‘/state’,
type: ‘GET’,
success : function(response) {
if (callback && (‘temp’ in response)) {
callback(response);
var temperature = document.getElementById(‘temp’).value;
temperature = temperature.tofloat()
}
}
});

    return temperature;
        }

`
Been thinking of another strategy, to dynamically change the values of an html table and have highcharts pull from there. Here’s a couple links from where I got the idea.

Ben

http://www.highcharts.com/docs/working-with-data/data-module

…forgot the snip

You may find some useful information if you dig through some working code (link at bottom). it’s old but I’ve used it a lot over the last few years. I don’t see a need to use the html table unless that is something you are really familiar with. I’ve always used javascript variables and I send the data as numbers, floats I suppose and convert using

http://www.w3schools.com/jsref/jsref_tofixed.asp

https://dl.dropboxusercontent.com/u/23332089/working%20web%20page.html

I think you want to review the series object in your Highcharts.stockChart function:

series: [{
name: ‘Random data’,
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;

            for (i = -999; i <= 0; i += 1) {
                data.push([
                    time + i * 15000,
                    Math.round(Math.random() * 100)
                ]);
            }
            return data;
        }())
    }]