In the previous post, we looked at creating a simple service with Muon and using that to feed event data to the Photon Event Store. Now we’re going to use that stored data to generate simulated sensor data and use a ReactJS front-end and MuonJS Gateway to display that data. MuonJS comprises a gateway service on the server and a Muon client runs in the browser. I’ll be using it to subscribe to streams, though as it is still a work-in-progress I cannot currently emit events from the browser back to the gateway. That feature is still being worked on.

I already defined our sensor activity event:

{
   "id": "sensor0001",
   "value": <int>,
   "time": <timestamp>
}

Our sensor data service is very similar to the previous sensor service:

var muoncore = require('muon-core');

var amqpurl = process.env.MUON_URL || "amqp://muon:microservices@rabbitmq"

var muon = muoncore.create('sensors', amqpurl);

let sensors = [];

const subscribe = () => {

    muon.replay('registered-sensors', {}, (event) => {
            switch(event['event-type']) {
                case 'sensor-connected':
                    console.log(event.payload);
                    sensors.push(event.payload.id);
                    console.log(sensors);
                    break;
            }
        },
        (error) => {
            console.log('stream error');
            console.dir(error)
            setTimeout(() => {
                subscribe();
            }, 2000)
        },
        () => {
            console.log('stream ended');
            setTimeout(() => {
                subscribe();
            }, 2000)
        }
    )
}

const perform = () => {
    var rand = Math.round(Math.random() * 5000) + 1000;

    if(sensors.length > 0) {
        setTimeout(function () {
            let s = sensors[Math.floor(Math.random() * sensors.length)].slice();
            let now = new Date().getTime();

            let payload = {
                "sensorid": s,
                "value": Math.floor(Math.random() * 100) + 1,
                "time": now
            }

            let promise = muon.emit({
                "stream-name": 'sensor-data-' + s,
                "event-type": 'sensor-data',
                "service-id": 'sensor-data',
                "payload": payload
            });
            promise.then(function (event) {
                console.log(event);
            }).catch(function(error) {
                console.dir("FAILED< BOOO ")
                console.dir(error);
            });
        }, rand);
    }

}

(function loop() {
    var rand = Math.round(Math.random() * 4000) + 600;
    setTimeout(function() {
        perform();
        loop();
    }, rand);
}());

subscribe();

As you can see, we subscribe to the registered-sensors stream (using the replay command this time) and listen for sensor-connected events, which are used to populate an array of sensor ids. I haven’t accounted for the possibility of dupes in the stream, which is bad practice, but I’m not concerned about that at the moment as I’m entirely in control of what the stream contains.

Next we have a perform function, which emits a random value for a random sensor. If I was being a bit more clever I could tweak this a little to send values based on the sensor type. I have muon emitting to a seperate stream for each sensor, in the format of sensor-data-<sensorid>, which makes it a lot easier to manage on the front-end. I had originally designed this to emit all the sensor data on one stream, but that quickly became an overwhelming torrent of unrelated data when viewing any individual sensor in isolation.

One of the advantages of event-based systems is that you can transform your data however you wish, which will come in handy at a later point in these articles.

Finally we have a little closure to execute a randomly timed loop of sensor emissions.

As you can see, unlike the last time, we haven’t subscribed to our own stream in this service. There’s no point to it here, as we’re not trying to maintain any sort of consistency across service instances.

The Front End

We’re developing the minimum necessary front-end to display our sensors for now. I won’t be showing you how to wire up a complete ReactJS application here as that is far, far beyond the scope of this article. Instead I’ll show the relevant components in detail. You can infer the rest.

First, lets start with a sensor component.

Sensor

import React from 'react'
import { Link } from 'react-router'
import { Glyphicon } from 'react-bootstrap'

class Sensor extends React.Component {

    constructor() {
        super();

        this.state = {
            timer: null,
            sensorVal: 0
        }

        this.types = [
            'generic',
            'temp',
            'audio'
        ];

        this.timeouts = {
            temp: -1,
            generic: 4,
            audio: 0.5
        }
    }

    loadTime(val, type) {

        this.endTime()

        if(this.types.indexOf(type) == -1) {
            type = 'generic';
        }

        if(this.timeouts[type] !== -1) {
            let timeout = this.timeouts[type] * 1000
            var timer = setTimeout(function () {
                this.setState({timer: null, sensorVal: 0});
            }.bind(this), timeout);
        }
        this.setState({sensorVal: val, timer: timer});
    }

    endTime() {
        if(this.state.timer !== null) {
            clearTimeout(this.state.timer);
        }
    }

    componentWillReceiveProps(props) {

        var current = this.props.value;
        var next = props.value;

        if(typeof current !== 'undefined') {
            if (current !== next) {
                this.loadTime(next, props.type);
            }
        }
    }

    componentWillUnmount() {
        this.endTime();
    }

    loading() {
        return (
            <sensor className={"sensor pull-left"}>
                <div className="indicator-pad">
                    <div className="square">
                        <Glyphicon glyph="refresh" className="spinner-loading"/>
                    </div>
                </div>
                <div>Loading...</div>
            </sensor>
        )
    }

    sensor() {

        var tint = '#fff';
        if(this.state.sensorVal > 0) {
            if(this.state.sensorVal < 30) {
                // up to half
                tint = '#ffcc99';
            } else if (this.state.sensorVal < 60) {
                tint = '#ff6600';
            } else {
                tint = '#ff0000';
            }
        }

        let type, title, sensorName;

        if(this.types.indexOf(this.props.type) == -1) {
            type = 'generic';
        } else {
            type = this.props.type;
        }

        title = (<Link to={"/sensor/" + this.props.id}>Sensor: {this.props.id}</Link>)

        sensorName = (<div className="sensor-name">{title}</div>)

        return (
            <sensor className="sensor pull-left">
                <div className="indicator-pad">
                    <div className="square">
                        <div className="indicator" style={ { height: this.state.sensorVal+"%", background: tint } }></div>
                        <div className={"sensor-type " + type}></div>
                    </div>
                </div>
                {sensorName}
            </sensor>
        )
    }

    render() {
        if(this.props.value == 0 && this.props.type == 'loading') {
            return this.loading();
        } else {
            return this.sensor()
        }
    }
}

export default Sensor

I’ve picked up this idea of separating the stateful component from the displaying component somewhere along the way (probably here). It helps to separate these concerns whenever possible, retrieving data in one component and using it to update the props of a second, rather than messing with the state and the data consumption in the same component.

So we have a dumb sensor, which only acts on what it knows from its props. It receives three props – id, value, and type – and displays the incoming value for a set period of time (or permanently in the case of a temperature display).

Sensor Controller

import React from 'react'
import Sensor from './Sensor';

// Muon

var Muon = require("muonjs");

class SensorController extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            sensor: {
                value:0,
                id:this.props.id,
                time: 0
            }
        }

        this.stream = null;
    }

    sensorUpdate(payload, time = 0) {
        let state = {sensor: payload};
        let now = new Date().getTime();
        if(time >= (now - 5000)) {
            this.setState(state);
        }

    }

    connectSensorStream() {
        let streamName = 'sensor-data-'+this.props.id;

        this.stream = this.muon.subscribe('stream://photon/stream', {"stream-name": streamName},
            function(event) {
                switch(event['event-type']) {
                    case 'sensor-data':
                        this.sensorUpdate(event.payload, event['event-time']);
                        break;
                }
            }.bind(this),
            function(error){
                console.log(error);
                setTimeout(function() {
                    this.connectSensorStream();
                }.bind(this), 1000)
            }.bind(this),
            function() {
                // Stream closed
                setTimeout(function() {
                    this.connectSensorStream();
                }.bind(this), 1000)
            }.bind(this))
    }

    componentWillMount() {
        // stream name
        this.muon = Muon.client({port: 9898});
        this.connectSensorStream();
    }

    render() {
        let sensor = this.props.sensor;

        return (
            <Sensor value={this.state.sensor.value} id={sensor.id} type={sensor.type} />
        );

    }
}

export default SensorController;

Our controller component receives an input sensor object and uses that to subscribe to an event stream for that sensor. It then passes that data as props to the sensor display component.

The sensorUpdate function is set up to ignore anything from 5 seconds before now, but you can turn that off and have a pretty flicker of incoming values.

The App

import React from 'react'
import Sensor from './SensorController';

// Muon
var Muon = require("muonjs");

class SensorsPage extends React.Component {

    constructor(props) {
        super(props);
        this.subscriptions = {
            sensors: {cancel:() =>{}}
        }

        this.state = {
            sensors: {}
        }
    }

    connectSensor(sensor) {
        let s = Object.assign({}, this.state.sensors, {[sensor.id]: sensor})
        this.setState({sensors: s});
    }

    componentWillMount() {
        // Monitor rooms I guess?
        this.muon = Muon.client({port: 9898});
        this.subscribeSensors();
    }

    componentWillUnmount() {
        let subs = this.subscriptions;
        subs.sensors.cancel();
    }

    subscribeSensors() {
        this.subscriptions.sensors = this.muon.subscribe('stream://photon/stream', {"stream-name": "registered-sensors"},
            function (event) {
                switch(event['event-type']) {
                    case 'sensor-connected':
                        this.connectSensor(event.payload);
                        break;
                }
            }.bind(this),
            function (error) {
                console.dir(error);
            },
            function () {
                console.log('Stream Completed');
            }
        );
    }

    render() {

        return (
            <div>
                <h2>Sensors</h2>
                <div>
                    {
                        Object.keys(this.state.sensors).map(function(key){
                            return <Sensor id={key} key={key} sensor={this.state.sensors[key]} />
                        }.bind(this))
                    }
                </div>
            </div>
        );
    }
}

export default SensorsPage;

And finally our App component consumes sensor-connected events in order to populate our sensor page.

You’ll also need some form of routing and some styling to make the sensors look readable. React Router is good enough for the job. At the end I’ll provide a link to a complete github repo with a working example of the UX, as well as a a docker-compose file for the services it relies on.

Because it is passively listening for events on a stream, any new sensors that are added will populate the moment they arrive. In future it could be fun to see how to use Redux and some jiggery-pokery to create an alert for the user so that they can review the sensors before they’re added.

Where Next?

The relationship is very simple at the moment, with no feedback running back up to other components, but you can likely see some advantages to this arrangement already. Each component effectively acts as its own service, consuming events as it receives them, without any sort of knowledge of or connection to other components/services. The mapping between components and services feels natural and intuitive. At least to me.

The one thing we don’t have on this side of the MuonJS gateway is the ability to emit events back through the gateway, which is a future promised feature. We can, however, use RPC to send some information back.

One of the great things about this arrangement is that it marries perfectly with the event-driven lifecycle of Flux and Redux. A component can consume an event from muon and then emit events in Redux that other components consume. Eventually they will be able to emit events in Muon as well, closing the event loop and fully integrating the UX as just another set of services. In the near future we’ll be looking at how to use Redux to set up communication between our components, and then using Redux and an RPC-based Muon service called Aether to create a simple user authorisation system.