---
title: "Using the IoT Dashboard App"
slug: "using-the-iot-dashboard-app-1"
description: "Build an app to visualize sensor data via MQTT, with alerts for thresholds, using Node.js, TypeScript, and NavVis IVION for a dynamic dashboard experience."
tags: ["API Integration", "Frontend API", "MQTT Broker", "Sensor Data"]
updated: 2024-03-11T12:44:27Z
published: 2024-03-11T12:44:27Z
canonical: "knowledge.navvis.com/using-the-iot-dashboard-app-1"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://knowledge.navvis.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Using the IoT Dashboard App

Using the steps described here, you can develop an app that allows you to get sensor data over MQTT, plot the data in POI details dialogs and show the ones that have exceeded an adjustable threshold. Default POIs can be used alongside custom sensor POIs.

![](https://cdn.document360.io/bf174766-fa1a-4fe1-a4d7-b1db1e7cb996/Images/Documentation/using-the-iot-dashboard-app-image-kryxunim.png)

This example has three separate modules that operate individually: The frontend, MQTT broker, and the backend.

The frontend is built using the NavVis IVION API. The backend uses Node.js and simulates the sensors. Both modules are written with TypeScript. MQTT broker is a Docker container with RabbitMQ MQTT Broker installed.

To use the Internet of Things (IoT) Dashboard App, [download the sample app](https://ivion-api.docs.navvis.com/latest/samples/iot-dashboard.zip).

## Backend

The backend is a simple Node.js app that simulates the sensors.

`TemperatureSensor` and `PressureSensor` classes represent a sensor instance. They generate pseudo-realistic sensory data that is generated within a range. When the data is generated, it is published to the corresponding MQTT topic. (`/iot/temperature/{sensor_id}`**or `iot/pressure/{sensor_id}`).

The backend application also exposes several endpoints. `/temperature/{sensor_id}` lists a temperature sensor's historical data. `/pressure/{sensor_id}` lists a pressure sensor's historical data.

The sensor ids can be listed using `/temperature/` or `/pressure/`**endpoints.

You can run the backend using the following commands in the `iot-dashboard-backend` folder:

```plaintext
npm install
npm start
```

## Setting up MQTT Broker

MQTT Broker is a standard Docker container with RabbitMQ MQTT broker installed.

### Procedure

> **Note:**The broker needs to be configured to support MQTT over WebSockets. Make sure ports 1883 and 15675 are accessible.

1. Create the container:

```plaintext
sudo docker run --hostname navvis-rabbitmq --name navvis-rabbitmq -p 5672:5672 -p 15672:15672 -p 1883:1883 -p 15675:15675 -td rabbitmq:3
```
2. Enter the container terminal:

```plaintext
sudo docker exec -it navvis-rabbitmq bash
```
3. Enable required plugins:

```plaintext
rabbitmq-plugins enable rabbitmq_management
rabbitmq-plugins enable rabbitmq_web_mqtt
```
4. Exit the container terminal:

```plaintext
exit
```
5. Create a new file named**`rabbitmq.conf`.

```plaintext
loopback_users.guest = false
listeners.tcp.default = 5672
management.tcp.port = 15672

mqtt.listeners.tcp.default = 1883
## Default MQTT with TLS port is 8883
# mqtt.listeners.ssl.default = 8883

# anonymous connections, if allowed, will use the default
# credentials specified here
mqtt.allow_anonymous  = true
mqtt.default_user     = guest
mqtt.default_pass     = guest

mqtt.vhost            = /
mqtt.exchange         = amq.topic
# 24 hours by default
mqtt.subscription_ttl = 86400000
mqtt.prefetch         = 10
```
6. Replace the config file in the container with the new config file:

```plaintext
sudo docker cp rabbitmq.conf navvis-rabbitmq:/etc/rabbitmq/
```

## Frontend

The frontend module is responsible for getting sensor data via MQTT, plotting the data in POI details dialogs and showing the ones that have exceeded an adjustable threshold.

POIs that exceed a certain threshold will enter alert mode. Their icon will change and they will be displayed in the site menu dashboard.

This is accomplished by defining new POI groups and types. In this case `Temperature` is the POI group and `Temperature&nbsp;Normal`**and**`Temperature&nbsp;Alert `are the types that belong to the group.

POIs that have a sensor attached to them switch to the Alert POI type when the sensor exceeds a certain threshold value. They will return back to the Normal POI type when the sensor no longer exceeds the threshold.

Default POIs can be used alongside custom sensor POIs.

What the frontend does can be summarized as follows:

1. Find all nearby POIs that have a sensor attached to them.
2. Subscribe to the MQTT topics of the nearby POIs.
3. Update the POIs according to the incoming data.
4. Periodically update the site menu dashboard with POIs that have exceeded their threshold values.

The app contains reusable components. The three main components are: `PoiChart`, `PoiConfiguration`, `SidebarMenuDashboard`. It also has several helper services: `PoiHelperService`, `SensorDataHelperService`, `ToggleablePoiService`, and `CommunicationService`.

### Running the Frontend

To run the frontend, go to the `iot-dashboard-app` folder while NavVis IVION is working in the background and run:

```plaintext
npm install
npm run serve
```

### Components

The app has three main components (`PoiChart`, `PoiConfiguration`, `SidebarMenuDashboard`) and an abstract parent class `PoiDialogModule`.

#### PoiChart

`PoiChart` is a class that contains all the functions that are needed to create and append a chart to a POI dialog. It extends the parent class `PoiDialogModule`.

![](https://cdn.document360.io/bf174766-fa1a-4fe1-a4d7-b1db1e7cb996/Images/Documentation/image-1702384061329.png)

Its main function `drawChart`**receives three parameters.

```plaintext
// ./src/js/components/poi-chart/PoiChart.ts
...
public drawChart(chartData: FormattedSensorDatum[], threshold: number, yDomain: number[]): void
...
```

- `chartData` is the historical data of the current POI. It is listed by the backend using `/temperature/{sensor_id}` or `/pressure/{sensor_id}` endpoints and formatted using `SensorDataHelperService`.
- `threshold` is a number that is used to draw the threshold line in the chart.
- `yDomain` is the range for which the chart will be drawn. For example, if the `yDomain` is [10,100] the chart will have a minimum value of 10 and a maximum value of 100 on the y-axis.

The example below shows how to use `PoiChart`.

```plaintext
// ./src/js/index.ts
...
this.poiChart.drawChart(this.historicalData, this.threshold, this.SENSOR_TYPES[type].range);
...
```

#### PoiConfiguration

`PoiConfiguration` is a class that appends a configuration panel with various input fields to the POI dialog. It extends the parent class `PoiDialogModule`.

![](https://cdn.document360.io/bf174766-fa1a-4fe1-a4d7-b1db1e7cb996/Images/Documentation/components-image-h1qllv0k.png)

Its main function `addToDialog` receives one parameter.

```plaintext
// ./src/js/components/poi-configuration/PoiConfiguration.ts
...
public addToDialog(configs: Configuration): InputBoxesDictionary
...
```

`configs` is the configuration template that is used to define the input fields.

```plaintext
// ./src/js/components/poi-configuration/PoiConfiguration.ts
...
export type Configuration = {
  [key: string]: {
      label: string,
      type: ConfigsType,
      min?: number,
      max?: number,
      options?: HTMLOptionElement[],
      value: number
  };
}
...
```

The `addToDialog` function returns a dictionary of HTMLElement representing the input field boxes. The key of each entry corresponds to the key of the `Configuration` object.

The example below shows how to use `PoiConfiguration`.

```plaintext
// ./src/js/index.ts
...
const configs: Configuration = {};
configs["threshold"] = {
    label: "Threshold",
    type: ConfigsType.SLIDER,
    min: this.SENSOR_TYPES[poiGroup].range[0],
    max: this.SENSOR_TYPES[poiGroup].range[1],
    value: customData.threshold || this.SENSOR_TYPES[poiGroup].threshold
};
configs["sensorId"] = {
    label: "Sensor ID",
    type: ConfigsType.DROPDOWN,
    options: options,
    value: customData.sensorId || 0
};
const inputBoxes = this.poiConfiguration.addToDialog(configs);
...
```

#### SidebarMenuDashboard

`SidebarMenuDashboard` is a class that contains all the functions needed to create a site menu sensor dashboard.

> [!NOTE]
> **Note:**Learn more about adding custom menu items to the site menu here.

Its main function `refreshItems` receives one parameter.

```plaintext
// ./src/js/components/sidebar-menu-dashboard/SidebarMenuDashboard.ts
...
public refreshItems(pois: PoiInterface[]): void
...
```

`pois` is the `PoiInterface`**array that will be shown in the site menu.

```plaintext
// ./src/js/index.ts
...
const poisOnAlert: PoiInterface[] = [];
for (const poi of Object.values(this.nearbyPois))
{
      const customData: PoiCustomData = JSON.parse(poi.customData);
      if (customData.value > customData.threshold)
      {
    	poisOnAlert.push(poi);
      }
}
this.sidebarMenuDashboard.refreshItems(poisOnAlert);
...
```

### Services

The app has four main services: `PoiHelperService`, `SensorDataHelperService`, `ToggleablePoiService`, and `CommunicationService`.

#### PoiHelperService

`PoiHelperService` contains helper functions to fetch or modify POIs.

It has four public functions.

1. `savePoi `saves a POI to the server.

```plaintext
// ./src/js/services/PoiHelperService.ts
/**
* Save the POI to the server.
* @param {PoiInterface} poi POI to be saved
* @returns {Promise<PoiInterface>} A promise with the saved POI object from the server.
*/
public savePoi(poi: PoiInterface): Promise<PoiInterface>
{
  return this.ivApi.poi.repository.save(poi).then((pois) =>
  {
    const savedPoi = pois[0];
    return savedPoi;
  });
}
```
2. `savePois` saves acPOI array to the server.

```plaintext
// ./src/js/services/PoiHelperService.ts
/**
* Save the POIs to the server.
* @param {PoiInterface[]} pois POIs to be saved
* @returns {Promise<PoiInterface>} A promise with the saved POI objects from the server.
*/
public savePois(pois: PoiInterface[]): Promise<PoiInterface[]>
{
  return this.ivApi.poi.repository.save(pois).then((pois) =>
  {
    const savedPois = pois;
    this.ivApi.poi.service.refreshPois();
    return savedPois;
  });
}
```
3. `updateCustomData` updates the `customData` of a POI. `customData` holds the threshold value, sensor id and sensor value.

```plaintext
// ./src/js/services/PoiHelperService.ts
/**
* Update the customData of a specific POI.
* @param {number} id Poi id
* @param {string} customData Poi custom data
* @returns {Promise<PoiInterface>} A promise with the saved POI object from the server.
*/
public updateCustomData(id: number, customData:string): Promise<PoiInterface>
{
  return this.ivApi.poi.repository.findOne(id).then((poi) =>
  {
    poi.customData = customData;
    return this.savePoi(poi).catch((e) => console.error(e));
  });
}
```
4. `initPoiTypes` checks whether required POI types and groups exist. If they do not exist, it creates them.

#### SensorDataHelperService

`SensorDataHelperService` contains the functions needed to organize and format sensor data. It has three functions.

1. `formatSensorData `formats data taken from the backend

```plaintext
// ./src/js/services/SensorDataHelperService.ts
/**
* Formats the sensor data, removes unnecessary fields, changes the timestamp to a JS Date object.
* @param {SensorDatum[]} data Unformatted sensor data array
* @returns {FormattedSensorDatum[]} Formatted sensor data array
*/
public formatSensorData(data: SensorDatum[]): FormattedSensorDatum[]
{
  const formattedData: FormattedSensorDatum[] = [];
  for (let i = 0; i < data.length; i++)
  {
    formattedData[i] = {
      value: data[i].value,
      date: new Date(data[i].timestamp)
    }
  }
  return formattedData;
}
```
2. `getArrayWithLimitedLength` returns an empty array with a maximum length. If the array tries to exceed that limit, the last element gets popped.

```plaintext
// ./src/js/services/SensorDataHelperService.ts
/**
* Gives an array with a maximum length. If the array tries to exceed that limit, the last element gets popped.
* Used for storing sensor historical data.
* @param {number} length Maximum desired length of the array
* @returns {FormattedSensorDatum[]} Array with limited length
*/
public getArrayWithLimitedLength(length: number): FormattedSensorDatum[]
{
  const array: FormattedSensorDatum[] = [];
  array.push = function()
  {
    if (this.length >= length)
    {
      this.shift();
    }
    return Array.prototype.push.apply(this, arguments);
  };
  return array;
}
```
3. `isSensor` checks whether a POI group supports adding sensors.

```plaintext
// ./src/js/services/SensorDataHelperService.ts
/**
 * Checks whether the given POI group supports adding sensors.
 * @param {string} poiGroup POI group name
 * @param {SensorTypeDictionary} sensorTypes POI group name
 * @returns {boolean} true if POI group supports sensors
 */
public isSensor(poiGroup: string, sensorTypes: SensorTypeDictionary): boolean
{
  return (Object.keys(sensorTypes).includes(poiGroup));
}
```

#### ToggleablePoiService

`ToggleablePoiService` contains all the functions that are needed to create a toggleable POI. In the current system the POI type (Temperature, Pressure) is POI group type and POI states (Alert, Normal) are POI types.

It has two functions.

1. `switchPoiToOn` sets the POI type to alert.

```plaintext
// ./src/js/services/ToggleablePoiService.ts
public switchPoiToOn(poi: PoiInterface): PoiInterface
```
2. `switchPoiToOff` sets the POI type to normal.

```plaintext
// ./src/js/services/ToggleablePoiService.ts
public switchPoiToOff(poi: PoiInterface): PoiInterface
```

#### CommunicationService

`CommunicationService` contains helper functions to fetch data from a remote location. It has a main function called `fetch `and several functions that are dependent on the app.

`fetch` is used to send a HTTP GET request to a remote location. It returns a promise in the type of the output data.

```plaintext
// ./src/js/services/SensorDataHelperService.ts
/**
 * Fetch some data from an endpoint. Sends HTTP GET request, with response type = json.
 * @param {string} endpoint POI to be saved
 * @template T
 * @returns {Promise<T>} A promise that holds the response of the GET request.
 */
private fetch<T>(endpoint: string): Promise<T>
```

`CommunicationService` additionally has two app dependent public functions that use `fetch`: `fetchHistoricalData` and `fetchSensorIds`.

## NavVis IVION Functionality

The following functionalities are used to build this app.

### View API

The View API is used to set the primary view of the application to 2D Map mode.

```plaintext
this.ivApi.view.service.setPrimaryView(ViewType.MAP);
```

It is used to detect when the camera is moved to another location using the `onTransitionEnd` signal.

```plaintext
this.ivApi.view.service.onTransitionEnd.connect(() =>
{
  this.subscribeToNearbyPois();
});
```

It is used to get the ThreeJS camera instance to detect the distance between the camera and a POI.

```plaintext
const camera = this.ivApi.view.mainView.getCamera();
```

### UI API

The UI API is used to add new sidebar menu items.

```plaintext
this.ivApi.ui.sidebarMenuService.items.push(this.sidebarMenuDashboard.dashboard);
```

The `closeMenu` method is used to close the sidebar menu.

```plaintext
this.ivApi.ui.sidebarMenuService.closeMenu();
```

### POI API

The POI API is used extensively throughout the application.

`onPoiOpen` signal is used to check when a POI is clicked. It is used to append custom elements to the default POI dialog.

`onPoiDelete` is used to clean up the subscribed MQTT topics and several arrays after a POI delete.

`onPoiClose` is used to clean up the current (open) POI variable.

`onPoiSave` is used to assign default threshold and sensor id values if a newly created POI is in a type that supports sensor data.

`findAll` is used to query all POIs.

```plaintext
this.ivApi.poi.repository.findAll().then((pois) => { ... }
```

The `findOne` method is used to query one POI with its id.

```plaintext
this.ivApi.poi.repository.findOne(id).then((poi) => { ... }
```

The `unhighlightPois` method is used to unhighlight given POIs.

```plaintext
this.ivApi.poi.service.unhighlightPois(pois);
```

The `highlightPois` method is used to highlight given POIs.

```plaintext
this.ivApi.poi.service.highlightPois(nearbyPois);
```

The `goToPoi` method is used to move the camera next to the POI.

```plaintext
this.ivApi.poi.service.goToPoi(poi).catch((err) => {console.log(err)});
```

The `openPoi` method is used to open the POI dialog.

```plaintext
this.ivApi.poi.service.openPoi(poi);
```

The `save` method is used to save a given POI or POI array to the server.

```plaintext
return this.ivApi.poi.repository.save(poi).then((pois) =>
{
  const savedPoi = pois[0];
  return savedPoi;
});
```

Point Of Interest

Application Programming Interface

The Internet of Things describes physical objects with sensors, processing ability, software, and other technologies that connect and exchange data with other devices and systems over the Internet or other communications networks.

User Interface
