Skip to main content

How To Use An Agent As A Simple Web UI Server

Harness The power Of The Agent And Ajax/jQuery

Each imp-enabled device’s agent isn’t merely a go-between that relays data from the device to the wider Internet and back again, it can also operate as a web server too. An agent-hosted site isn’t going to capable of dealing with significant volumes of traffic, but as the basis for an interactive front-end for controlling the device and displaying data generated by the device, it is ideal.

The reason an agent can operate as a web server is that it communicates using the standard web protocol HTTP/HTTPS. As such, it can respond to a request from any web browser with a chunk of HTML. With no file system at its disposal and limited memory, the agent isn’t capable of hosting more than a handful of pages, each stored in a Squirrel string, but that’s usually sufficient for some basic web apps.

As it stands, HTTP is essentially a request-driven protocol: the browser asks for a resource; the server returns that resource. The server can’t push resources out to the browser. That’s not much help for a dynamic user interface that needs to be regularly updated: no one wants to be refreshing the page to get updates — they want the page to refresh itself. Fortunately, a number of additional web technologies have emerged that overcome this limitation to permit a more two-way communication between browser and server (or, in this case, Electric Imp agent), and we’ll make use of them in the following guide.


A simple, but dynamic UI hosted by an Electric Imp agent

The best way to show you how an agent can host a dynamic user interface is by example. What we’ll develop here is an application that runs on the Electric Imp impExplorer™ Kit. It integrates a temperature and humidity sensor that relay the data they generate to the agent, which presents it on the web page the agent hosts — and keeps updated with new readings as they’re received.

To make this happen, we’re going to use Ajax, a technique for dynamically updating a page without having to fully reload it. To simplify communication between the page and the agent, we’ll be using two libraries: jQuery, a JavaScript framework that will simplify the code in our webpage, and Rocky, an Electric Imp library that makes it easy to build the web server API.

jQuery

Browsers implement the Ajax API (and many others) differently. jQuery was designed to abstract away painful browser differences. Using jQuery’s Ajax API is as simple as passing an object with your request parameters into the $.ajax() function.

To use jQuery you can either download the library or find a web-hosted copy — we’ll be using a hosted copy. In your HTML code before your application’s JavaScript <script> tag include a further <script> tag with source set to the relative path (if local) or URL (if web hosted):

<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>

Incidentally, the HTML itself, including the above JavaScript tags and the code we’ll add shortly, is all placed in a string variable in your agent code. We use Squirrel’s @ symbol to indicate this is a literal string as this preserves our line breaks and tabbing, making the HTML source easy to read.

Rocky

Rocky is a framework for building powerful and scalable APIs for imp-powered devices. While it’s possible to handle HTTP requests using the imp API’s http object, Rocky provides a solution that tracks requests and handles responses, and is easy to set up.

To use Rocky, include the library at the beginning of your agent code and then create an instance of Rocky. All Rocky methods will be called using this instance.

#require "Rocky.class.nut:2.0.1"
api <- Rocky();

Rocky includes methods to handle the most common types of HTTP request, and we will be using these in this example. The documentation for Rocky can be found here.

Reading Data

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) {
    // Request for data from /state endpoint
    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/<AGENT_ID>/state, we should get a response in the form of JSON that provides the latest temperature and humidity reading from the impExplorer’s sensor. 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) {
        callback(response);
      }
    }
  });
}

Sending Data

Our example envisages multiple sensors in many locations, all sending out temperature and humidity readings. You might have one at home and another in your office, or sensors in different rooms in your house. So we know which sensor is which, we’ll give it a property locale, which you may have noticed in the Rocky method defined above.

An the Ajax side, this means we need to make a POST request and we’ll make it to a new endpoint, /location. Our handler will use Rocky’s post() method, and pass in "/location" as the first parameter and a callback for the second. In a POST request instead of using the callback to send data, we want to confirm the data we received from our web page is valid. If everything checks out, we pass the data on to the device and let the page know the data was received, otherwise we let the page know there was an error with the data.

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 != "") {
            // We have a non-zero string, so save it
            savedData.locale = data.location;
            context.send(200, { locale = data.location });
            return;
        }
    }

    context.send(200, "OK");
});

Here’s the communicating JavaScript function:

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

Not surprisingly, the variable place contains the information we want to send to the server. The parameter’s value is added to a JSON structure and then converted to a query string. This way when we decode the JSON string on the agent side, our data will be formatted as a table.

When we get a succesful response back, Ajax’s success callback updates the page. It checks that it has received data to display — the key locale in the response — and then writes the value of locale into the page HTML.

Creating A Dynamic Web Page

Now that our web page can communicate with our agent, we need to update our page with the data we have received. JavaScript allows us to grab and change specific HTML elements using their tag, class and/or ID.

In our HTML, we add a header to display the temperature status. We add a class of "temp-status" and a <span> tag to update a specific text area inside the header:

<h4 class='temp-status text-center'>Temperature: <span></span>&deg;C</h4>

We repeat this for the humidity reading and to display the sensor’s recorded location:

<h4 class='humid-status text-center'>Humidity: <span></span> per cent</h4>
<h4 class='locale-status text-center'>Location: <span></span></h4>

The Ajax requests we programmed need a callback function that takes the data we received and updates our page. Let’s write one using jQuery that selects and updates the text in our temp-status and other headers based on the data we received:

function updateReadout(data) {
  $('.temp-status span').text(data.temp);
  $('.humid-status span').text(data.humid);
  $('.locale-status span').text(data.locale);

  // Set a time to update the status readout in two minutes' time
  setTimeout(function() {
    getState(updateReadout);
  }, 120000);
}

Putting It All Together

Running the code causes the device to take regular sensor readings from the impExplorer’s HTS221 sensor and post them to the agent. In between readings, the device goes into deep sleep — handy, since the impExplorer can be battery powered.

The agent meanwhile operates continuously awaiting a request from a web browser. When one comes, it returns the web page, which presents the current sensor data and a field in which the user can enter the sensor’s location. Should they do so, the agent stores the location string in its persistent storage (we don’t want it to forget the location if it is restarted for some reason) and our jQuery code ensures that the view in the browser is updated automatically with the new value.

Likewise, it automatically requests the sensor’s current temperature and humidity readings; when they change, the web view is updated accordingly, all without user intervention — and all thanks to the agent’s roles as processor of device data and web server.

The Code

Agent Code

Device Code