Friday, June 17, 2016

Writing a ESP8266 based fire alarm plugin for WSO2 IoT Server

Internet of things or IoT is another big trend we are discussing on these days. There are lots of IoT ready embedded systems, microcontrollers and thousands of devices out there in today starting at very low price tags and in the other hand with much more capabilities.

Same as the IoT capable devices have evolved, IoT frameworks, device management systems and analytics systems have been developed to work with those IoT ready devices. Also there are reference architectures for IoT by defining how to create an IoT solution with capable devices by managing them efficiently and analyze collected data to extract valuable information.

WSO2 IoT Server (IoTS) is a powerful IoT framework based on IoT reference architecture introduced by WSO2 earlier. IoTS can be extended by device plugins to work with any IoT ready device in the market and it has out of the box capability to work with different types of IoT specific communication protocols that communicate with IoT devices. Also it is able to provide device management and IoT analytics with the power of WSO2 platform.

In this article, we are going to discover the capabilities of IoTS by writing a new device plugin for ESP8266 that will mimic a fire alarm device. It will be referred to as Fire Alarm as it will push Temperature and Humidity readings to IoTS while we control the device’s buzzer with the IoTS.

Please download WSO2 IoT Server 1.0.0-ALPHA from here.

You can find the source code of this sample from WSO2 IoT server sample sources.
https://github.com/wso2/product-iots/tree/master/modules/samples/firealarm

So let’s discover bit more about the ESP8266 module first.

The ESP8266 Module

ESP8266 is a popular, cheap IoT capable module which has built-in Wi-Fi and is available in the market for around $2-4. It comes with pre-programed AT command set firmware as it is originally designed to function as a WiFi module which can be connected with any microcontroller to connect with WiFi networks. However, it is possible to use another custom firmware like NodeMCU on top of the ESP8266 if needed.

ESP8266 has powerful on-board processing, 256k or higher flash storage and GPIO interface which allows it to be integrated with sensors and other application specific devices with minimal development up-front and minimal loading during runtime. Its high degree of on-chip integration allows for minimal external circuitry, including the front-end module, and is designed to occupy minimal PCB area. The ESP8266 contains built in RF components allowing it to work under all operating conditions, and requires no external RF components except the RF antenna.

Even though it has powerful capabilities, ESP8266 modules are affordable. They can be configured to work with many other sensors and actuators easily, and there are lots of examples with great community support.

ESP8266 – 201 Module with NodeMCU firmware


https://www.monwifi.fr/1249-thickbox_default/esp8266-esp201.jpg
ESP8266 – 201 module has built in voltage regulator, crystal oscillator and RF antenna connector along with ESP8266 SoC. The SDK board has connector to plug ESP 201 module and it consists with Buzzer, relay, RGB & White LEDs and DHT11 temperature & humidity sensor module. Also there is a DIP switch to connect or disconnect those sensors and actuators from GPIO whenever necessary.

As same as the other ESP8266 devices, ESP 201 module in SDK board is coming with pre-programed AT command firmware. SDK board has two USB ports and mini USB port is used to connect with the computer while micro USB port is just only used to power up the device.


Flashing ESP 201 Module with NodeMCU


We can flash NodeMCU firmware to this module as follows.
  • Download pre build NodeMCU firmware nodemcu_integer_0.9.6-dev_20150704.bin or build latest firmware using build tool and download it.
  • Download and run NodeMCU Firmware Programmer.
  • Connect SDK board to PC. You might need to install USB driver for the ESP8266 – 201 SDK board.
  • Set K1 & K2 in DIP switch array to ‘on’ and set others to ‘off’, then power on the SDK board. ESP8266 201 module will turn on now in programming mode.
  • Run ESP8266Flasher and select Config tab. Then open downloaded firmware file as follows.
  • Go to Advanced tab and set parameters as follows
  • Then go to Operation tab and click flash button. Once progress is completed NodeMCU firmware has been flashed to ESP8266.

Program ESP8266 with Lua


After flashing ESP8266 with NodeMCU firmware, turn off SDK board and set K2 to off and then turn on device again. This time device will turn on in operation mode and can be programmed with Lua scripting language.

You can follow NodeMCU documentation to get to know basic functionalities and features provided.

ESPTool can be used to program the module with Lua language. You can run “java -jar ESPlorer.jar” in terminal to start ESPTool. Please note that you might need run it as a root on linux or mac. There is a user guide available for ESPTool which covers basic functionalities and details. If you are using windows or mac to program ESP8266 201 SDK board, you need to install windows usb to serial drivers or mac usb to serial drivers.


Writing a new WSO2 IoT Server Device Type plugin for ESP8266

You can generate new device type for WSO2 IoT Server using cdmf-devicetype-archetype provided in WSO2 IoT Server. There is complete guide for Creating a New Device Type for WSO2 IoT Server. Referring to the documentation we can generate a Fire Alarm device type based on ESP8266.

Arch type properties


Define value for property 'groupId': : org.homeautomation
Define value for property 'artifactId': : firealarm
Define value for property 'version': 1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for property 'package': org.homeautomation: : org.homeautomation.firealarm
Define value for property 'deviceType': : firealarm
Define value for property 'nameOfTheSensor': : humidity

As we already having temperature sensor analytics stream inside the WSO2 IoT Server, so when creating device type we are only defining humidity sensor. After generating device type using the maven arch type, we need to modify it as specified in the Device Manufacturer Guide.

Update contents in generated device type


You can find updated fire alarm source code in WSO2 IoT Server samples directory. Here is an overview of modifications done in each component.

Analytics


Analytics
|   build.xml
│   pom.xml
|
└───src
    ├───assembly
    └───main
        └───resources
            └───carbonapps
                ├───firealarm
                │   ├───firealarm_execution
                │   ├───firealarm_publisher
                │   ├───firealarm_receiver
                │   └───firealarm_stream
                ├───humidity_sensor
                │   ├───humidity_receiver
                │   ├───humidity_script
                │   ├───humidity_store
                │   └───humidity_stream
                └───temperature_sensor
                    ├───temperature_publisher
                    ├───temperature_receiver
                    ├───temperature_script
                    ├───temperature_store
                    └───temperature_stream

We need to modify contents in analytics component as above to have firealarm, temperature and humidity carbon apps. Also firealarm carbon app is defined here to provide data receiver to receive sensor reading from the ESP8266 based fire alarm device. Then split temperature, humidity readings with execution plan and send them to temperature, humidity event streams in temperature and humidity carbon apps respectively.To build above carbon apps it is needed to update ant build file as follows and we can keep pom.xml file as it is without doing any modifications.

<project name="create-sample-sensor-capps" default="zip" basedir=".">
    <property name="project-name" value="${ant.project.name}"/>
    <property name="target-dir" value="target/carbonapps"/>
    <property name="src-dir" value="src/main/resources/carbonapps"/>
    <property name="temperature_sensor_dir" value="temperature_sensor"/>
    <property name="humidity_sensor_dir" value="humidity_sensor"/>
    <property name="devicetype_dir" value="firealarm"/>
    <target name="clean">
        <delete dir="${target-dir}"/>
    </target>
    <target name="zip" depends="clean">
        <mkdir dir="${target-dir}"/>
        <zip destfile="${target-dir}/${temperature_sensor_dir}.car">
            <zipfileset dir="${src-dir}/${temperature_sensor_dir}"/>
        </zip>
        <zip destfile="${target-dir}/${humidity_sensor_dir}.car">
            <zipfileset dir="${src-dir}/${humidity_sensor_dir}"/>
        </zip>
        <zip destfile="${target-dir}/${devicetype_dir}.car">
            <zipfileset dir="${src-dir}/${devicetype_dir}"/>
        </zip>
    </target>
</project>

We need to place above carbon app contents for firealarm, temperature_sensor and humidity_sensor carbon apps as they are in the fire alarm sample code. For more details about writing carbon apps for analytics, please refer WSO2 Data Analytics Server documentation.

API


In API DeviceTypeService.java interface, we need to change feature annotation content of changeStatus method and also need to change getSensorStats method for retrieve sensor data for the given time period since we are getting data from two sensor streams.

@Path("device/{deviceId}/change-status")
@POST
@Feature(code = "change-status", name = "Change status of buzzer: on/off",
         description = "Change status of buzzer: on/off")
@Permission(scope = "firealarm_user", 
            permissions = {"/permission/admin/device-mgt/change-status"})
Response changeStatus(@PathParam("deviceId") String deviceId,
                      @QueryParam("state") String state,
                      @Context HttpServletResponse response);

@Path("device/stats/{deviceId}/sensors/{sensorName}")
@GET
@Consumes("application/json")
@Produces("application/json")
Response getSensorStats(@PathParam("deviceId") String deviceId,
                        @PathParam("sensorName") String sensorName,
                        @QueryParam("from") long from, 
                        @QueryParam("to") long to);

As DeviceTypeServiceImpl implements DeviceTypeService interface we need to update getSensorStats method in DeviceTypeServiceImp.java as follows to retrieve sensor data from analytics tables.

@Path("device/stats/{deviceId}/sensors/{sensorName}")
@GET
@Consumes("application/json")
@Produces("application/json")
public Response getSensorStats(@PathParam("deviceId") String deviceId,
                               @PathParam("sensorName") String sensorName,
                               @QueryParam("from") long from,
                               @QueryParam("to") long to) {
    String fromDate = String.valueOf(from);
    String toDate = String.valueOf(to);
    String query = "deviceId:" + deviceId + " AND deviceType:" +
                   DeviceTypeConstants.DEVICE_TYPE + " AND time : [" + 
                   fromDate + " TO " + toDate + "]";
    String sensorTableName;
    switch (sensorName) {
        case DeviceTypeConstants.STREAM_TEMPERATURE:
            sensorTableName = DeviceTypeConstants.TEMPERATURE_EVENT_TABLE;
            break;
        case DeviceTypeConstants.STREAM_HUMIDITY:
            sensorTableName = DeviceTypeConstants.HUMIDITY_EVENT_TABLE;
            break;
        default:
            return Response.status(Response.Status.BAD_REQUEST)
                   .entity("Invalid event stream").build();
    }

    try {
        if (!APIUtil.getDeviceAccessAuthorizationService()
            .isUserAuthorized(new DeviceIdentifier(deviceId,
                              DeviceTypeConstants.DEVICE_TYPE))) {
           return Response.status(Response.Status.UNAUTHORIZED.getStatusCode())
                  .build();
        }
        List<SortByField> sortByFields = new ArrayList<>();
        SortByField sortByField = new SortByField("time", SORT.ASC, false);
        sortByFields.add(sortByField);
        List<SensorRecord> sensorRecords = APIUtil.getAllEventsForDevice(
                                           sensorTableName, 
                                           query, sortByFields);
        return Response.status(Response.Status.OK.getStatusCode())
               .entity(sensorRecords).build();
    } catch (AnalyticsException e) {
        String errorMsg = "Error on retrieving stats on table " + 
                          sensorTableName + " with query " + query;
        log.error(errorMsg);
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR
               .getStatusCode()).entity(errorMsg).build();
    } catch (DeviceAccessAuthorizationException e) {
        log.error(e.getErrorMessage(), e);
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
}

Also we need to update createZipFile method in ZipUtil.java to provide variables specified in device agent source, when downloading the agent. Other content can be use as they generated by the maven arch type.

public ZipArchive createZipFile(String owner, String tenantDomain, 
                                String deviceType, String deviceId, 
                                String deviceName, String token, 
                                String refreshToken) 
                                throws DeviceManagementException {

    String sketchFolder = "repository" + File.separator + "resources" 
                          + File.separator + "sketches";
    String archivesPath = CarbonUtils.getCarbonHome() + File.separator 
                          + sketchFolder + File.separator + "archives" 
                          + File.separator + deviceId;
    String templateSketchPath = sketchFolder + File.separator + deviceType;
    String iotServerIP;

    try {
        iotServerIP = Utils.getServerUrl();
        String httpsServerPort = System.getProperty(HTTPS_PORT_PROPERTY);
        String httpServerPort = System.getProperty(HTTP_PORT_PROPERTY);
        String httpsServerEP = HTTPS_PROTOCOL_APPENDER + iotServerIP + ":" 
                               + httpsServerPort;
        String httpServerEP = HTTP_PROTOCOL_APPENDER + iotServerIP + ":" 
                              + httpServerPort;
        String apimEndpoint = httpsServerEP;
        String mqttEndpoint = MqttConfig.getInstance().getBrokerEndpoint();
        if (mqttEndpoint.contains(LOCALHOST)) {
            mqttEndpoint = mqttEndpoint.replace(LOCALHOST, iotServerIP);
        }

        String[] mqttEPParams = mqttEndpoint.split(":");
        String mqttEPIP = mqttEPParams[1].replace("//", "");
        String mqttPort = mqttEPParams[2];

        Map<String, String> contextParams = new HashMap<>();
        contextParams.put("SERVER_NAME", APIUtil.getTenantDomainOftheUser());
        contextParams.put("DEVICE_OWNER", owner);
        contextParams.put("DEVICE_ID", deviceId);
        contextParams.put("DEVICE_NAME", deviceName);
        contextParams.put("HTTPS_EP", httpsServerEP);
        contextParams.put("HTTP_EP", httpServerEP);
        contextParams.put("APIM_EP", apimEndpoint);
        contextParams.put("MQTT_EP", mqttEPIP);
        contextParams.put("MQTT_PORT", mqttPort);
        contextParams.put("DEVICE_TOKEN", token);
        contextParams.put("DEVICE_REFRESH_TOKEN", refreshToken);

        ZipArchive zipFile = Utils.getSketchArchive(archivesPath,
                             templateSketchPath, contextParams, deviceName);
        return zipFile;
    } catch (IOException e) {
        throw new DeviceManagementException("Zip File Creation Failed", e);
    }
}

Plugin


There are no any changes to do in plugins except adding constants for Stream names and event table names to DeviceTypeConstants.java file. These constants are used in the API implementation and some of them are used in the plugin component itself.

public final static String DEVICE_TYPE = "firealarm";
public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME";
public final static String DEVICE_PLUGIN_DEVICE_ID = "firealarm_DEVICE_ID";
public final static String STATE_ON = "ON";
public final static String STATE_OFF = "OFF";

//sensor events summerized table name
public final static String STREAM_TEMPERATURE = "temperature";
public final static String STREAM_HUMIDITY = "humidity";
public final static String TEMPERATURE_EVENT_TABLE = "DEVICE_TEMPERATURE_SUMMARY";
public final static String HUMIDITY_EVENT_TABLE = "DEVICE_HUMIDITY_SUMMARY";

public static final String DATA_SOURCE_NAME = "jdbc/firealarmDM_DB";
public final static String DEVICE_TYPE_PROVIDER_DOMAIN = "carbon.super";

//mqtt tranport related constants
public static final String MQTT_ADAPTER_NAME = "temperature_mqtt";
public static final String MQTT_ADAPTER_TYPE = "oauth-mqtt";
public static final String ADAPTER_TOPIC_PROPERTY = "topic";
public static final String MQTT_PORT = "\\{mqtt.broker.port\\}";
public static final String MQTT_BROKER_HOST = "\\{mqtt.broker.host\\}";
public static final String CARBON_CONFIG_PORT_OFFSET = "Ports.Offset";
public static final String DEFAULT_CARBON_LOCAL_IP_PROPERTY = "carbon.local.ip";
public static final int CARBON_DEFAULT_PORT_OFFSET = 0;
public static final int DEFAULT_MQTT_PORT = 1883;
public static final String RESOURCE = "resource";

public static final String USERNAME_PROPERTY_KEY = "username";
public static final String DCR_PROPERTY_KEY = "dcrUrl";
public static final String BROKER_URL_PROPERTY_KEY = "url";
public static final String SCOPES_PROPERTY_KEY = "scopes";
public static final String QOS_PROPERTY_KEY = "qos";
public static final String CLIENT_ID_PROPERTY_KEY = "qos";
public static final String CLEAR_SESSION_PROPERTY_KEY = "clearSession";
public static final String MQTT_CONFIG_LOCATION = 
                           CarbonUtils.getEtcCarbonConfigDirPath() + File.separator
                           + "mqtt.properties";


UI


All necessary UI units are generated in the maven arch type when device type is generated.

Type-view


We need to update type-view.hbs file in cdmf.unit.device.type.firealarm.type-view unit to show device type detail contents in device enrollment page. Also we can add necessary images and styles to public folder inside that unit. Furthermore we need update config.json file inside the private folder of above unit to specify display name and device type category. When we generated the device type with the maven arch type, category is set to ‘virtual’ by default. As our sample is a real IoT hardware device, we are changing it to ‘iot’. Also we can specify custom uri to download the agent if necessary. In that sample, we don’t need to modify download uri.

{
  "deviceType": {
    "label": "Firealarm",
    "category": "iot",
    "downloadAgentUri": "firealarm/device/download"
  }
}

Device-view


When device type is generated with the maven arch type, it is generating necessary contents required for cdmf.unit.device.type.firealarm.device-view unit and therefore it is not needed to change anything. It is basically containing device operations, which automatically fetch with from the annotations in DeviceTypeService.java class and also contains real time analytics graphs and contents.

Real time Analytics-view


cdmf.unit.device.type.firealarm.realtime.analytics-view unit is used to provide real time data graphs in device view. It is calling from device view unit of the device type and need to update according to the number of sensors and graphs. As we have two event streams, we need to add chartWrappers to analytics-view.hbs as follows.

{{unit "cdmf.unit.lib.rickshaw-graph"}}

<div id="chartWrapper">
</div>

<div id="div-chart" data-websocketurl="{{websocketEndpoint}}">
    <div id="chartWrapper-temperature" class="chartWrapper">
        <div id="y_axis-temperature" class="custom_y_axis" 
            style="margin-top: -20px;">
                Temperature
        </div>
        <div id="chart-temperature" class="custom_rickshaw_graph">
        </div>
        <div class="custom_x_axis">
            Time
        </div>
    </div>
    <div id="chartWrapper-humidity" class="chartWrapper">
        <div id="y_axis-humidity" class="custom_y_axis" 
            style="margin-top: -20px;">
                Humidity
        </div>
        <div id="chart-humidity" class="custom_rickshaw_graph">
        </div>
        <div class="custom_x_axis">
            Time
        </div>
    </div>
</div>

<a class="padding-left"
    href="{{@app.context}}/device/{{device.type}}/analytics?
         deviceId={{device.deviceIdentifier}}&deviceName={{device.name}}">
    <span class="fw-stack">
        <i class="fw fw-ring fw-stack-2x"></i>
        <i class="fw fw-statistics fw-stack-1x"></i>
    </span> View Device Analytics
</a>

<!-- /statistics -->
{{#zone "bottomJs"}}
    {{js "js/moment.min.js"}}
    {{js "js/socket.io.min.js"}}
    {{js "js/device-stats.js"}}
{{/zone}}

Also to populate data to graphs in above template, we need to update device-stats.js file as in public/js directory follows.

var ws, temperature, temperatureData = [], humidity, humidityData = [];
var palette = new Rickshaw.Color.Palette({scheme: "classic9"});

$(window).load(function () {
    temperature = lineGraph("temperature", temperatureData);
    humidity = lineGraph("humidity", humidityData);
    var websocketUrl = $("#div-chart").data("websocketurl");
    connect(websocketUrl)
});

$(window).unload(function () {
    disconnect();
});

function lineGraph(type, chartData) {
    var tNow = new Date().getTime() / 1000;
    for (var i = 0; i < 30; i++) {
        chartData.push({
            x: tNow - (30 - i) * 15,
            y: parseFloat(0)
        });
    }
    var graph = new Rickshaw.Graph({
        element: document.getElementById("chart-" + type),
        width: $("#div-chart").width() - 50,
        height: 300,
        renderer: "line",
        padding: {top: 0.2, left: 0.0, right: 0.0, bottom: 0.2},
        xScale: d3.time.scale(),
        series: [{
            'color': palette.color(),
            'data': chartData,
            'name': type && type[0].toUpperCase() + type.slice(1)
        }]
    });
    graph.render();
    var xAxis = new Rickshaw.Graph.Axis.Time({
        graph: graph
    });
    xAxis.render();
    new Rickshaw.Graph.Axis.Y({
        graph: graph,
        orientation: 'left',
        height: 300,
        tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
        element: document.getElementById('y_axis-' + type)
    });
    new Rickshaw.Graph.HoverDetail({
        graph: graph,
        formatter: function (series, x, y) {
            var date = '<span class="date">' + 
                moment(x * 1000).format('Do MMM YYYY h:mm:ss a') + '</span>';
            var swatch = '<span class="detail_swatch" style="background-color: ' +
                series.color + '"></span>';
            return swatch + series.name + ": " + parseInt(y) + '<br>' + date;
        }
    });
    return graph;
}

//websocket connection
function connect(target) {
    if ('WebSocket' in window) {
        ws = new WebSocket(target);
    } else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(target);
    } else {
        console.log('WebSocket is not supported by this browser.');
    }
    if (ws) {
        ws.onmessage = function (event) {
            var dataPoint = JSON.parse(event.data);
            if (dataPoint) {
                var time = parseInt(dataPoint[0]) / 1000;
                graphUpdate(temperatureData, time, dataPoint[3], temperature);
                graphUpdate(humidityData, time, dataPoint[4], humidity);
            }
        };
    }
}

function graphUpdate(chartData, xValue, yValue, graph) {
    chartData.push({
        x: parseInt(xValue),
        y: parseFloat(yValue)
    });
    chartData.shift();
    graph.update();
}

function disconnect() {
    if (ws != null) {
        ws.close();
        ws = null;
    }
}

device-stats.js file connecting to websocket publishers respective to each event stream and then update graphs from the data pushed to particular websocket endpoint.

Batch analytics-view


Batch analytics are produced after executing batch analytics on top of the collected data and we can retrieve summarized data for ESP8266 device from WSO2 IoT server with this unit. As same as the real time analytics, we need to modify this unit also to show two graphs for both temperature and humidity streams. So it is needed to update contents in cdmf.unit.device.type.firealarm.analytics-view as they are in the sample code.

Agent Code


By default, the IoT server device type generator maven arch type is generating agent code which can work as a virtual agent on any device which has python. However, as we are using ESP8266 hardware module as the hardware device, we need to remove existing python code and place lua scripts for ESP2866 module inside the agent directory.

Agent code templates are located in feature/src/main/resources/agent directory of the sample. Agent code is parsed and populate variables in the template file when user download an agent. (Please refer ZipUtil.java class described in API section for more details). Finally, agent code will pack in a to zip container with populated template which is specific to device instance and available for download.

Sketch Properties


We are using property file to specify code template and the zip file name as follows.

templates=read-sensor.lua
zipfilename=firealarm.zip

DHT11 Library


To communicate with DHT11 sensor, we are using dht_lib.lua script. Once dht_lib.lua is uploaded to ESP8266 module, we can refer it as a library module. As we are using dht_lib as it is, it can be referred directly from samples.

Init Script


When ESP module is powered up with NodeMCU firmware, device automatically tried to execute init script if available. In this sample init.lua script is used to run scripts to connect with wifi and read data from the sensor.

tmr.alarm(0, 1000, 0, function()
    dofile("wifi-connect.lua");
    dofile("read-sensor.lua");
end)

WiFi Script


WiFi script has entries to connect with existing WiFi network. By default, it is with only dummy SSID and pre-shared key as shown in below. So we need to update our SSID and pre-shared key before uploading agent into device.

wifi.setmode(wifi.STATION)
wifi.sta.config("SSID", "Password")

Read Sensor Script


Entire flow of the agent is implemented in read_sensor.lua file. It has MQTT client with both subscriber and receiver topics to publish sensor data and receive command from the server.
First we need to initialize global variables and configure GPIO mode of the pin that we have already selected for the buzzer. Then to connect with mqtt broker, we need to create a mqtt client.

DHT = require("dht_lib")
dht_data = 2
buzzer = 1
gpio.mode(buzzer, gpio.OUTPUT)
client_connected = false
m = mqtt.Client("ESP8266-" .. node.chipid(), 120, "${DEVICE_TOKEN}", "")

We can set alarm to retrieve data from DHT11 sensor and send them to the WSO2 IoT Server periodically. In the data publishing alarm, we need to publish data to carbon.super/firealarm/${DEVICE_ID}/data  topic which is specific to particular device instance. To control device with the WSO2 IoT server, we also need to subscribe with carbon.super/firealarm/${DEVICE_ID}/command topic. The ${DEVICE_ID} variable with be fill with actual device id once you download the agent.

tmr.alarm(0, 10000, 1, function()
    DHT.read(dht_data)
    local t = DHT.getTemperature()
    local h = DHT.getHumidity()
    if t == nil then
        print("Error reading from DHTxx")
    else
        if (client_connected) then
            local payload = "{event:{metaData:{owner:\"${DEVICE_OWNER}\",deviceId:\"${DEVICE_ID}\"},payloadData:{temperature:" .. t .. ", humidity:" .. h .. "}}}"
            m:publish("carbon.super/firealarm/${DEVICE_ID}/data", payload, 0, 0, function(client)
                print("Published> Temperature: " .. t .. "C  Humidity: " .. h .. "%")
            end)
        else
            connectMQTTClient()
        end
    end
end)

function connectMQTTClient()
    local ip = wifi.sta.getip()
    if ip == nil then
        print("Waiting for network")
    else
        print("Client IP: " .. ip)
        print("Trying to connect MQTT client")
        m:connect("${MQTT_EP}", ${MQTT_PORT}, 0, function(client)
            client_connected = true
            print("MQTT client connected")
            subscribeToMQTTQueue()
        end)
    end
end

function subscribeToMQTTQueue()
    m:subscribe("carbon.super/firealarm/${DEVICE_ID}/command", 0, function(client, topic, message)
        print("Subscribed to MQTT Queue")
    end)
    m:on("message", function(client, topic, message)
        print("MQTT message received")
        print(message)
        buzz(message == "on")
    end)
    m:on("offline", function(client)
        print("Disconnected")
        client_connected = false
    end)
End

function buzz(status)
    local buzzerOn = true
    if (status) then
        tmr.alarm(1, 500, 1, function()
            if buzzerOn then
                buzzerOn = false
                gpio.write(buzzer, gpio.HIGH)
            else
                buzzerOn = true
                gpio.write(buzzer, gpio.LOW)
            end
        end)
    else
        tmr.stop(1)
        gpio.write(buzzer, gpio.LOW)
    end
end

Install created device type to IoT Server


To install newly created device type to IoT server, we need to place device type plugin to samples folder in the server and then update device-deployer.xml in the <IoTS_HOME> directory as follows.

<modules>
    <!-- STEP#1 ADD THE LOCATION OF YOUR DEVICE-TYPE -->
    <module>samples/firealarm</module>
</modules>

    ...

<featureArtifactDef>
     <!-- STEP#2. ADD YOUR DEVICE TYPE FEATURE HERE
          "<GROUP_ID:ARTIFACT_ID:VERSION>" -->
     org.homeautomation:org.homeautomation.firealarm.feature:1.0-SNAPSHOT
</featureArtifactDef>

    ...

<features>
    <!-- STEP#3. ADD YOUR DEVICE TYPE FEATURE GROUP HERE "<ARTIFACT_ID>.group" -->
    <feature>
        <id>org.homeautomation.firealarm.group</id>
        <version>1.0-SNAPSHOT</version>
    </feature>
</features>

Navigate to the <IoTS_HOME>, and deploy the sample device type you created with mvn clean install -f device-deployer.xml command and then start the server after installation is succeeded. Please note that you need to shutdown server if you are running, before installing the sample.

Enroll ESP8266 Device to IoT Server

Download agent


To download an agent, go to my devices page in WSO2 IoT Server web console and then click Enroll New Device button. Select Fire Alarm device from the available device types and click download agent.

Uploading agent to ESP8266 201 SDK board


Extract downloaded zip archive and upload files with ESPlorer tool to WSO2 IoT Server in following order.
  1. dht_lib.lua
  2. wifi-connect.lua (Update SSID and password for your network)
  3. read_sensor.lua
  4. init.lua
After uploading all files with ESPlorer tool, please restart the ESP8266 module with restart button available in the ESPlorer tool.

In the ESPlorer console, you can see that, agent is waiting for network first and then will trying to connect with the mqtt queue after few seconds (less than a minute). After connecting with the mqtt, agent will subscribe controller topic and then start publishing data to WSO2 IoT Server.

Manage and Monitor fire alarm device


You can navigate to device details page to manage and monitor fire alarm device. We have one control option to send a command to device, and have two graphs to display realtime data stream from the device.

Control buzzer on ESP8266 SDK board


The buzzer in the SDK board can be enable by turning on 6th dip switch in the dip switch array. After enabling the buzzer, you can select control operation and then type on in the command text box to turn on the buzzer in the SDK board and click Send to Device button. Then buzzer will beep until you turn it off. To turn off the buzzer, send off command to the device.

View real time data stream


As device is sending data periodically to the server, real time graphs for both temperature and humidity readings will update. You can notice if there any change in the values instantly on the graphs. For more details, please refer WSO2 IoT Server Realtime analytics documentation.
 

View batch analysis


WSO2 IoT Server can summarize collected data based on different scenarios. So in this sample device we are showing historical data for a selected period. You can browse and navigate historical data from both temperature and humidity streams with the batch analytics. For more details, please refer WSO2 IoT Server Batch analytics documentation.


Demo Video