Jokapäiväinen säämme

Säänäytön rakentaminen

Monet lukijat ovat toivoneet näppärää sovellusta, joka kertoisi päivän kalenterin ja sään kotona. Internetistä on ladattavissa erilaisia puolivalmiita paketteja, joilla tämänkaltaisen laitoksen saa pyörimään kohtuullisen vähällä vaivalla. Oman rakentaminen on kuitenkin opettavampaa ja järkevämpää, sillä tällöin uusien omien ominaisuuksien lisääminen tulee helpommaksi. Itse tehdessä tuntee myös järjestelmän rajat paremmin.

Käymme seuraavilla sivuilla läpi tällaisen dashboardin rakentamista ja erinäisiin teknisiin ratkaisuihin johtaneita päätöksiä.

Vaikka projekti on luonteeltaan tekninen, pääosa rajoitteista tulee muista tekijöistä. Sovellusta rakennettaessa on mietittävä eri näkökulmista, miten sovellus tulee toimimaan. Mitä tietoja statusnäytössä näytetään lienee tärkein kysymys, sillä se asettaa omat rajansa projektille.

Halusin näyttöön ainakin säädataa. Sitä saa esimerkiksi Ilmatieteen laitoksen avoimesta rajapinnasta (https://en.ilmatieteenlaitos.fi/open-data). Päädyin kuitenkin käyttämään parvekkeelle sijoitettavaa sääsensoria, sillä yksi sellainen oli tyhjän panttina, ja olin jo pitkään halunnut kokeilla, miten se toimii. Oma sensori tuo projektiin myös lisää mielenkiintoa, sille sen sijainnin saan itse valita. Oman sääsensorin käyttö mahdollistaa myös dashboardin laajentamisen helposti esimerkiksi sisälämpötilojen, kosteuden ja ilmanpaineen mittaamiseen.

Sääsensori mittaa kolmea suuretta: lämpötila, suhteellinen ilmankosteus ja ilmanpaine. Halusin dashboardin visua­lisoivan nämä suureet ja ehkä tulevaisuudessa näyttävän esimerkiksi kodin älylamppujen tilan. Lisäksi halusin ohjelman piirtävän käyriä lämpötilasta ja ilmanpaineesta.

Kun on vastattu kysymykseen mitä, yritetään seuraavaksi vastata kysymykseen miten. Parvekkeella olevalle sensorille tulee kommunikoida jotenkin, ja sen havainnoimat tiedot olisi hyvä tallentaa vielä tietokantaankin.

Halusin järjestelmän toimivan paikallisesti ja jopa ilman internettiä, joten toisena rajoittavana tekijänä projektille asetin Raspberry Pi 3 (RPI) -korttitieto­koneen, jolla kaikkien projektin ohjel­mien tulisi pyöriä. Projektin palvelimena toimiva RPI kommunikoi myös parvekkeella olevan sääsensorin kanssa.

Sääsensorille kommunikoimisen ja tietokannan lisäksi dashboardille tarvitaan käyttöliittymä. Käyttöliittymästä on kätevintä tehdä web-pohjainen, sillä datan visualisointiin on tarjolla suhteellisen helppokäyttöisiä työkaluja. Web-pohjaisena dashboard toimii lähes kaikissa laitteissa, joissa on web-selain.

Koska käyttöliittymä on web-pohjainen, päätin rakentaa rajapinnan käyttöliittymän ja tietokannan välille. Rajapinnan avulla vältytään monilta mahdollisilta sudenkuopilta kuten SQL-injektioilta, mikäli dashboardini olisi joskus auki avoimeen internetiin. Rajapinnassa on myös muita hyötyjä: käytössä oleva toteutus on helposti vaihdettavissa ja lisätoimintojen lisääminen helpottuu, mikäli järjestelmään tulee useampia sensoreita.

Näiden vaatimusten pohjalta projekti rakentuu kolmesta osasta:

1. Ohjelma, joka kommunikoi sääsensorille ja tallentaa dataa kantaan.

2. Ohjelma, joka lukee arvoja tieto­kannasta ja toteuttaa rajapinnan.

3. Ohjelma, joka muodostaa käyttö­liittymän ja hakee tietonsa raja­pinnalta.

Rajapinnan tehtävänä on kommunikoida turvallisesti käyttöliittymän ja tietokannan välillä. Tietokannaksi valitsin mysql-tietokannan lähinnä sen käytön helppouden takia. Myös sen vaatima asennustila on huomattavasti pienempi kuin esimerkiksi MongoDB-tietokannalla, mikä on positiivinen asia, sillä RPI:n käytössä oleva tallennustila on verrattain rajattu.

Ohjelmakoodit löytyvät Mikrobitin Github-tililtä osoitteesta github.com/mikrobitti

Ruuvitag sääsensorina

Sääsensoriksi valittiin suomalainen Ruuvitag, joka mittaa lämpötilan, ilmanpaineen ja suhteellisen ilmankosteuden. Sensorille kommunikoidaan Bluetooth-yhteydellä, ja kommunikointiin löytyy suomalaisen Tomi Tuhkasen kirjoittama avoimen lähdekoodin Python-kirjasto, jonka avulla Ruuvitagilta on yksinkertaista pyytää sensoritietoja. Kirjaston asentamiseenkin löytyy varsin kattavat ohjeet kirjaston Github-sivuilla https://github.com/ttu/ruuvitag-sensor/blob/master/install_guide_pi.md.

Ruuvitag julkaistiin Kickstarter-joukkorahoitusalustalla, mutta nykyään laitteen saa ostettua yhtiön verkkokaupan kautta. Ruuvitag on hieman avaimenperää suurempi iot-laite, joka osaa toimia myös Eddy­stone-lähettimenä. Aikaisemmin Googlen Chrome-selain ilmoitti näiden laitteiden läsnäolosta, mutta nykyään toiminto on poistettu. Laitteen löytää parhaiten käyttämällä Bluetooh-lähettimien etsimiseen tarkoitettua ohjelmaa, esimerkiksi nRF Connect -nimistä sovellusta. Ruuvitagin hieman sekavilta verkkosivuilta löytyy etsinnän jälkeen myös kattavasti ohjeita erilaisiin projekteihin, joihin Ruuvitagia voi käyttää.

Laitetta myydään kolmen kappaleen paketissa, joka maksaa noin 85 euroa. Ruuvitag käyttää virtalähteenään paristoa, jonka avulla sen luvataan toimivan normaalikäytössä muutaman vuoden ajan. Kickstarter-paketin mukana tulleella paristolla se kestää kylmää jopa –40 Celsius-asteeseen saakka. Laitteessa on lisäksi nfc ja monia muitakin toimintoja. Verkkosivuilla sitä mainostetaan esimerkiksi kylmäketjun tarkkailuun.

Alustana Raspberry Pi

Palvelimena dashboardissa käytettiin arm-suoritinarkkitehtuurilla tehtyä Raspberry Pi 3 -korttitietokonetta (RPI). Noin 50 euron hintainen tietokone on projektiin erittäin sopiva, sillä se tukee suoraan Bluetooth-yhteyksiä.

Vaikka RPI:hin saa kiinni ulkoisen näytön, hiiren ja näppäimistön, kaikki projektissa tehty kehitystyö tapahtui perinteisellä mallilla SSH-yhteyden yli. Yksi mahdollisuus olisi ollut kehittää ohjelmat jossain muussa ympäristössä, mutta tällöin ei voitaisi olla täysin varmoja, että kaikki toimisi RPI:ssä ilman lisäsäätöjä. Vaihtoehtoa vaikeutti edelleen se, että projektissa sääsensorin kanssa kommunikointiin käytetty Python-kirjasto toimii vain Linux-ympäristöissä.

Pelkkä SSH-yhteys ei tarjoa moderneinta kehittäjäkokemusta, mutta tiedostojen jatkuva siirtäminen paikasta toiseen tai liika säätö työkalujen kanssa ei innostanut. Niinpä Visual Studio Code vaihtui vim-editoriin ja ikkunointijärjestelmä screeniin, mutta Linuxin varusohjelmien käyttö menee tuskin koskaan hukkaan.

RPI:tä etäyhteydellä käytettäessä laitteen pieni­tehoisuuden huomaa pidentyneistä ohjelmien asennusajoista, mutta laittella koodatessa tehon puutetta ei huomaa. Projekti olisi voitu toteuttaa myös millä tahansa muulla Linux-­tietokoneella, josta löytyy ­Bluetooth-yhteys.

Tietojen haku tietokannasta ja rajapinnan muodostaminen

Rajapinta luodaan Facebookin kehittämän avoimen lähdekoodin Graphql-kirjaston avulla. Graphql tarjoaa kätevän tavan yhdistää useampia lähteitä yhteen rajapinnan vastaukseen, jolloin vältytään useammalta pyynnöltä rajapinnalle. Tähän projektiin Graphql on mitä todennäköisimmin hieman ”liian hieno”, mutta halusin sen käytöstä kokemuksia.

Toinen rajapintaohjelman mukana oleva kirjasto graphql-express luo automaattisesti rajapinnasta verkkosivun, jossa rajapinnalle lähetettäviä pyyntöjä voi testata. Tämä on erittäin kätevää rajapinnan kehitysvaiheessa, sillä graafinen käyttöliittymä kertoo automaattisesti, millaiset pyynnöt ovat ylipäätänsä mahdollisia.

Graphql:ää käytettäessä rajapinnan tukemille pyynnöille ja vastauksille määritellään ehdot, jotka niiden pitää täyttää. Näin rajapinta ei hyväksy mitä tahansa pyyntöjä ja hoitaa myös haitallisten pyyntöjen putsausta. Tämän projektin rajapinta ei tosin hyväksy mitään syötteitä, joten tästä ei tarvitse välittää ennen monimutkaisempien toimintojen lisäämistä rajapintaan.

Rajapinta on kirjoitettu Javascript-ohjelmointikielellä, ja se tekee pyynnöt tietokannalle SQL-kyselyllä.

Lisäksi rajapinta hoitaa samalla käyttöliittymän tarvitsemien tiedostojen palvelemisen (web server), sillä muuten tähän oltaisiin tarvittu erillinen ohjelma.

api/index.js

const express = require('express');

const graphqlHTTP = require('express-graphql');

const { buildSchema } = require('graphql');

const mysql = require('mysql');

const halfDay = 43200

const measurementsQuery = `SELECT * FROM observations

WHERE timestamp > UNIX_TIMESTAMP() - ${halfDay}`

// Connect to mysql database

const connection = mysql.createConnection({

host: 'localhost',

user: 'writer',

password: 'writer',

database: 'observations',

});

connection.connect();

// Define schema for grapql

const schema = buildSchema(`

type Data {

temperature: Int,

pressure: Int,

relativehumidity: Int,

timestamp: Int

date: String,

time: String

}

type Query {

measurements: [Data],

}

`

);

function querySQL(query) {

return new Promise(function (resolve, reject) {

connection.query(query, (error, results) => {

const stringify = JSON.stringify(results)

const json = JSON.parse(stringify)

resolve(json)

})

});

}

async function queryMeasurements(){

const values = await querySQL(tempQuery);

return values;

}

// Define functions for graphql

const root = {

measurements: queryMeasurements

}

const app = express()

app.use(express.static('../frontend'))

app.use('/graphql', graphqlHTTP({

schema: schema,

graphiql: true,

rootValue: root,

}));

app.listen(4000);

console.log('Running')

Käyttöliittymä ja datan haku rajapinnalta

Web-käyttöliittymä rakennetaan perinteisillä teknologioilla: HTML, CSS ja Javascript. Koska rajapinta on suhteellisen monimutkainen, halusin pitää käyttöliittymän yksinkertaisena. Ainoa ulkoinen kirjasto käyttöliittymässä on graafit piirtävä kiinalaisen internetjätin Baidun Echarts-kirjasto. Rajapinnalle kommunikoidaan lähettämälle POST-pyynnöllä objekti, joka sisältää tiedot, mitä rajapinnasta halutaan. Rajapinnan antama vastaus syötetään Echart-kirjastolle ja se käskytetään muodostamaan graafi.

Käyttöliittymän tyylimäärittelyssä hyödynnetään kohtuullisen uutta Grid-ominaisuutta. Grid luo nimensä mukaisesti ruudukon, jonka avulla dashboardin käyttöliittymän elementit saadaan kätevästi paikoilleen. Vaikka ominaisuus on suhteellisen tuore, löytyy sille tuki kaikista moderneista selaimista, tosin Internet Explorer 11:ssä tuki on vajavainen.

Käyttöliittymän näkymän päivitys hoidetaan mahdollisimman yksinkertaisesti käskyttämällä selain lataamaan sivu uudelleen minuutin välein HTML:n meta-tagin avulla.

index.html

<!doctype html>

<html lang=”en”>

<head>

<meta charset=”utf-8”>

<title>Dashboard</title>

<meta name=”description” content=”Dashboard”>

<meta name=”author” content=”Niko Nummi”>

<script src=”https://cdnjs.cloudflare.com/ajax/libs/echarts/4.0.2/echarts.min.js”></script>

<script src=”https://code.highcharts.com/highcharts.js”></script>

<link rel=”stylesheet” href=”css/styles.css”>

<meta http-equiv=”refresh” content=”60”>

</head>

<body>

<div class=”grid”>

<div id=”temperature” class=”grid-item”></div>

<div id=”relativehumidity” class=”grid-item”></div>

<div id=”pressure” class=”grid-item”></div>

<div class=”large-chart grid-chart”>

<div id=”temperatureChart” style=”width: 100%; height:100%;”></div>

</div>

<div class=”grid-chart”>

<div id=”pressureChart” style=”width: 100%; height:100%;”></div>

</div>

</div>

<script type=”text/javascript”>

let tempData = [];

let relHumData = [];

let presData = [];

let timeData = [];

// specify chart configuration item and data

let option = {

title: {

text: 'Lämpötilan kehitys',

textStyle: {

color: '#ffffff',

},

},

tooltip: {},

name: 'Aika',

xAxis: {

data: timeData,

axisLabel: {

color: '#ffffff',

},

},

yAxis: {

axisLabel: {

color: '#ffffff',

},

min: Math.min(...tempData) - 1,

max: Math.max(...tempData) + 1,

},

series: [{

name: 'Lämpötila',

color: '#ffffff',

type: 'scatter',

data: tempData,

}]

};

const xhr = new XMLHttpRequest();

xhr.responseType = 'json';

xhr.open(”POST”, ”/graphql”);

xhr.setRequestHeader(”Content-Type”, ”application/json”);

xhr.setRequestHeader(”Accept”, ”application/json”);

xhr.onload = function () {

tempData = xhr.response.data.measurements.map( (item) => item.temperature);

timeData = xhr.response.data.measurements.map( (item) => item.time);

relHumData = xhr.response.data.measurements.map( (item) => item.relativehumidity);

presData = xhr.response.data.measurements.map( (item) => item.pressure);

let tempChart = echarts.init(document.getElementById('temperatureChart'));

let presChart = echarts.init(document.getElementById('pressureChart'));

// Set charts

setOptionValues('Lämpötilan kehitys', tempData, timeData);

tempChart.setOption(option);

setOptionValues('Ilmanpaineen kehitys', presData, timeData);

presChart.setOption(option);

// Set Current Values

document.getElementById('temperature').innerHTML = `${tempData.pop()}°C`

document.getElementById('relativehumidity').innerHTML = `${relHumData.pop()}%`

document.getElementById('pressure').innerHTML = `${presData.pop()}hPa`

}

xhr.send(JSON.stringify({ query: ”{ measurements { temperature relativehumidity pressure time} }” }));

const setOptionValues = (title, series, timeData) => {

option.title.text = title;

option.xAxis.data = timeData;

option.series[0].data = series;

option.yAxis.min = Math.min(...series) - 1;

option.yAxis.max = Math.max(...series) + 1;

}

// use configuration item and data specified to show chart

</script>

</body>

</html>

styles.css

body {

background-color: yellow;

overflow-y: hidden;

overflow-x: hidden;

margin: 0;

}

.grid {

display: grid;

grid-template-columns: repeat(3, auto);

grid-template-rows: repeat(2, auto);

grid-gap: 5px;

width: 100vw;

height: 100vh;

}

.grid-item {

color: #ffffff;

/* padding: 1em; */

width: 33vw;

height: 50vh;

font-size: 9vw;

}

.grid-chart {

grid-row: 2/4;

height: 50vh;

}

.large-chart{

grid-column: 1/3;

}

.grid > * {

background: #171717;

display: flex;

align-items: center;

justify-content: center;

}

Käyttöönoton vaiheet

Sitten vain otamme kodin säänäytön käyttöön, järjestelmällisesti vaiheittain.

Ohjelmakoodit kokonaisuudessaan löytyvät Mikrobitin Github-sivuilta osoitteesta https://github.com/mikrobitti. Ohjelmat vaativat toimiakseen hieman esivalmisteluja, sillä projektissa käytetään muutamia muita ohjelmistoja/kirjastoja, jotka eivät ole vakiona Linuxissa asennettuna. Mikäli itse koodausta ei oteta huomioon, työläimmät vaiheet ovat tietokannan luonti ja asennus, sekä sääsensorin kanssa keskustelevan kirjaston asennus.

Lataa dashboardin tiedostot komennolla git clone https://github.com/Mikrobitti/ruuvi.git

Kannan luonti

1. Mysql-tietokanta asentuu RPI:n Rasbian-linuxilla komennolla

sudo apt-get install mysql-server python-mysqldb.

2. Anna kannalle root-salasana asennuksen aikana.

3. Luo tietokanta, taulu ja käyttäjä käyttämällä dashboardin sql/

-hakemiston alla olevaa create_table_and_user.sql -tiedostoa.

Aja tiedosto komennolla

mysql -u root -p < create_table_and_user.sql

Datan tallentaminen kantaan

1. Asenna datan lukemista varten ruuvitag-kirjasto komennolla

pip install ruuvitag-sensor. Mikäli asennus epäonnistuu,

seuraa tarkempia asennusohjeita osoitteessa: https://github.com/

ttu/ruuvitag-sensor/blob/master/install_guide_pi.md

2. Testaa yhdeyten toimivuutta read_write/ -kansiosta löytyvän connect_

test.py -ohjelman avulla. Mikäli saat seuraavaa muistuttavan

vastauksen, asennus on onnistunut:

{'pressure': 1025.0, 'identifier': None,

'temperature': -11.0, 'humidity': 72.0} 1025.0

3. Datan lukemisen ajoittaminen tapahtuu cronin avulla. Avaa crontab

editorilla esimerkiksi komennolla sudo nano /etc/crontab.

4. Lisää listaan rivi

*/2 * * * * root python <tiedosto>

jossa <tiedosto> on polku tiedostoon, esimerkiksi /home/niko/

ruuvi/read-write/read_and_save_observations.py. Tallenna. Nyt

sääsensorilta dataa lukeva ja sitä tietokantaan tallentava ohjelma

suoritetaan kahden minuutin välein.

Rajapinnan asennus ja käyttöliittymän käynnistäminen

1. Asenna node.js, mikäli se ei ole jo asennettu. Noden voi asentaa

komennolla curl -o- https://raw.githubusercontent.

com/creationix/nvm/v0.33.8/install.sh | bash. Node.js

mahdollistaa Javascript-koodin suorittamisen ilman selainta.

2. Ota node.js käyttöön komennolla nvm use node.Tämän jälkeen

node.js:ää ei tarvitse asentaa enää uudestaan.

3. Siirry api/ -kansioon ja aja komento npm install.

4. Rajapinnan voi nyt kytkeä päälle komennolla node index.js.

5. Rajapinta on nyt käynnissä ja siitä pääsee katsomaan osoitteesta

localhost:4000/graphql (korvaa localhost laitteen ip:llä)

6. Dashboard sijaitsee nyt osoitteessa localhost:4000/index.html

Ruuvitag. Suomalainen sensori kommunikoi Bluetooth-yhteydellä ja mittaa lämpötilan, ilmanpaineen ja suhteellisen ilmankosteuden.Bosch Sensor­tecin BME 280. Anturi mittaa lämpötilaa, suhteellista ilman­kosteutta ja ilman­painetta.CR2477-paristo. Laitteelle luvataan usean vuoden käyttöaika yhdellä paristolla. Ruuvi
IP67. Vedenpitävä kotelo päästää kuitenkin ilmankosteuden lävitseen pienen filtterin kautta. KIMMO HAAPALA
Perinteisellä mallilla. Vaikka RPI:hin saa kiinni ulkoisen näytön, hiiren ja näppäimistön, kehitystyö tapahtui perinteisellä mallilla SSH-yhteyden yli. Niko Nummi
Ruuvi valmiudessa. Ei muuta kuin koodit kuntoon ja säitä odottelemaan. Niko Nummi