Building a smart (IoT) product is more an art than it is a science because it is about unifying the physical world with the digital one. When you put a piece of hardware on the web, magic happens. But one device on the web is one thing. Think about tens of them, interconnected, forging an automated ecosystem of pure reverie. Now imagine tens of thousands of these ecosystems spread throughout the globe. Seems profound, no? But developing an IoT product and then managing it is just as profoundly difficult. It involves development across a huge technology stack (your IoT hardware itself, your apps to monitor/control your hardware and a server back-end to manage them) to make such products work in production. And then in production comes the hardest challenge; you are going to have to scale it up as your user base grows.

Therefore making your system event-driven goes a long way and simplifies processes as you scale. An event-driven system is highly scalable and resilient to failures as components are loosely coupled and can be scaled independently. One part of the system does not need to know about other parts. An event can be generated at one point of the system and is processed at several other points without the source or sink knowing about any who or hows. This makes it a very powerful choice for distributed architectures.

And in the IoT world where thousands of nodes can be connected via web, having event-drive in your system quickly becomes crucial. Switching off most of your appliances and regulating others to power-saving mode when you go out on vacation, fire alarm notifying all members of the house about a could-be emergency, theft-detecting sensors enabling your building's security system and updating the guards about the status — you can't achieve these use cases efficiently, if at all, solely by polling.

And that's why Grandeur is designed as an event-driven architecture (EDA). Devices can listen for updates from their users, users can listen for updates from their devices, users can publish updates to their devices which the devices can react to instantly, and devices can publish updates which the users can make decisions on. And all of this happens almost instantly (with latency of ~200ms 😮).

This tutorial will help you get comfortable with the EDA of Grandeur. Like the last time, we will build an app to publish data to a device in real-time, but this time, we'll put the event-driven nature of Grandeur to use. This tutorial is part of a whole introductory series and we urge you to checkout our previous one too where we simply published some data to a device through polling. If you've read it already or just want to skip over, keep reading 👇.

Step 1: Getting Started

We will continue from where we left off and use the same project and user-device pair we created then. We were sending random data from web app periodically after every five seconds and were receiving it on hardware-end by polling every five seconds. That method, while gets the work done, had one inherent problem. Since you have to check for the updates every few seconds, critical ones may not reach your device as frequently as they need to be. There will always be a delay equal to the time difference between when you polled and when the update really occurred. This time we'll do things in a better way. Instead of polling to get updates from the cloud, we will put a listener which will get us the updates as soon as they occur.

Step 2: Web App

Just like the last time, we have two files: index.html and main.js and the code remains pretty much the same.

Here's the index.html:

<!-- @file: Index.html -->

<!DOCTYPE html>
<html>
  <!-- Head block of our page-->
  <head>
    <!-- Title of our page-->
    <title>First Grandeur App</title>

    <!-- Link to SDK's CDN -->
    <script src="https://unpkg.com/grandeur-js"></script>
  </head>
  
  <!-- Body block of our page-->
  <body>
    <!-- Heading of the page -->
    <h1>Events with Grandeur</h1>
    <p id="status">Starting</p>

    <!-- Link to the main script file -->
    <script src="./main.js"></script>
  </body>
</html>

and the main.js:

/*
    @file: main.js

    Initialize the SDK and get
    a reference to the project
*/

var project = grandeur.init("YOUR-API-KEY", "YOUR-ACCESS-KEY", "YOUR-ACCESS-TOKEN");

var timer = null;

/* Setting the connection status update handler */
project.onConnection((status) => {
  /* 
      This callback gets fired
      whenever the connection status
      changes.
  */

  switch(status) {
    case "CONNECTED": 
        /*
            If SDK is connected,
            we set the status.
        */
        document.getElementById("status").innerText = "Connected";

        /* Here we set up the timer to update data every 5 seconds */
        timer = setInterval(async function() { 
            /* 
                This function updates the device parameters
                and set the state to a random string.
            */
            
            var deviceID = "YOUR-DEVICE-ID";

            /* Here we use *Date* for a random state value */
            var state = Date.now();
            
            /* Gets reference to devices class */
            var devices = project.devices();

            /* Updates the device state */
            await devices.device(deviceID).data().set("state", Date.now());
   
            /* Logs the state to browser's console */  
            console.log(state);
        }, 5000);
        
        break;
    default: 
        /* If SDK gets disconnected, we display the status
           on the app and clear the timer.
         */
        document.getElementById("status").innerText = "Disconnected";

        /* Clears timer */
        clearInterval(timer);
  }
});

/* Function to login user */
async function login() {
    /* Store credentials into variables */
    var email = "EMAIL";
    var password = "PASSWORD";

    /* Set the status to logging in */
    document.getElementById("status").innerText = "Logging in";

    /* Then get a reference to auth class */
    var auth = project.auth();

    /* and in a try catch block */
    try {
        /* Submit request using login function */
        var res = await auth.login(email, password);

    /* 
        Got the response to login
        handle response
    */
    switch(res.code) {
      case "AUTH-ACCOUNT-LOGGEDIN": 
      case "AUTH-ACCOUNT-ALREADY-LOGGEDIN":
        /*
            User Authenticated
            Set the status to success
        */
        document.getElementById("status").innerText = "User Authenticated";
        break;

      default: 
        /* 
            Logging failed due
            to invalid data
        */
        document.getElementById("status").innerText = "Authentication Failed";
    }
  }
  catch(err) {
    /*
        Error usually got generated when
        we are not connected to the internet
    */
    document.getElementById("status").innerText = "Network Error";
  }
}

/* Call login on startup */
login();

This app uses the hard-coded user credentials to log in the user's account. After logging in, it tries to establish real-time connection with our project on the cloud. When the connection is established, a timer starts updating the state variable in device's data with five seconds interval.

Step 3: Device Program

We start from the same Hardware.ino as we created last time. But this time instead of polling for the data, we subscribe to the data update event in setup. This is the final code 👏

/* Including the SDK and WiFi header */
#include <Grandeur.h>
#include <ESP8266WiFi.h>

/* Configurations */
String deviceID = "YOUR-DEVICE-ID";
String apiKey = "YOUR-APIKEY";
String token = "YOUR-ACCESS-TOKEN";

/* WiFi credentials */
String ssid = "WIFI-SSID";
String password = "WIFI-PASSWORD";

/* Create variable to hold project and device */
Project project;
Device device;

/* Function to check device's connection status */
void onConnection(bool status) {
  switch(status) {
        case CONNECTED:
          /* Device connected to the cloud */
          
          Serial.println("Device is connected to the cloud.");
          return;

        case DISCONNECTED:
          /* Device disconnected to cloud */
          Serial.println("Device is disconnected from the cloud.");

          return;
  }
}

/* Function to handle update in device state */
void updateHandler(const char* path, bool state) {
    /* Print state */
    Serial.printf("Updated state is %f\n", state);

}

/* Function to connect to WiFi */
void connectWiFi() {
    /* Set mode to station */
    WiFi.mode(WIFI_STA);

    /* Connect using the ssid and password */
    WiFi.begin(ssid, password);

    /* Block till WiFi connected */
    while (WiFi.status() != WL_CONNECTED) {
         delay(500);
         Serial.print(".");
    }
    
    /* Connected to WiFi so print message */
    Serial.println("");
    Serial.println("WiFi connected");

    /* and IP address */
    Serial.println(WiFi.localIP());
}

/* In setup */
void setup() {
    /* Begin the serial */
    Serial.begin(9600);

    /* Connect to WiFi */
    connectWiFi();
    
    /* Initializes the global object "grandeur" with your configurations. */
    project = grandeur.init(apiKey, token);
    
    /* Get reference to device */
    device = project.device(deviceID);
    
    /* Sets connection state update handler */
    project.onConnection(onConnection);
    
    /* Add event handler on data update */
    device.data().on("state", updateHandler);
}

/* Loop function */
void loop() {
    /* Synchronizes the SDK with the cloud */
    project.loop(WiFi.status() == WL_CONNECTED);
}

As soon as the device connects with WiFi, it uses the access tokento establish real-time connection with our project on the cloud. Since we have stored state in data, we use the on function of the SDK to subscribe to data update and set an update handler function to run our post-update code.

void on(Callback dataUpdateHandler)

Calling on sets up a listener on the cloud. Whenever the state variable is updated on the cloud, the callback dataUpdateHandler is called right away with its updated value.

Step 4: Testing Locally

It's time to test our little product on our local system before making it live through a hosting service. We'll be using grandeur CLI for that 😁. If you have npm, you can easily install the CLI with the following command:

npm install grandeur-cli -g

And initialize it with:

grandeur init

Running this command in your workspace folder and selecting your project when your browser's window opens will associate your folder with your project.

CLI init.gif

To test our app, we serve it

grandeur serve

This starts a local server in our folder and opens our web app in browser. And we see our app eventually getting connected with the cloud. Then we plug our ESP into the laptop and open the serial monitor where we see our device establishing connection with the cloud 🙌

Events with Grandeur Cloud.gif

Conclusion

In this article, we updated a variable (state) from the app and and the update was propagated to our device which was listening for it. In a similar fashion, variables can be updated from the device-end and the app receives the updates if it's listening for them on the other end. Besides setting up update handlers on device variables, the app can also listen for updates to device name, online status or to the list of user's devices (which is updated when a device is paired/unpaired with the user).

So this is it. About 99% of all communications in IoT happens as events and now you've mastered the art. Go build your own production grade IoT apps and hardware. Grandeur comes with a decent free tier and we make money only when you make money. This is ideal for an IoT startup just starting out.

This tutorial is part of an introductory series and the best is still being brewed. In the next tutorial, we'll go a step ahead of local development and see how you can deploy your web apps and host them on Grandeur with a single command.

You can go through the docs or checkout our youtube channel to learn to use the event-drive in complement with other features and build pompous apps in a snap. Ask your questions and give us your feedback at hi@grandeur.tech. Join us on slack for a direct communication with us.