IoT Light Sensor
deepstream's HTTP API is perfect for low-frequency data updates in low-power environments where the cost of establishing and maintaining a WebSocket connection can be prohibitive.
In this tutorial we'll use a remote, low-power ESP8266-based system-on-chip and a light sensor to send live light readings to deepstream and display them on a webpage. Additionally, red and green LEDs will show whether the update has been successful.
I'd recommend being familiar with the basics of Records before you start.
Here's how it looks:
Hardware​
- Get hold of a board with an ESP8266 chip. There are many such boards available through merchants and action sites for as little as $5. The one we're using is listed as an "Elegiant Nodemcu Lua ESP8266 ESP 12E" and cost €10 delivered.
- An electronics breadboard and jumper cables.
- A photoresistor/LDR like this one.
- 1 x 20kΩ resistor (a pull-up for the photoresistor).
- 2 x status LEDs – 1 red, 1 green.
- 2 x 330Ω resistors for the LEDs.
The circuit is very simple to put together, just make sure the LEDs are correctly oriented.
Setting up​
We'll be using the Arduino IDE to program the device.
- The Arduino IDE is available from here.
- To get your board setup with Arduino IDE I recommend following a guide like this one and try to load up an example sketch like Blink.
- For OSX Yosemite I needed to install the CH340G driver available from here.
- With the board now setup, go to
Sketch > Include Library > Manage Libraries...
and search for the ArduinoJson, then click on Install.
Go to File -> New
to create a new sketch.
The empty sketch defines two functions:
setup()
contains initialization code that is run once when the board is powered on.loop()
is run repeatedly until the device is switched off.
To make debugging easier, we can enable debugging over a serial connection.
To do so, simply add the following to the setup()
function:
Serial.begin(115200);
and open Tools > Serial Monitor
to see any output generated.
Reading the sensor​
To start with, we'll want to read the sensor.
The analog input pin is the one labelled A0
, and a global variable of the
same name is defined.
We need to initialize this as an input before we read from it:
const int sensorPin = A0;
void setup() {
Serial.begin(115200);
// initialize sensor
pinMode(sensorPin, INPUT);
}
Now in loop()
we can use the function analogRead(int pin)
to read the value
on the sensor and Serial.printf()
to print the value.
The variable readDelayMs
defines the amount of time between readings in milliseconds.
The value will be an integer between 0 and 1024 corresponding to the brightness level.
const int readDelayMs = 10000;
void loop() {
int level = analogRead(sensorPin);
Serial.printf("Light level: %d\n", level);
delay(readDelayMs);
}
If you build and upload the script now and look in the Serial Monitor
window
you should see log lines, with the value changing as the light level changes e.g.
Light level: 270
Light level: 373
Light level: 384
Connecting to WiFi​
To submit this data to deepstream we'll need an internet connection, and for that we'll use the chip's builtin WiFi and networking functionality.
Include the following headers:
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
Now we need to initialize the WiFi client, and wait for the connection to be setup:
ESP8266WiFiMulti WiFiMulti;
const char* ssid = "YOUR_NETWORK_SSID";
const char* password = "YOUR_NETWORK_PASSWORD";
void setup() {
// ...
// connect to WiFi
WiFiMulti.addAP(ssid, password);
}
void loop() {
if (WiFiMulti.run() != WL_CONNECTED) {
delay(200);
return;
}
// ...
}
Building a request​
We're going to be writing the light level into a
record each time it's read, so let's create a
function called updateRecord
that takes the level as an argument, and call it
in loop()
.
void loop() {
// print level...
updateRecord(level);
// delay
}
void updateRecord(int level) {
// ...
}
You'll also need to select the relevant TLS fingerprint that relates to the subdomain in your HTTP URL, or you can follow the instructions here to generate your own:
const char* deepstreamHttpUrl = "<YOUR HTTP URL>";
/*
* Generated TLS fingerprints:
*
* 013.deepstream.com: "3A:FC:6E:78:94:18:C0:A2:36:F3:C7:DF:86:27:4B:5A:CA:CF:28:3F"
* 035.deepstream.com: "57:18:5A:22:07:94:03:EF:90:C9:C2:56:58:C9:BB:06:66:A6:EA:76"
* 154.deepstream.com: "3C:65:CA:7C:3F:43:2D:FF:A1:63:38:F3:23:D5:59:25:E4:85:8C:0F"
*/
const char* deepstreamTlsFingerprint = "<YOUR HTTP DOMAIN FINGERPRINT>";
We have to create a new HTTPClient
for each message, so we'll create that in
updateRecord()
and make sure it's closed after.
HTTPClient http;
// configure client
http.begin(deepstreamHttpUrl, deepstreamTlsFingerprint);
// ...
http.end();
The deepstream HTTP API uses a JSON payload, so to help us build that we'll
include the ArduinoJSON
library we installed earlier.
The body we're creating needs to look like this:
{
"topic": "record",
"action": "write",
"recordName": "readings/light-level",
"path": "value",
"data": 534 /* value read from the sensor */
}
Here's the code to do that:
#include <ArduinoJson.h>
void updateRecord(int level) {
// ...
// create message body
StaticJsonBuffer<200> bodyBuff;
JsonObject& root = bodyBuff.createObject();
JsonArray& body = root.createNestedArray("body");
JsonObject& message = body.createNestedObject();
message["topic"] = "record";
message["action"] = "write";
message["recordName"] = "readings/light-level";
message["path"] = "value";
message["data"] = level;
// copy object into array
size_t bodySize = bodyBuff.size();
char requestBody[bodySize];
root.printTo(requestBody, bodySize);
}
Now let's put this in a POST request:
void updateRecord(int level) {
// ...
// set content type
http.addHeader("Content-Type", "application/json");
// make request
int httpCode = http.POST(requestBody);
}
Handling failure​
There are three main ways the record update could fail:
The request could fail e.g. a connection error In this case
httpCode
will be negative.The request could fail to parse or authenticate on the server. In this case
httpCode
will be a 4xx response.The record update could fail e.g. the Valve permissions to not allow writes In this case
httpCode
will be 200, but the JSON response will indicate a failure.
Let's handle those and log the outcome:
void updateRecord(int level) {
// ...
if(httpCode == HTTP_CODE_OK) {
// parse response
String payload = http.getString();
StaticJsonBuffer<200> respBuff;
JsonObject& resp = respBuff.parseObject(payload);
if (!resp["body"][0]["success"]) {
// failed to update record
Serial.printf("Record update error: %s\n", resp["body"][0]["error"]);
return;
}
// record update success
Serial.println("Record was updated successfully!");
} else if (httpCode < 0) {
Serial.printf("Request failed, error: %s\n", http.errorToString(httpCode).c_str());
} else {
Serial.printf("Error response %d: %s\n", httpCode, http.getString().c_str());
}
}
Now let's set up the green LED to flash if the update is successful, the red LED otherwise:
const int greenLed = D1;
const int redLed = D2;
void setup() {
// ...
// initialize LEDs
pinMode(redLed, OUTPUT);
pinMode(greenLed, OUTPUT);
digitalWrite(redLed, LOW);
digitalWrite(greenLed, LOW);
}
void flashLed(int led) {
digitalWrite(led, HIGH);
delay(500);
digitalWrite(led, LOW);
}
void updateRecord(int level) {
// httpCode will be negative on error
if(httpCode == HTTP_CODE_OK) {
// parse payload
// ...
if (!resp["body"][0]["success"]) {
// failed to update record
Serial.printf("Record update error: %s\n", resp["body"][0]["error"]);
flashLed(redLed);
return;
}
// record update success
Serial.println("Record was updated successfully!");
flashLed(greenLed);
} else if (httpCode < 0) {
Serial.printf("Request failed, error: %s\n", http.errorToString(httpCode).c_str());
flashLed(redLed);
} else {
Serial.printf("Error response %d: %s\n", httpCode, http.getString().c_str());
flashLed(redLed);
}
}
Subscribing to updates​
Now let's display those updates as they happen using Javascript and log them to the console:
<head>
<script src="http://code.deepstream.io/js/latest/deepstream.min.js"></script>
<script type="text/javascript">
const ds = new DeepstreamClient('localhost:6020')
ds.login()
const record = ds.record.getRecord('readings/light-level')
record.subscribe('value', (value) => {
console.log('Light level update:', value)
})
</script>
</head>
This simple setup has all the elements required to aggregate and display readings from millions of incoming sensors.
For the full code, please take a look at the GitHub repository.