IoT devices can be remote data viewers, remote data controllers, data capture devices, remote sensors, or remotely controllable endpoints for different actuators and controls. HTTP provides a great semi-standard way of calling into devices. I end up putting an HTTP API tier on all my network-connected IoT devices for either data or just for status. HTTP provides a request-response protocol. It doesn't mean you have to present web pages.
You should use an API testing tool and unit tests to exercise the device. Custom web pages present a lot better demo and are easier for us lazy types if the device can cough up some type of web page with working controls on it.
HTTP and have a lot of overhead. Know your use case. The end-to-end time for my browser request-response cycle is from 150ms to 300ms. That means the ESP8255 or RP2040 are only looking at 4 requests per second. Dropping the response or using a lower overhead protocol would have a significant impact.
More than just buttons and fields
I wanted more controls than just buttons and fields. I wanted scrollers and different kinds of combo boxes that aren't in the standard HTML toolkit. This image shows a scroller that provides a simple mechanism for specifying the servo position. It sends the desired position back to the server in degrees, values from 0 to 180 degrees.
The IoT device responds to any HTTP request with an HTML page containing markup and script references. The slider is implemented using aj
Query UI widget. We annotate the HTML wherever we want a slider. JQuery pattern matches against the markup when the page is loaded. It then manipulates the Document model onLoad() to lay in the correct styling and other behavior.
In my case, I wanted to do a GET call every time a slider moved. A script callback function bound to the same markup elements. The function is called every time the slider is manipulated. The new slider value is then sent back to the IoT device as an HTTP GET to make parsing easier.
References
Flow - Setup and Actions
A user/browser makes the initial connection to an IoT HTTP endpoint. The IoT device is either a hardcoded page or as dynamically built content. A fixed page is simpler because the javascript function binding is simpler. In my case, I wanted to be able to change the number of sensors, I/O pins, motors, lights, and other components attached to the IoT device via configuration without changing the page creation or data submission logic.
Each user interaction is an HTTP GET submission to the device. The query parameters are processed as mutation. The device returns a page that is functionally identical to the initial page.
Browsers or other tools push commands to the IoT device as query parameters in HTTP GET calls. This means that every field on the page needs to trigger a GET call. We do that two ways.
- The buttons are built as anchor tags around buttons. Clicking on the button triggers the anchor.
- Other more complex or generated controls are bound to functions when the page finishes loading.
Segregating APIs and Web Pages
We should have two request paths. One that is purely API and a second that returns HTTP markup for humans. The former should be cheaper and faster than the latter. I've been too lazy and just generate HTTP pages even when the caller doesn't need the markup.
Walking through two scrollers
Our IoT server-side code generates HTML markup, one label/div pair for each scroller. Client-side code renders slider CSS and binds functions to do other configurations.
Version 1
HTML with fixed default values
The important <div></div> pairs are the ones with ids servo_0 and server_1. Those ids will become GET query parameter names. The slider value becomes query parameter values. This markup lends itself to simpler document.ready() function bindings.
<fieldset><legend>Servo Pins</legend>
<div class=controlgroup-vertical>
<div><label><strong>Servo Pan</strong> uSec: 1800 degrees: 120</label>
<div id=servo_0></div>
</div>
<div><label><strong>Servo Tilt</strong> uSec: 900 degrees: 30</label>
<div id=servo_1></div>
</div>
</div>
</fieldset>
Creating sliders with fixed values
We create two functions.
- a document.ready() is bound to code that converts the <div></div> tag with an ID of servo_* into a slider style.
- The changeServo() function is bound to each slider creation time. The function binding causes the function to be invoked whenever the slider is moved.
<script>
function changeServo(event, ui) {
var id = $(this).attr('id'); $.get('/', {[id]: ui.value} ) }
$(document).ready(function(){ $('[id^=servo_]')
.slider({min: 0, max:180, change:changeServo}); });
</script>
Then the magic happens and the user sees scrollers where there were none before.
Server-side code loops across the list of devices
The IoT device generates HTML for each slider including the slider label and the <div></div> discussed above. This template works when all the sliders are left at the same default initial value.
<div>
<label><strong>%s</strong> uSec: %d degrees: %d</label>
<div id=servo_%d></div>
</div>
Version 2
Initializing sliders with the current value
I wanted to initialize the sliders with their current server-side values and needed a place to put the current values that could easily be picked up by the scroller construction functions.
HTML with current values
The only way I could find to do this was to put the current value inside the div tag between <div> and </div>
<fieldset><legend>Servo Pins</legend>
<div class=controlgroup-vertical>
<div><label><strong>Servo Pan</strong> uSec: 1800 degrees: 120</label>
<div id=servo_0>120</div>
</div>
<div><label><strong>Servo Tilt</strong> uSec: 900 degrees: 30</label>
<div id=servo_1>30</div>
</div>
</div>
</fieldset>
Creating sliders with variable initial values
We need an extra layer, another function binding that extracts the data in the div and erases it after setting it as a value() on the slider. Our outer function is identical to the previous one identifying all objects with id=servo_*. The inner function extracts the value from between <div> and </div> and then defines the scroller using a variant of the one above except that it sets the current slider the value that was between <div> and </div>
<script>
function changeServo(event, ui) {
var id = $(this).attr('id'); $.get('/', {[id]: ui.value} ) }
$(document).ready(function(){
$('[id^=servo_]').each(
function(){
var currentValue = $(this).text();
$(this).empty()
.slider({min: 0, max:180, change:changeServo, value:currentValue});
}
)
});
</script>
Server-side code loops across the list of devices
The IoT device generates HTML for each slider including the slider label and the <div></div> discussed above. This string demonstrates the template for the servos shown above. This template works when all the sliders are left at the same default initial value. Note the '%d' between the div tags.
<div>
<label><strong>%s</strong> uSec: %d degrees: %d</label>
<div id=servo_%d>%d</div>
</div>
Video
<TO BE CREATED>
Revision log
Create 2023 01
Comments
Post a Comment