Hey,
in the previous post I discussed the basic implementation of an air quality monitoring system based on Raspberry Pi Zero W and a couple of sensors plus suitable software for storing and providing the data. In this post I will briefly discuss the web application created for visualizing the measurement values.
As I basically want to learn something whenever implementing hobby projects, I usually select some technologies that I am not familiar with. This time I wanted to have a look at Angular2 with Typescrupt, used with ExpressJS and ChartistJS, a responsive chart library. As basis I selected a suitable boilerplate providing a passable skeleton for the app.
The objective is to visualize air quality measurement data gathered by Raspberry Pi and stored to a No SQL database to a server. The data is accessible through a RESTful API and is therefore well suited for this exercise. The app architecture is very simple: there is a model providing the basis for measurement results (see the code listing below), a service accessing data over the Internet through the REST API and a Angular App where the visualizing component draws the chart and views it on the browser.
The model contains the fields that are stored to the database at the moment, a timestamp, a value for the temperature and the humidity.
export class Measurement { date: Date; temperature: number; humidity: number; constructor(measurementInfo:any) { this.date = measurementInfo.date; this.temperature = measurementInfo.temperature; this.humidity = measurementInfo.humidity; } }
The service class in turn reads the server URL from the configuration, specifies in the headers that the accepted content-type is JSON etc. Additionally the class contains the implemented methods, in this case we have getters for all the measurements, for a set of latest measurements, and on the other hand a get method for obtaining a single measurement by its id. These methods are accessible from the Angular component where the service is injected.
import { Injectable } from '@angular/core'; import { Http, Response, Headers } from '@angular/http'; import 'rxjs/add/operator/map' import { Observable } from 'rxjs/Observable'; import { Measurement } from './Measurement' import { Configuration } from '../app.constants'; @Injectable() export class DataService { private actionUrl: string; private baseUrl: string; private headers: Headers; constructor(private _http: Http, private _configuration: Configuration) { this.actionUrl = _configuration.ServerWithApiUrl + 'measurements/'; this.baseUrl = _configuration.ServerWithApiUrl; this.headers = new Headers(); this.headers.append('Content-Type', 'application/json'); this.headers.append('Accept', 'application/json'); this.headers.append('Access-Control-Allow-Origin', '*'); } public getAll = (): Observable<Measurement[]> => { return this._http.get(this.actionUrl) .map((response: Response) => <Measurement[]>response.json()); } public getLatest = (): Observable<Measurement[]> => { return this._http.get(this.baseUrl +'latest_measurements/') .map((response: Response) => <Measurement[]>response.json()); } public getSingle = (id: number): Observable<Measurement> => { return this._http.get(this.actionUrl +id) .map((response: Response) => <Measurement>response.json()); } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } }
In the component class we specify e.g. which providers are accessible, the base template for the page to be viewed. If you are not familiar with the Angular syntax, it is worthwhile of reading some tutorials. The essential part in this case is the div tag where the chart component is placed to the page.
The method ngOnInit() is executed when the class is initialized, and it handles the download of the latest measurements. When the REST call is finished and if a successful response is returned, the values for the chart are set. For this there are three arrays specified: one for labels (timestamps) and the further ones for the temperature and humidity measurement data. Finally, the obtained measurements are iterated, and set to the arrays after which the line chart based on the data is plotted.
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import {Http} from '@angular/http'; import {DataService} from '../service/ng_service'; import {Measurement} from '../service/Measurement'; import {HTTP_PROVIDERS} from '@angular/http'; import {Configuration} from '../app.constants'; import {ChangeDetectorRef} from '@angular/core'; declare var moment: any; @Component({ selector: 'my-app', providers: [HTTP_PROVIDERS, DataService, Configuration], template: ` <h1>{{ title }}</h1> <h2>Latest temperature (°C) and humidity (%) measurements</h2> <div class="ct-chart ct-perfect-fourth"></div> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent implements OnInit { public measurements: Measurement[]; title: string; constructor(private _dataService: DataService, private cd: ChangeDetectorRef) { this.title = 'Airquality at home'; } ngOnInit() { this.getLatestMeasurements(); } private getLatestMeasurements(): void { this._dataService .getLatest() .subscribe((data:Measurement[]) => this.measurements = data, error => console.log(error), () => { console.log('Get all Items complete') this.cd.markForCheck() var labels:string[] = new Array(20); var temperatureValues:number[] = new Array(20); var humidityValues:number[] = new Array(20); var series = [ temperatureValues, humidityValues ]; this.measurements.reverse().forEach((item, index) => { var d = new Date(item.date); labels[index] = d.getHours() +':' +d.getMinutes(); temperatureValues[index] = item.temperature; humidityValues[index] = item.humidity; }); var chartData = { labels, series}; // Create a line to the chart new Chartist.Line('.ct-chart', chartData); } )}; }
The figure below illustrates the current state of the development. It still does not look especially fancy, but works. I really would like to further enhance this one, but the problem with the hobby projects is that one often loses the interest, once the app functions well enough for its purpose, so let’s see. Now it’s time for a good night’s sleep. Until the next time!
Update April 27, 2017
I finally got the initial version of the Web app running on my server. I hope I still have time and energy to develop this further.