Build

A Guide To Real-time IoT Analytics: Dashboards(2/2)

5 min read Syed Ahmed on May 21, 2018

In Part One of our Guide To Real-time IoT Analytics, we showed you how to connect your devices to the PubNub network so you could stream updates and trigger action in real time. In this part, we’ll visualize that real-time IoT data with a live visualization.

To build our interface, we’re going to use React Native. This lets us ship our interface on both Android and iOS and reduces our coding time. Luckily for you, PubNub already has an SDK for React which we can plug into React Native.

Getting Started

First, we’ll need to prime our development environment by downloading and installing Node.js. Then we can create a new react-native project and name it something interesting. I’m going to name it Hermes:

$ npm install -g create-react-app
$ create-react-app Hermes

This will create a new folder which we can navigate to.

$ cd Hermes

Here we’ll find a pre-made file structure that is configured to be the most streamlined way to get up and running. To verify our app is running, we can run this in the terminal:

$ npm start

The terminal then gives us options to view our project. We’ll use Expo, which is a viewer for React Native that runs on both iOS and Android.

Opening up the explorer, we see that we’re greeted by a screen that feeds us a bit of information about what we can do and where we can go to edit files.

New React Native App Screenshot

Let’s take the screens advice and start exploring our project folder. To start off open your App.js file and you’ll find that there’s a main View component with some text inside. Feel free to fiddle around, but now that we have our react-native app running, we’re going to establish a connection to the channel/channel group.

Code Walkthrough

First, make sure you have the PubNub library for React. Running the following command in your terminal should get you going:

$ npm install --save pubnub pubnub-react

Now that you have that, you can start by creating a folder called Containers and inside here we are going to create our screens.

The first screen we’re going to create, we’ll call home.js and it will allow the user to see the various IoT devices connected. The second screen we’re going to create is analytics.js which will also be in the same folder, and it will let users see the specific data for the device they selected.

Building the Views: Home.JS

constructor(props) {
    super(props);
    this.pubnub = new PubNubReact({
        publishKey: 'pub-c-7805ed36-f5af-48a4-9574-224a779d3416',
        subscribeKey: 'sub-c-2583d912-4f1e-11e8-9796-063929a21258'
    });
    this.state = {
      channels: []
    }
    this.pubnub.init(this);
}

In our componenWillMount function we are going to subscribe to our channel group. This will be the same name that we made in our IoT sided python script in the previous post. Following the docs, we’ll end up with a function looking similar to this:

componentWillMount() {
    this.pubnub.subscribe({
        channel_group: 'parcel',
        withPresence: true
    });
    this.pubnub.channelGroups.listChannels(
      {
          channelGroup: "parcel"
      }, 
      (status, response) => {
          if (status.error) {
              console.log("error:", status);
              return;
          }
          response.channels.forEach( (channel) => {
              this.setState({ 
                channels: [...this.state.channels, channel] 
              });
          })
      }
  );
}

We also want to make sure that we unsubscribe whenever we don’t need the channel/channel group. We can do that by unsubscribing in the componentWillUnmount function.

componentWillUnmount() {
    this.pubnub.unsubscribe({
        channel_group: 'parcel'
    });
}

Then, in our render function will populate our view with the different channel groups.

  render() {
    return (
      <View style={{flex:1}}>
        <ScrollView
        style={{backgroundColor:'#fff'}}>
          <List containerStyle={{marginBottom: 20}}>
            {
              this.state.channels.map((channel, i) => (
                <Card
                  key={i}
                  title={channel}
                  image={require('../images/pi.png')}>
                  <Text style={{marginBottom: 10}}>
                    Click the View More button to see real-time analytics for your IoT device!
                  </Text>
                  <Button
                    onPress={ () => this.props.navigation.navigate('Analytics', channel) }
                    icon={{name: 'code'}}
                    backgroundColor='#D32F2F'
                    buttonStyle={{borderRadius: 0, marginLeft: 0, marginRight: 0, marginBottom: 0}}
                    title='View More' />
              </Card>
              ))
            }
          </List>
        </ScrollView>
      </View>
    );
  }
}

Don’t worry too much about styling. If you are unfamiliar with the packages you can go to the GitHub page and get that version running and play around with it.

Navigation

The next thing we need to look at is how we can navigate between multiple screens. The best way to do this is by using the react-navigation package.

We can begin by creating a file named Nav.js file in the root of our project. In here we can create a StackNavigator and define the different screens we are going to have in our app and add the styling we like. by the end, Nav.js should look something like this:

import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, Image, ScrollView } from 'react-native';
import { createStackNavigator } from 'react-navigation';
import Home from './Containers/Home';
import Analytics from './Containers/Analytics';
export default Nav = createStackNavigator(
  {
    Home: { 
      screen: Home, 
      navigationOptions: ({ navigation }) => ({
        title: 'Available Devices', 
        headerTintColor: '#fff',
        headerStyle: {
          backgroundColor: '#D32F2F',
        },
      }),
    },
    Analytics: { 
      screen: Analytics,
      navigationOptions: ({ navigation }) => ({
        title: `${ navigation.state.params } data`,
        headerTintColor: '#fff',
        headerStyle: {
          backgroundColor: '#D32F2F',
        },
      })
    },
  },
);

But before our navigation works, we need to change the render function of our App.js so that it includes our new navigation component. Which would turn out to be like this:

render() {
    return (
      <View style={{flex:1}}>
        <Nav />
      </View>
    );
  }

That should allow our screens to be connected, and now we can start making our analytics view.

Building the Views – Analytics.JS

This view is going to be fairly similar to our home screen, with some difference in terms of our componentWillMount function. This is because now we’re going to be looking for the messages that are being sent in the channel.

You’re also probably wondering how are we able to know which channel is being used. If you remember from the home screen, when we set the navigation, we also passed in the current channel of the item. This way, in our analytics we now have context of which item we need data for.

constructor(props) {
        super(props);
        this.pubnub = new PubNubReact({
            publishKey: 'pub-c-7805ed36-f5af-48a4-9574-224a779d3416',
            subscribeKey: 'sub-c-2583d912-4f1e-11e8-9796-063929a21258'
        });
        this.pubnub.init(this);
        this.data = {};
    }
    componentWillMount() {
        this.pubnub.subscribe({
            channels: [this.props.navigation.state.params],
            withPresence: true
        });
        this.pubnub.addListener({
            status: function(statusEvent) {
            },
            message: (message) => {
                if(message.message != this.data){
                    this.data = message.message;
                    this.forceUpdate();
                }
            },
            presence: function(presenceEvent) {
            }
        })
    }
    componentWillUnmount() {
        this.pubnub.unsubscribe({
            channels: [this.props.navigation.state.params]
        })
    }

The only thing left now is to make our render view to show the data we’re receiving from the channel. Again, feel free to style this however you like.

render() {
        const { Temperature } = this.data
        return (
            <View style={{
                flex: 1,
                flexDirection: 'column',
                justifyContent: 'center',
                alignItems: 'center',
                backgroundColor:  Temperature ? `rgb(255, 87,${Math.floor(Temperature/3) * 34})` : 'rgb(255, 87, 34)'
              }}> 
                <Text style={{color: '#FFFFFF'}} h2>Temperature (C)</Text>
                <Text style={{color: '#FFFFFF'}} h3>{Temperature}</Text>
            </View>  
        );
    }

Now we can run our react project again.

$ npm start

Congrats!

We now have a fully-functional IoT analytics platform running. I would love to see what improvements you make to the platform. Be sure to fork my repo and leave a comment!

0