How To Use Tags To Organize Data In Mango

June 16th, 2020


main.png

Let’s say that we want to monitor three HVAC systems (hvac-1, hvac-2, and hvac-3)at different locations, two of them on Site 1 (hvac-1 and hvac-2) and the last one on Site 2. Each HVAC will have next variables:

  • Current

  • Voltage

  • Temperature

  • Outside temperature

  • Temperature set point

  • Fan state (ON/OFF)

First, we need to go to Bulk data point edit page and create a tag to group HVAC data points into sites. So, we click on the + button, next to select tags dropdown:

addTag.png

A pop-up window will be opened, and we will create a siteName tag:

addTag2.png

At the end of the table, you will see a new column called Tag ‘siteName’.Now, we select the hvac-1 and hvac-2 data points, and we will assign a site name of Site 1.

site1.png

And for hvac-3 we will assign a site name of Site 2.

site2.png

We will do the same for a hvacName tag to assign a name for each HVAC system.

hvacNames.png

Now, we will create a new module and a component to show the data by site. You can check How To Develop Custom Views With AngularJS Components In Mango Automation to know how to create them in more detail. I’ll explain it real quick.

In the public folder, in File Stores, we will create the next structure inside the tagsTest folder:

- tagsTest
       - tagsTest.js
       - components
          - hvacsOverview.js
          - hvacsOverview.html
          - hvacDetails.js
          - hvacDetails.html
  • tagsTest.js will be the AngularJS module.

  • hvacsOverview.js and hvacsOverview.html will be an overview component.

  • hvacDetails.js and hvacDetails.html will be a detailed view of every HVAC system.

Bellow is the starter code of hvacsOverview component:

hvacsOverview.js

define(['angular', 'require'], function(angular, require) {
'use strict';
hvacsOverviewController.inject = ['$scope'];
function hvacsOverviewController($scope) {
    this.$onInit = () => {

    };
}
return {
    bindings: {},
    controller: hvacsOverviewController,
    templateUrl: require.toUrl('./hvacsOverview.html')
};

});

hvacsOverview.html

<h1>Overview</h1>

Bellow is the starter code of hvacDetails component:

hvacDetails.js

define(['angular', 'require'], function(angular, require) {
'use strict';
hvacDetailsController.inject = ['$scope'];
function hvacDetailsController($scope) {
    this.$onInit = () => {

    };
}
return {
    bindings: {},
    controller: hvacDetailsController,
    templateUrl: require.toUrl('./hvacDetails.html')
};

});

hvacDetails.html

<h1>Details</h1>

Next is the starter code of the module:

define([
    'angular', 
    'require',
    './components/hvacsOverview.js',
    './components/hvacDetails.js'
], 
function(angular, require, hvacsOverview, hvacDetails) {
    'use strict';
    var tagsTestModule = angular.module('tagsTestModule', ['maUiApp']);

    tagsTestModule.component('hvacsOverview', hvacsOverview);
    tagsTestModule.component('hvacDetails', hvacDetails);

    tagsTestModule.config(['maUiMenuProvider', function(maUiMenuProvider) {
        maUiMenuProvider.registerMenuItems([
            {
                name: 'ui.tagsTest',
                url: '/tags-test',
                menuText: 'Tags Test',
                menuIcon: 'list',
                abstract: true,
                menuHidden: false,
                weight: 1,
            },
            {
                name: 'ui.tagsTest.overview',
                url: '/overview?site',
                template: '<hvacs-overview></hvacs-overview>',
                menuIcon: 'fa-home',
                menuText: 'Home',
                weight: 2,
                params: {
                    noPadding: false,
                    hideFooter: false,
                },
            },
            {
                name: 'ui.tagsTest.hvacDetails',
                url: '/hvac-details?site&hvac',
                template: '<hvac-details></hvac-details>',
                menuIcon: 'insert_chart',
                menuText: 'HVAC Details',
                weight: 3,
                params: {
                    noPadding: false,
                    hideFooter: false,
                    dateBar: {
                        rollupControls: true
                    }
                },
            },
        ]);
    }]);
    return tagsTestModule;
});

The module will load the hvacsOverview and hvacDetails components, and then it will add three menu items for them.

NOTE: Remember to add this module, to User module field in UI Settings.

You will see something like this:

overview1.png

details1.png

Now, let’s start working on the Overview page. In this view, we will see an overview of HVAC systems filtered by the site.

The Overview component will have the next code:

hvacsOverview.js

define(['angular', 'require'], function(angular, require) {
'use strict';

hvacsOverviewController.$inject = ['$scope', 'maDataPointTags', '$state', '$stateParams', 'maPoint'];
function hvacsOverviewController($scope, maDataPointTags, $state, $stateParams, maPoint) {
    this.$onInit = () => {
        this.refreshSites();
    };

    this.refreshSites = () => {
        return maDataPointTags
        .buildQuery('siteName')
        .query()
        .then(values => {
            this.sites = values.sort();
            this.sites.unshift('ALL');

            if (!this.sites.includes(this.site)) {

                if (this.sites.includes($stateParams.site)) {
                    this.site = $stateParams.site;
                } else if (this.sites.length) {
                    this.site = this.sites[0];
                } else {
                    this.site = 'ALL';
                }
                this.siteChanged();
            }

        });
    };

    this.siteChanged = () => {
        this.hvacs = {};
        $stateParams.site = this.site;
        $state.go('.', $stateParams, {location: 'replace', notify: false});

        this.getHvacNames();
    };

    this.getHvacNames = () => {
        let queryBuilder = maDataPointTags.buildQuery('hvacName');

        if (this.site == 'ALL') {
            queryBuilder.ne('siteName', null);
        } else {
            queryBuilder.eq('siteName', this.site);
        }

        return queryBuilder
            .query()
            .then(values => {
                this.hvacNames = values.sort();
                this.refreshHvacs();
            });
    };

    this.refreshHvacs = () => {

        let queryBuilder = maPoint.buildQuery();

        if (this.site == 'ALL') {
            queryBuilder.ne('tags.siteName', null);
        } else {
            queryBuilder.eq('tags.siteName', this.site);
        }

        return queryBuilder
            .limit(1000)
            .query()
            .then((points) => { 
                this.orderPoints(points);
            });
    };

    this.orderPoints = (points) => {

        this.hvacNames.forEach(hvacName => {

            this.hvacs[hvacName] = {
                'temp': this.filterByNameAndHvacName(points, 'Temp', hvacName),
                'outsiteTemp': this.filterByNameAndHvacName(points, 'Outside Temp', hvacName),
                'setPoint': this.filterByNameAndHvacName(points, 'Set Point', hvacName),
                'amps': this.filterByNameAndHvacName(points, 'Amps', hvacName),
                'volts': this.filterByNameAndHvacName(points, 'Volts', hvacName),
                'fan': this.filterByNameAndHvacName(points, 'Fan', hvacName),
            };

        });

    };

    this.filterByNameAndHvacName = (points, name, hvacName) => {
        return points.filter(point => {
            return point.name == name && point.tags.hvacName == hvacName;
        })[0];
    };

}
return {
    bindings: {},
    controller: hvacsOverviewController,
    templateUrl: require.toUrl('./hvacsOverview.html')
};

});

So, we have some interesting methods:

  • $onInit: This method runs when the component has started. In this case, it runs the refreshSites() method.

  • refreshSites: It runs a handy service, which is called maDataPointTags. This service will return us an array with all the possible values for a specific tag. For siteName tag, the possible values are Site 1 and Site 2.Then, we add a new ALL value to this array to use it on a dropdown list in the view. This method will help us to filter data by site. Finally, we run siteChanged method.

  • siteChanged: It takes care of updating the siteName param in the URL on the view every time that the user changes a site, and runs getHvacNames().

  • getHvacNames: It uses maDataPointTags to get an array with all possible hvacName values on a selected site. Next, it runs refreshHvacs() method.

  • refreshHvacs: This method will return an array with all the points that belong to a selected site. Then, it runs orderPoints() method.

  • orderPoints: It finally order the data points so that we can show them in the view easier.

In the end, we get an hvacs object like this, depending on the site that we have selected:

    {
        'HVAC 1': {
            'temp': tempDatapoint...,
            'outsiteTemp': outsiteTempDatapoint...,
            'setPoint': setPointDatapoint...,
            'amps': ampsDatapoint...,
            'volts': voltsDatapoint...,
            'fan': fanDatapoint...,
        },
        'HVAC 2': {
            'temp': tempDatapoint...,
            'outsiteTemp': outsiteTempDatapoint...,
            'setPoint': setPointDatapoint...,
            'amps': ampsDatapoint...,
            'volts': voltsDatapoint...,
            'fan': fanDatapoint...,
        },
        ...
    }

Now, the component HTML file hvacsOverview.html will look like this:

<div layout="column" flex layout-wrap>
    <md-card flex>
        <md-card-content>
            <div layout="row" layout-align="space-between start" layout-wrap>
                <md-input-container flex="100" flex-gt-xs="40">
                    <label>Select site:</label>
                    <md-select ng-model="$ctrl.site" ng-change="$ctrl.siteChanged()">
                        <md-option ng-repeat="siteItem in $ctrl.sites" value="{{siteItem}}">
                            {{siteItem}}
                        </md-option>
                    </md-select>
                </md-input-container>
            </div>
        </md-card-content>
    </md-card>

    <div layout="row" flex>

        <md-card flex ng-repeat="(hvacName, hvac) in $ctrl.hvacs">
            <md-card-content>
                <h2>{{hvacName}}</h2>

                <ma-get-point-value point="hvac.temp"></ma-get-point-value>
                <h4>Temperature: {{ hvac.temp.renderedValue }}</h4>

                <ma-get-point-value point="hvac.outsiteTemp"></ma-get-point-value>
                <h4>Outsite Temperature: {{ hvac.outsiteTemp.renderedValue }}</h4>

                <ma-get-point-value point="hvac.setPoint"></ma-get-point-value>
                <h4>Set Point: {{ hvac.setPoint.renderedValue }}</h4>

                <ma-get-point-value point="hvac.amps"></ma-get-point-value>
                <h4>Amps: {{ hvac.amps.renderedValue }}</h4>

                <ma-get-point-value point="hvac.volts"></ma-get-point-value>
                <h4>Volts: {{ hvac.volts.renderedValue }}</h4>

                <ma-get-point-value point="hvac.fan"></ma-get-point-value>
                <h4>Fan Status: {{ hvac.fan.renderedValue }}</h4>
            </md-card-content>
        </md-card>

    </div>
</div>

The <md-select> component will use as options the same sites array that we got on the refreshSites method. We use the hvacs object to show the HVACs data that we want, inside a <md-card> component.

Finally, we will see the next view:

home1.png

And depending on what we select, we will see different data:

home2.png

Now, we will work in the hvacDetails view, here is the code of the component:

define(['angular', 'require'], function(angular, require) {
'use strict';
hvacDetailsController.$inject = ['$scope', 'maDataPointTags', '$state', '$stateParams', 'maPoint', 'maUiDateBar'];
function hvacDetailsController($scope, maDataPointTags, $state, $stateParams, maPoint, maUiDateBar) {
    this.$onInit = () => {
        this.maUiDateBar = maUiDateBar;
        this.refreshSites();
    };

    this.refreshSites = () => {
        return maDataPointTags
        .buildQuery('siteName')
        .query()
        .then(values => {
            this.sites = values.sort();
            this.sites.unshift('ALL');

            if (!this.sites.includes(this.site)) {

                if (this.sites.includes($stateParams.site)) {
                    this.site = $stateParams.site;
                } else if (this.sites.length) {
                    this.site = this.sites[0];
                } else {
                    this.site = 'ALL';
                }
                this.siteChanged();
            }

        });
    };

    this.siteChanged = () => {
        $stateParams.site = this.site;
        $state.go('.', $stateParams, {location: 'replace', notify: false});

        this.refreshHvacs();
    };

    this.refreshHvacs = () => {
        let queryBuilder = maDataPointTags.buildQuery('hvacName');

        if (this.site == 'ALL') {
            queryBuilder.ne('siteName', null);
        } else {
            queryBuilder.eq('siteName', this.site);
        }

        return queryBuilder
            .query()
            .then(values => {
                this.hvacNames = values.sort();

                if (!this.hvacNames.includes(this.hvac)) {

                    if (this.hvacNames.includes($stateParams.hvac)) {
                        this.hvac = $stateParams.hvac;
                    } else if (this.hvacNames.length) {
                        this.hvac = this.hvacNames[0];
                    } else {
                        this.hvac = null;
                    }
                    this.hvacChanged();
                }

            });
    };

    this.hvacChanged = () => {

        $stateParams.hvac = this.hvac;
        $state.go('.', $stateParams, {location: 'replace', notify: false});

        return maPoint
            .buildQuery()
            .eq('tags.hvacName', this.hvac)
            .limit(1000)
            .query()
            .then((points) => { 
                this.orderPoints(points);
            });
    };

    this.orderPoints = (points) => {

        this.temp = this.filterByName(points, 'Temp');
        this.outsiteTemp = this.filterByName(points, 'Outside Temp');
        this.setPoint = this.filterByName(points, 'Set Point');
        this.amps = this.filterByName(points, 'Amps');
        this.volts = this.filterByName(points, 'Volts');
        this.fan = this.filterByName(points, 'Fan');

    };

    this.filterByName = (points, name) => {
        return points.filter(point => {
            return point.name == name;
        })[0];
    };

}
return {
    bindings: {},
    controller: hvacDetailsController,
    templateUrl: require.toUrl('./hvacDetails.html')
};

});

This component is similar to hvacsOverview. The main difference is that now we have to dropdowns, one for sites and other for hvacs. Here is the HTML template code:

<div layout="column" flex layout-wrap>
    <md-card flex>
        <md-card-content>
            <div layout="row" layout-align="space-between start" layout-wrap>
                <div flex="100" flex-gt-sm="40" layout="row">
                    <md-input-container flex="100" flex-gt-xs="50">
                        <label>Select site:</label>
                        <md-select ng-model="$ctrl.site" ng-change="$ctrl.siteChanged()">
                            <md-option ng-repeat="siteItem in $ctrl.sites" value="{{siteItem}}">
                                {{siteItem}}
                            </md-option>
                        </md-select>
                    </md-input-container>
                    <md-input-container flex="100" flex-gt-xs="50">
                        <label>Select HVAC:</label>
                        <md-select ng-model="$ctrl.hvac" ng-change="$ctrl.hvacChanged()">
                            <md-option ng-repeat="hvac in $ctrl.hvacNames" value="{{hvac}}">
                                {{hvac}}
                            </md-option>
                        </md-select>
                    </md-input-container>
                </div>
            </div>
        </md-card-content>
    </md-card>

    <div layout="row" flex="100" layout-align="space-between start">
        <md-card flex="100" flex-gt-sm="50">
                <md-card-title>
                    <md-card-title-text>
                        <span class="md-headline">Temperature Variables</span>
                    </md-card-title-text>
                </md-card-title>

            <md-card-content>
                    <ma-point-values 
                        point="$ctrl.temp" 
                        values="$ctrl.tempValues" 
                        from="$ctrl.maUiDateBar.from" 
                        to="$ctrl.maUiDateBar.to" 
                        rollup="{{$ctrl.maUiDateBar.rollupType}}" 
                        rollup-interval="{{$ctrl.maUiDateBar.rollupIntervals}} {{$ctrl.maUiDateBar.rollupIntervalPeriod}}">
                    </ma-point-values>
                    <ma-point-values 
                        point="$ctrl.outsiteTemp" 
                        values="$ctrl.outsiteTempValues" 
                        from="$ctrl.maUiDateBar.from" 
                        to="$ctrl.maUiDateBar.to" 
                        rollup="{{$ctrl.maUiDateBar.rollupType}}" 
                        rollup-interval="{{$ctrl.maUiDateBar.rollupIntervals}} {{$ctrl.maUiDateBar.rollupIntervalPeriod}}">
                    </ma-point-values>
                    <ma-point-values 
                        point="$ctrl.setPoint" 
                        values="$ctrl.setPointValues" 
                        from="$ctrl.maUiDateBar.from" 
                        to="$ctrl.maUiDateBar.to" 
                        rollup="{{$ctrl.maUiDateBar.rollupType}}" 
                        rollup-interval="{{$ctrl.maUiDateBar.rollupIntervals}} {{$ctrl.maUiDateBar.rollupIntervalPeriod}}">
                    </ma-point-values>

                    <ma-serial-chart style="height: 480px; width: 100%"
                        series-1-values="$ctrl.tempValues"
                        series-1-point="$ctrl.temp"
                        series-1-title="Temperature"
                        series-2-values="$ctrl.outsiteTempValues"
                        series-2-point="$ctrl.outsiteTemp"
                        series-2-title="Outsite Temperature"
                        series-3-values="$ctrl.setPointValues"
                        series-3-point="$ctrl.setPoint"
                        series-3-title="Set Point"
                        legend="true">
                    </ma-serial-chart>

            </md-card-content>
        </md-card>

        <md-card flex="100" flex-gt-sm="50">
                <md-card-title>
                    <md-card-title-text>
                        <span class="md-headline">Electrical Variables</span>
                    </md-card-title-text>
                </md-card-title>

            <md-card-content>
                    <ma-point-values 
                        point="$ctrl.amps" 
                        values="$ctrl.ampsValues" 
                        from="$ctrl.maUiDateBar.from" 
                        to="$ctrl.maUiDateBar.to" 
                        rollup="{{$ctrl.maUiDateBar.rollupType}}" 
                        rollup-interval="{{$ctrl.maUiDateBar.rollupIntervals}} {{$ctrl.maUiDateBar.rollupIntervalPeriod}}">
                    </ma-point-values>
                    <ma-point-values 
                        point="$ctrl.volts" 
                        values="$ctrl.voltsValues" 
                        from="$ctrl.maUiDateBar.from" 
                        to="$ctrl.maUiDateBar.to" 
                        rollup="{{$ctrl.maUiDateBar.rollupType}}" 
                        rollup-interval="{{$ctrl.maUiDateBar.rollupIntervals}} {{$ctrl.maUiDateBar.rollupIntervalPeriod}}">
                    </ma-point-values>

                    <ma-serial-chart style="height: 480px; width: 100%"
                        series-1-values="$ctrl.ampsValues"
                        series-1-point="$ctrl.amps"
                        series-1-title="Amps"
                        series-2-values="$ctrl.voltsValues"
                        series-2-point="$ctrl.volts"
                        series-2-title="Volts"
                        legend="true">
                    </ma-serial-chart>

            </md-card-content>
        </md-card>

    </div>
</div>

This component will give us a view like this:

details2.png

details3.png

Copyright © 2020 Radix IoT, LLC.