Archiv der Kategorie: IoT
Logbuch Server einrichten – Teil 2
Im ersten Teil habe ich beschrieben, wie der Server mit nginx und node-red bespielt wurde. In diesem Teil werde ich zeigen, wie InfluxDB und Grafana installiert und konfiguriert werden. Auch in diesem Tutorial sind Eingaben und Ausgaben farblich gekennzeichnet.
Installation und Einrichtung der InfluxDB
Die Installation ist auf der Influx Webseite gut beschrieben, daran habe ich mich orientiert. Dabei jeweils auf die Version der Software achten. Zur reinen Installation sind nur 2 Schritte erforderlich.
curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - source /etc/lsb-release echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list deb https://repos.influxdata.com/ubuntu xenial stable sudo apt-get update && sudo apt-get install influxdb Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease Hit:2 https://repos.influxdata.com/ubuntu xenial InRelease Hit:3 http://us.archive.ubuntu.com/ubuntu xenial InRelease Hit:4 http://us.archive.ubuntu.com/ubuntu xenial-updates InRelease Hit:5 http://us.archive.ubuntu.com/ubuntu xenial-backports InRelease Reading package lists... Done Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: influxdb 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 23.0 MB of archives. After this operation, 76.5 MB of additional disk space will be used. Get:1 https://repos.influxdata.com/ubuntu xenial/stable amd64 influxdb amd64 1.6.0-1 [23.0 MB] Fetched 23.0 MB in 0s (27.7 MB/s) Selecting previously unselected package influxdb. (Reading database ... 104707 files and directories currently installed.) Preparing to unpack .../influxdb_1.6.0-1_amd64.deb ... Unpacking influxdb (1.6.0-1) ... Processing triggers for man-db (2.7.5-1) ... Setting up influxdb (1.6.0-1) ... Created symlink from /etc/systemd/system/influxd.service to /lib/systemd/system/influxdb.service. Created symlink from /etc/systemd/system/multi-user.target.wants/influxdb.service to /lib/systemd/system/influxdb.service.
Im nächsten Schritt wird der Service influxdb gestartet
sudo service influxdb start
Eine Kontrolle zeigt, dass die Datenbank läuft
ps -ax|grep influx 5372 ? Ssl 0:00 /usr/bin/influxd -config /etc/influxdb/influxdb.conf
User, Autorisierung und Authentifikation
Im momentanen Zustand ist influx noch vollkommen offen für jeden, deshalb werde ich in den nächsten Schritten User und deren Rechte anlegen. Eine detaillierte Beschreibung findet sich auf den Webseiten von InfluxDB.
Zunächst benötigen wir einen User „admin“, der alle Rechte besitzt. Ich starte influx im ssh-Terminal mit
influx Connected to http://localhost:8086 version 1.6.0 InfluxDB shell version: 1.6.0 >
Wir befinden uns jetzt auf der Befehlszeile der InfluxDB, alle weiteren Ein- und Ausgaben kommen von dort. Zunächst wird der User admin mit einem Passwort angelegt.
CREATE USER admin WITH PASSWORD '<password>' WITH ALL PRIVILEGES >
Ich lege einen weiteren User an, dessen Rechte allerdings dann auf eine bestimmte DB erteilt werden.
CREATE USER <username> WITH PASSWORD '<password>' >
Als nächstes erstelle ich eine Datenbank und gebe dem zuletzt angelegten User alle Rechte darauf
CREATE DATABASE <datenbank-name> > GRANT ALL ON "<datenbank-name>" TO "<username>" >
Zusätzlich lege ich noch einen Read-only User auf diese DB an
CREATE USER RoUser WITH PASSWORD '<password>' > GRANT READ ON "<datenbank-name>" TO "RoUser" >
Influx wird geschlossen mir Strg-d oder „exit“. Damit die Authentifikation generell wirksam wird, muss der Config-File noch angepasst werden.
sudo nano /etc/influxdb/influxdb.conf
darin im Abschnitt [http]
auth-enabled = true
setzen. Den Service dann restarten.
sudo service influxdb stop sudo service influxdb start
Beim nächsten Aufruf von influx daran denken, dass sich nun der User admin zunächst mit
auth username: admin password: xxx
authentifizieren muss.
…..tbc…..
Logbuch – Server einrichten
In der letzten Zeit habe ich einige Server neu eingerichtet, hauptsächlich zur Nutzung für IoT Projekte. Empfehlen möchte ich hier die Cloud Server von Hetzner, die sich für diesen Zweck hervorragend eignen. Ich lasse mir die Server mit einer minimal Ubuntu Konfiguration betanken. Dieses Logbuch beschreibt die Installation eines Servers für das Projekt openair.cologne. Auf diesem Server werden die Sensordaten gespeichert und grafisch ausgewertet.
Folgende SW wird benötigt (wird mglw noch vervollständigt)
– Node-Red
– nginx
– Influx
– Grafana
Vorbereitung
Ich gehe davon aus, dass der Server bereits einen ssh Zugang besitzt. Ich nutze Linux aus der Command Line zur Verbindung mit dem Server, Windows Nutzer müssen hier Putty nutzen. Damit die nachfolgende Beschreibung auch auf andere Server angewendet werden kann, wurden die veränderbaren Daten in < > Klammern gesetzt und müssen entsprechend ersetzt werden. Eingaben und Ausgaben des Servers sind farblich gekennzeichnet.
ssh <user>@<servername oder IP> <user>@<servername> password: xxx Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-128-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage 0 packages can be updated. 0 updates are security updates. *** System restart required *** Last login: Tue Jul 10 20:38:06 2018 from 2.206.50.224 <user>@<servername>:~$
Als erster Schritt muss immer zuerst ein Update / Upgrade des Systems ausgelöst werden.
sudo apt-get update sudo apt-get upgrade
Da ich immer gerne einen Filemanager einsetze, installiere ich mir noch den MidnightCommander.
sudo apt-get install mc
Installation node-red
Die Installation von node-red wird sehr gut in den Tutorials von DigitalOcean beschrieben. Daran habe ich mich weitestgehend orientiert.
sudo apt-get install nodejs-legacy
Testen, ob die Installation erfolgreich war
node -v v4.2.6
Dann weiter
sudo apt-get install npm
und testen
npm -v 3.5.2
und jetzt die Installation von node-red.
sudo npm install -g --unsafe-perm node-red node-red-admin
Node-red wird nicht mit der Installation gestartet, zunächst müssen noch einige Konfigurationen vorgenommen werden. Als erstes lege ich einen User an und vergebe sein Passwort, unter dem node-red laufen soll.
sudo adduser node-red-user
Um node-red bei Systemstart automatisch starten zu lassen ist eine Datei zu erstellen
sudo nano /etc/systemd/system/node-red.service
mit folgendem Inhalt (ggf hier den Usernamen ändern)
[Unit] Description=Node-RED After=syslog.target network.target [Service] ExecStart=/usr/local/bin/node-red-pi --max-old-space-size=128 -v Restart=on-failure KillSignal=SIGINT # log output to syslog as 'node-red' SyslogIdentifier=node-red StandardOutput=syslog # non-root user to run as WorkingDirectory=/home/node-red-user/ User=node-red-user Group=node-red-user [Install] WantedBy=multi-user.target
Dann wird der Service enabled
sudo systemctl enable node-red
und gestartet
sudo systemctl start node-red
Node-Red kann jetzt aus dem Browser mit dem Aufruf <servername:1880> gestartet werden. Zukünftig soll node-red nicht unter dem Port 1880 sonder unter <servername>/node-red aufgerufen werden, dazu benötige ich als Reverse Proxy nginx.
Installation nginx
nginx wird hier hauptsächlich als Reverse-Proxy eingesetzt, kann aber auch als Webserver genutzt werden. Weiterführende Tutorials findet ihr auf DigitalOcean.
sudo apt-get install nginx
nginx wird mit der Installation bereits als Service gestartet. Es wird getestet, ob nginx läuft.
systemctl status nginx ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2018-07-09 20:31:55 CEST; 24h ago Main PID: 23970 (nginx) Tasks: 2 Memory: 2.0M CPU: 386ms CGroup: /system.slice/nginx.service ├─23970 nginx: master process /usr/sbin/nginx -g daemon on; master_process on └─23971 nginx: worker process
Zusätzlich sollte auch der Webservice erreichbar sein. Dazu aus dem Browser den Server unter seinem Namen oder seiner IP aufrufen. Zu sehen sein sollte eine Begrüßungsseite von nginx.
Um später auf die ursprüngliche Konfiguration zurückgehen zu können, benenne ich den Konfigurationsfile um
sudo mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.old
und erstelle einen neuen – zunächst leeren File – mit
sudo nano /etc/nginx/sites-available/default
dieser File wird hiermit befüllt, <….> entsprechend anpassen
server { listen 80; server_name <servername>; root /var/www/html; index index.html index.htm index.nginx-debian.html; location /node-red { proxy_pass http://localhost:1880; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
und der Service wird restartet
sudo systemctl restart nginx
Damit node-red auch richtig funktioniert muss noch eine kleine aber wesentliche Änderung vorgenommen werden, dazu editieren
sudo nano /home/node-red-user/.node-red/settings.js
und diese Zeile anpassen
httpRoot: '/node-red',
danach den Service neu starten mit
sudo systemctl stop node-red sudo systemctl start node-red
Danach sollte node-red aus dem Browser mit <servername>/node-red aufrufbar sein.
Node-Red User Authentication
Momentan ist node-red noch vollkommen offen aus dem Internet. Ich lege einen/mehrere User an, die node-red nutzen dürfen. Dazu wird die Datei settings.js editiert. Vorher öffne ich noch ein zweites ssh Fenster auf dem gleichen Server. In diesem Fenster kann ich den Hash erzeugen. Ich öffne die settings.js im ersten Fenster
sudo nano /home/node-red-user/.node-red/settings.js
in dieser Datei findet man einen Bereich unterhalb von // Securing Node-RED mit einem Beispiel, wie die Authentisierung eingefügt werden muss. Hier ein Beispiel, wie der einzufügende Teil für einen User aussehen sollte
adminAuth: { type: "credentials", users: [{ username: "admin", password: "your hash", permissions: "*" }] },
Das Komma am Schluss des Blocks nicht vergessen. Werden mehrere User eingerichtet, muss jeder User in { } gesetzt werden.
adminAuth: { type: "credentials", users: [ { username: "admin", password: "your hash", permissions: "*" }, { username: "user2", password: "user2 hash", permissions: "*" } ] },
Den Hash erzeuge ich im zweiten Fenster, ich werde nach meinem sudo Passwort gefragt und danach nach dem Passwort for Node-Red.
sudo node-red-admin hash-pw [sudo] password for <user>: xxx Password: xxx $2a$08$refeJAyPqLeeb6EIfNv0Mu.EhZMV6H/wZlJleUdTZWWNplrjfuZ5y
Den ausgegebenen Hash kopiere im anderen Fenster in den File settings.js.
password: "$2a$08$refeJAyPqLeeb6EIfNv0Mu.EhZMV6H/wZlJleUdTZWWNplrjfuZ5y",
Nach dem Speichern des Files und einem Neustart von Node-Red
sudo systemctl stop node-red sudo systemctl start node-red
werde ich nach dem Aufruf der Node-Red Seite im Browser nach meinen Zugangsdaten gefragt.
Installation InfluxDB
….. mit der Installation der InfluxDB geht’s weiter im nächsten Blogpost.
Create your own workflow to visualize your TTN coverage
In short
To determine the radio coverage, GPS data is sent via TheThingsNetwork, processed with Node-Red and stored in an InfluxDB. The data is read from the database via JS and displayed graphically on a map.
The detailed version
This blog post will be the first of a series related to TheThingsNetwork. For some months now I have been actively involved in the development of the TheThingsNetwork in the city of Cologne. The TheThingsNetwork initiative aims to build an open and free wireless infrastructure for the Internet of Things. The community provides the gateways for use by everyone and the central infrastructure is provided by various data centers worldwide, for Europe by the Netherlands.
When building a radio infrastructure, the most important information is the range and coverage of the city in the area. The TTN Mapper from JP Meijers works very well for this application. If I had a smartphone that would be suitable for this app, this post would probably never have existed.
For my own purposes, however, I wanted to build a webpage that I could adapt flexibly to my own needs. In addition, it was a special attraction for me to familiarize myself with this topic and to master the task.
The Node
The first task was to build a TTN node equipped with a GPS module that sends the GPS data via the TTN. The construction of this node was similar to the node described by Bjoern in his blog. A GPS module is serially connected to an Arduino Pro Mini and an RFM95W is used to send the data. The circuits are powered by a lithium-ion battery and the voltage is stabilized by a low quiescent current LDO and a low dropout voltage type HX7333. Even if the structure doesn’t look very professional, it still fulfils its function.
The antenna used is an old GSM 900MHz window antenna. The only change to Bjoern’s software is that in my case OTAA activation is used.
The software checks the validity of the GPS data and sends latitude and longitude data encoded in 6 bytes. The relevant lines here:
void get_coords () { bool newData = false; unsigned long chars; unsigned short sentences, failed; float flat, flon; unsigned long age; // For one second we parse GPS data and report some key values for (unsigned long start = millis(); millis() - start < 1000;) { while (SoftS.available()) { char c = SoftS.read(); Serial.write(c); // uncomment this line if you want to see the GPS data flowing if (gps.encode(c)) { // Did a new valid sentence come in? newData = true; } } } if ( newData ) { gps.f_get_position(&flat, &flon, &age); flat = (flat == TinyGPS::GPS_INVALID_F_ANGLE ) ? 0.0 : flat; flon = (flon == TinyGPS::GPS_INVALID_F_ANGLE ) ? 0.0 : flon; } gps.stats(&chars, &sentences, &failed); int32_t lat = flat * 10000; int32_t lon = flon * 10000; // Pad 2 int32_t to 6 8uint_t, big endian (24 bit each, having 11 meter precision) coords[0] = lat; coords[1] = lat >> 8; coords[2] = lat >> 16; coords[3] = lon; coords[4] = lon >> 8; coords[5] = lon >> 16; } } ...
The complete program code can be found in my git. Define an application and device in the TheThingsNetwork console and fill in the keys in the program code.
To decode the data in the TTN cloud define this function in the TTN Application Console.
function Decoder(b, port) { var lat = (b[0] | b[1]<<8 | b[2]<<16 | (b[2] & 0x80 ? 0xFF<<24 : 0)) / 10000; var lng = (b[3] | b[4]<<8 | b[5]<<16 | (b[5] & 0x80 ? 0xFF<<24 : 0)) / 10000; return { location: { lat: lat, lng: lng }, love: "TTN payload functions" }; }
Data Processing
Node-Red handles the further processing of the data from the TTN cloud. For the first attempts an installation on a RaspberryPi is sufficient or you can use an online version on https://fred.sensetecnic.com/
The gps-logger node is connected to the application in the TTN cloud and receives json fomatted data. The function node decodes the data and prepares the data for the influxdb. The influxdb node finally writes the data to the influxdb. For each gateway receiving the data a record is written in the database. Later when visualizing the data on the map only the record with the highest rssi will be drawn to the map. The record will keep data for the gps location of the node, the gps location and ID of the receiving gateway, rssi, snr, the node’s counter of the record and the device ID of the node. Also the distance between the node and the gateway is calculated and written to the database but not used so far. The structure of the record is defined in the multiple-readings function node.
var lgid = msg.metadata.gateways.length; var array_aussen = []; var array_innen = []; var last_time; for (i = 0; i < lgid; i++) { array_innen = [{ counter: msg.counter, rssi: msg.metadata.gateways[i].rssi, snr: msg.metadata.gateways[i].snr, lat_gw: msg.metadata.gateways[i].latitude, lon_gw: msg.metadata.gateways[i].longitude, no_gw: lgid, lat_sense: msg.payload.location.lat, lon_sense: msg.payload.location.lng, dist2gw: distance(msg.metadata.gateways[i].latitude,msg.metadata.gateways[i].longitude,msg.payload.location.lat,msg.payload.location.lng), time: new Date(msg.metadata.gateways[i].time).getTime() * 1000000 || 0 }, { dev_id: msg.dev_id, gtw_id: msg.metadata.gateways[i].gtw_id } ]; if (array_innen[0].time === last_time) { array_innen[0].time += 1000; } array_aussen.push(array_innen); } var msg1 = {}; msg1.payload = array_aussen; var msg2 = {}; msg2.payload = array_innen[0].time; return [msg1, msg2]; function distance(gw_lat, gw_lon,sense_lat, sense_lon){ if(sense_lat<1 || sense_lon < 1){ return -1; } else { return Math.round(Math.sqrt(Math.pow(70.12*(gw_lon-sense_lon),2) + Math.pow(111.3*(gw_lat-sense_lat),2))*1000); }}
The Website
The far more difficult part of the task was the creation of the website. My Javascript knowledge was rather rudimentary in the beginning and I had to learn a lot while programming the website. For those who want to deal with this, the source code is located on the git. The website performs the following functions: The data is fetched by ajax from the influxdb, first the data of the receiving gateways, then the gps data of the node. The data is displayed as colored squares with the JS extension LeafletJS on an Openstreetmap. If you move the mouse over the squares, detailed data such as rssi, snr, number of receiving gateways and ID of the gateway with the highest rssi are displayed. The coding of color of the squares for rssi is the same as in the ttnmapper. If you hover over a gateway, the gateway ID is displayed.
The time period can be limited by passing HTTP GET parameters. In this way, you can also select whether the map should be colored or black and white. To overpass the parameters a simple http form is provided. A separately recorded gpx track can be placed under the data via a control element on the page. In this way, it is possible to illustrate very clearly where there is no radio coverage. The following video shows the possibilities that the website offers perhaps best.
VIDEO – coming soon
last but not least
The workflow described here may not be suitable for processing a lot of mapping data. I provide the members of the TTN community in Cologne with access to this server, so the amount of data is limited. The workflow is very simple and you don’t need to have a very high programming knowledge to understand it. Maybe this will be also interesting for other communities. You are welcome to try out the website, here in direct access to all available timeseries data or via the filter page with input option for the time span and selection of the optional b/w representation.
Was hat ein kühles Bier mit IoT zu tun
Seit einiger Zeit beschäftige ich mich intensiv mit dem Internet-of-Things (IoT) und frage mich dabei immer wieder: Wozu braucht man denn so was. Aber zugegebenermaßen – in Verbindung mit Mikrocontrollern wie dem Arduino ist es natürlich sehr reizvoll vom Smartphone Steckdosen zu schalten oder die Temperatur und Feuchte seines Kellers zu messen und vom Smartphone aus zu kontrolieren, und das egal wo ich mich gerade befinde.
Ein aktuelles Problem aus dem wahren Leben brachte mich diesem Thema gerade jetzt näher und ich möchte meine Erfahrungen mit euch teilen.
Die Aufgabe
Für ein kleines Fest sollten natürlich ausreichend kalte Getränke, insbesonders leckeres Kölsch zur Verfügung stehen. Der Kühlschrank in der Küche war schon mit leckerem Essen gefüllt und die Temperatur im Keller lag bei ca. 21°C während die Außentemperatur auf knapp 30°C tagsüber anstieg.
Die Idee
Was also tun? Im Keller steht seit einiger Zeit ein ungenutzter Gefrierschrank. Den kann man doch hervorragend als Kühlschrank nutzen, wenn man verhindert, dass er die Getränke einfriert. Also einfach, so die Idee, die Kühlung bei einer Temperatur von ca 5°C ausschalten und bei 6°C wieder einschalten.
Die Realisierung
Alles, was ich dazu brauche habe ich in meiner Bastelkiste:
a) zum Messen der Temperatur
– einen Arduino
– einen DS18B20 Temperatursensor
– einen 4k7 Widerstand
b) zum Schalten der Steckdose
– einen 433MHz Sender- eine Funksteckdose
c) zusätzlich
– ein Protoshield
Der DS18B20 wurde an ein Flachbandkabel gelötet und mit Acryldichtmasse wasserdicht gekapselt. Das Flachbandkabel wurde durch die Türdichtung des Gefrierschranks geführt und der Sensor wurde mit Klebeband im Innern befestigt. Das Programm wurde schnell aus den im Netz vorhandenen Bausteinen zusammengesetzt. Zum Messen der Temperatur wurde die OneWire Lib genutzt, wobei das Programm auf die bekannte Adresse des genutzten Sensors reduziert wurde. Jedem, der mit Netzspannung in seinen Projekten arbeitet, kann ich nur raten, diese immer mit einer Funksteckdose zu schalten. Netzspannung ist lebensgefährlich und mit Nutzung einer Funksteckdose kann fast nichts mehr passieren. Für die Ansteuerung der Funksteckdose benutze ich die RCSwitch Lib. Ich habe den Vorteil Steckdosen vom TypA mit einem Hauscode zu besitzen, damit ist das Schalten sehr simpel.
#include <OneWire.h> OneWire ds(6); // on pin x (a 4.7K pullup resistor is necessary) #include <RCSwitch.h> RCSwitch mySwitch = RCSwitch(); boolean refri_stat; float fptemp = 5.5; // initial set float delta = 0.5; // adjustment range /*************************** Sketch Code ************************************/ void setup(void) { Serial.begin(115200); Serial.println(F("Program started ...")); // Transmitter is connected to Arduino Pin #xx mySwitch.enableTransmit(7); // Switch off refrigerator mySwitch.switchOff("01111", "01000"); refri_stat = false; } void loop(void) { float temp_c = (measure_t()); // call the subroutine to measure if (!refri_stat && temp_c > fptemp + delta) { // decide to switch on/off with hysteresis mySwitch.switchOn("01111", "01000"); refri_stat = true; } if (refri_stat && temp_c < fptemp - delta) { mySwitch.switchOff("01111", "01000"); refri_stat = false; } Serial.print(" on/off state = "); Serial.println(refri_stat); } float measure_t() { byte i; byte present = 0; byte type_s; byte data[12]; byte addr[8] = { 0x28, 0x62, 0x21, 0x80, 0x04, 0x00, 0x00, 0xC1 }; // adress of the sensor ds.reset(); ds.select(addr); ds.write(0x44, 1); // start conversion, with parasite power on at the end delay(1000); // maybe 750ms is enough, maybe not // we might do a ds.depower() here, but the reset will take care of it. present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } int16_t raw = (data[1] << 8) | data[0]; float celsius = (float)raw / 16.0; Serial.print("Sensor present = "); Serial.print(present, HEX); Serial.print(" Temperature[C] = "); Serial.print(celsius); Serial.print(" "); return celsius; }
Die Ausgabe sieht dann so aus, gemessen wird ca. im Sekundenrhythmus, hier noch bei Raumtemperatur:
Program started ... Sensor present = 1 Temperature[C] = 24.94 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 24.94 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.00 on/off state = 1 Sensor present = 1 Temperature[C] = 25.06 on/off state = 1 Sensor present = 1 Temperature[C] = 25.06 on/off state = 1
…. und nun zum Thema IoT
Ein gewisses ungutes Gefühl blieb bestehen. Was ist mit der Temperatur? Schaltet der Arduino zuverlässig? Stürzt das Programm nicht ab? Ich hatte verständlicherweise keine Lust ständig in den Keller zu laufen, also musste eine andere Lösung her. Die Daten müssen in die Cloud! Während ich in meinem letzten Blogbeitrag den Service von data.sparkfun.com genutzt habe, werde ich hier den Service von Adafruit nutzen. Adafruit bietet nicht nur die Speicherung der Daten, sondern auch ein Dashboard, um seine Daten zu sehen und Eingaben zu tätigen. Die Basis der Kommunikation setzt hier auf das MQTT Protokoll auf. Das einzige zusätzliche Material, was zum Einsatz kam, war ein Ethernet Shield. Über ein Powerline Netzwerk wurde vom Router das Ethernet bis in den Keller verlängert.
Mit Hilfe der Tutorials auf Adafruit war mein Account schnell erstellt. Auf meinem Dashboard wollte ich nicht nur die Temperatur und den Zustand der Funksteckdose anzeigen lassen, sondern ich wollte auch den Sollwert der Temperatur verändern können. Ich brauchte also 3 Feeds:
1. die Temperatur – schreibend (publish) vom Arduino auf den Broker (so bezeichnet man den Server in einer MQTT Kommunikation)
2. den Zustand der Funksteckdose (an/aus) und damit die Funktion des Gefrierschranks (kühlen) – schreibend vom Arduino auf den Broker
3. die Einstellung des Soll-Temperaturwertes: am Dashboard einzustellen, der Arduino greift lesend (subscribe) auf den Broker zu
Hier die Sicht auf das Dashboard knapp 3 Stunden nach dem Einschalten. Die Temperatur schwingt zunächst noch über den Zielwert um sich dann später zwischen 13°C und 14°C einzupendeln. Der Status ist momentan Kühlung = aus. Die Einschaltdauer liegt jeweils bei ca. 1 min, aus bei ca. 30 min.
Das oben gelistete Programm wurde durch die notwendigen Befehle der Adafruit IO Library ergänzt. Ich habe versucht das durch zahlreiche Kommentare verständlich zu machen.
#include <OneWire.h> OneWire ds(6); // on pin x (a 4.7K pullup resistor is necessary) #include <RCSwitch.h> RCSwitch mySwitch = RCSwitch(); boolean refri_stat; /*************************************************** Adafruit MQTT Library Ethernet Example Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! Written by Alec Moore Derived from the code written by Limor Fried/Ladyada for Adafruit Industries. MIT license, all text above must be included in any redistribution ****************************************************/ #include <SPI.h> #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include <Ethernet.h> #include <EthernetClient.h> #include <Dns.h> #include <Dhcp.h> /************************* Ethernet Client Setup *****************************/ byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; //Uncomment the following, and set to a valid ip if you don't have dhcp available. IPAddress iotIP (192, 168, 2, 99); //Uncomment the following, and set to your preference if you don't have automatic dns. //IPAddress dnsIP (8, 8, 8, 8); //If you uncommented either of the above lines, make sure to change "Ethernet.begin(mac)" to "Ethernet.begin(mac, iotIP)" or "Ethernet.begin(mac, iotIP, dnsIP)" /************************* Adafruit.io Setup *********************************/ #define AIO_SERVER "io.adafruit.com" #define AIO_SERVERPORT 1883 #define AIO_USERNAME "<your username here>" #define AIO_KEY "<your AIO key here>" /************ Global State (you don't need to change this!) ******************/ //Set up the ethernet client EthernetClient client; // Store the MQTT server, client ID, username, and password in flash memory. // This is required for using the Adafruit MQTT library. const char MQTT_SERVER[] PROGMEM = AIO_SERVER; // Set a unique MQTT client ID using the AIO key + the date and time the sketch // was compiled (so this should be unique across multiple devices for a user, // alternatively you can manually set this to a GUID or other random value). const char MQTT_CLIENTID[] PROGMEM = __TIME__ AIO_USERNAME; const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME; const char MQTT_PASSWORD[] PROGMEM = AIO_KEY; Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD); /****************************** Feeds ***************************************/ // Setup a feed called 'biertemp' for publishing. // Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname> const char BIERMON_FEED[] PROGMEM = AIO_USERNAME "/feeds/biertemp"; Adafruit_MQTT_Publish biermon = Adafruit_MQTT_Publish(&mqtt, BIERMON_FEED); const char ONOFF_FEED[] PROGMEM = AIO_USERNAME "/feeds/onoff"; Adafruit_MQTT_Publish onoffbutton = Adafruit_MQTT_Publish(&mqtt, ONOFF_FEED); const char SETTEMP_FEED[] PROGMEM = AIO_USERNAME "/feeds/settemp"; // definition of feedname Adafruit_MQTT_Subscribe settemp = Adafruit_MQTT_Subscribe(&mqtt, SETTEMP_FEED); // subscription float stat = 0; float fptemp = 25; // initial set float delta = 0.1; // adjustment range unsigned long lastmillis; /*************************** Sketch Code ************************************/ void setup(void) { Serial.begin(115200); Serial.println(F("Program started ...")); // Transmitter is connected to Arduino Pin #xx mySwitch.enableTransmit(7); // Switch off refrigerator mySwitch.switchOff("01111", "01000"); refri_stat = false; Ethernet.begin(mac, iotIP); delay(1000); //give the ethernet a second to initialize Serial.print("My IP address: "); Serial.println(Ethernet.localIP()); mqtt.subscribe(&settemp); // activate subscription to feed settemp } void loop(void) { MQTT_connect(); Adafruit_MQTT_Subscribe *subscription; // check subscription while ((subscription = mqtt.readSubscription(500))) { // Check if its the settemp feed if (subscription == &settemp) { Serial.print(F("Got new Temp: ")); Serial.println((char *)settemp.lastread); fptemp = atof((char *)settemp.lastread); // set to new temperature Serial.println(fptemp); } else { Serial.print(F("nothing to read ")); } } float temp_c = (measure_t()); if (!refri_stat && temp_c > fptemp + delta) { mySwitch.switchOn("01111", "01000"); refri_stat = true; } if (refri_stat && temp_c < fptemp - delta) { mySwitch.switchOff("01111", "01000"); refri_stat = false; } Serial.print(" on/off state = "); Serial.println(refri_stat); // Now we can publish stuff! if (stat != float(refri_stat)) { // only when changed Serial.print(F("\nSending changed on/off state ")); Serial.print(refri_stat); Serial.println("..."); stat = float(refri_stat); // published value has to be float (don't know why) if (! onoffbutton.publish(stat)) { Serial.println(F("Failed")); } else { Serial.println(F("OK!")); } } if (millis() > lastmillis + 15000) { // read / write broker every xx milliseconds MQTT_connect(); // Now we can publish stuff! Serial.print(F("\nSending temp val ")); Serial.print(temp_c); Serial.println("..."); if (! biermon.publish(temp_c)) { Serial.println(F("Failed")); } else { Serial.println(F("OK!")); } // ping the server to keep the mqtt connection alive if (! mqtt.ping()) { mqtt.disconnect(); } lastmillis = millis(); } } float measure_t() { byte i; byte present = 0; byte type_s; byte data[12]; byte addr[8] = { 0x28, 0x62, 0x21, 0x80, 0x04, 0x00, 0x00, 0xC1 }; //byte addr[8] = { 0x28, 0x42, 0xCC, 0x7F, 0x04, 0x00, 0x00, 0xAA }; ds.reset(); ds.select(addr); ds.write(0x44, 1); // start conversion, with parasite power on at the end delay(1000); // maybe 750ms is enough, maybe not // we might do a ds.depower() here, but the reset will take care of it. present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } int16_t raw = (data[1] << 8) | data[0]; float celsius = (float)raw / 16.0; Serial.print("Sensor present = "); Serial.print(present, HEX); Serial.print(" Temperature[C] = "); Serial.print(celsius); Serial.print(" "); return celsius; } // Function to connect and reconnect as necessary to the MQTT server. // Should be called in the loop function and it will take care if connecting. void MQTT_connect() { int8_t ret; // Stop if already connected. if (mqtt.connected()) { return; } Serial.print("Connecting to MQTT... "); while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected Serial.println(mqtt.connectErrorString(ret)); Serial.println("Retrying MQTT connection in 5 seconds..."); mqtt.disconnect(); delay(5000); // wait 5 seconds } Serial.println("MQTT Connected!"); }
Video
Daten in der Cloud – Visualisierung auf dem Bildschirm
Einleitung
In den letzten Wochen habe ich mich intensiv mit dem Thema IoT beschäftigt. Auf der Suche nach Diensten zur Speicherung von Daten in der Cloud bin ich auf data.sparkfun.com aufmerksam geworden. Dieser Dienst eignet sich gerade für Programmieranfänger hervorragend Daten strukturiert in der Cloud abzulegen.
In diesem Blogpost beschreibe ich eine einfache Methode auf data.sparkfun abgelegte numerische Daten grafisch zu präsentieren. Dabei bediene ich mich wieder des Javascript Moduls dygraphs, das ich schon in früheren Blogposts genutzt habe, um Daten grafisch darzustellen.
Der Dienst data.sparkfun
Die Konfiguration einer Datentabelle auf data.sparkfun ist denkbar einfach, so dass ich an dieser Stelle auf eine Anleitung verzichten möchte.
Mit nur wenigen Klicks hat man eine Datentabelle (data stream) angelegt. Auf der Webseite sind nur wenige Eingaben zu machen. Der Dienst ist kostenlos und anonym, allerdings ist die Größe des Streams auf 50MB beschränkt, die ältesten Daten werden bei Überschreitung gelöscht. Die Daten sind öffentlich, jeder, der die url kennt, kann die Daten abrufen. Eine Sicherheit für die Verfügbarkeit der Daten wird nicht gegeben. Nichtsdestotrotz ist es ein hervorragender Dienst, um mit dem IoT erste Erfahrungen zu machen.
Die Daten und deren Darstellung
Über die Webseite data.sparkfun.com kann man seine Daten tabellarisch ansehen und als Datei im csv oder json Format herunterladen. Eine grafische Darstellung ist hier nicht vorhanden. Anleitungen zur grafischen Darstellung mit Google Charts oder als eigene Platform (analog.io) existieren, sind mir aber entweder zu unflexibel oder zu google-lastig. Mein Favorit ist nach wie vor DyGraphs, zur Nutzung mit data.sparkfun gab es aber bisher keine Anleitung, so dass ich selbst kreativ werden musste. Dabei habe ich wieder so einiges über JavaScript lernen müssen / dürfen und meine Erfahrungen gebe ich wie immer gerne weiter.
JavaScript eignet sich für diese Aufgabe im Gegensatz zu PHP besonders deshalb, weil kein separater Server zum Aufruf dieser Webseite benötigt wird. Die html-Datei mit der Formatierung der Webseite kann lokal auf dem eigenen Rechner liegen, die Verarbeitung findet komplett im Browser statt.
Die hier genutzte html-Beispieldatei greift auf einen aktiven Stream auf data.sparkfun zu und zeigt im Screenshot oben die Entladung und Ladung eines Li-Ionen Akkus, über den der Arduino mit Ethernetshield, der die Daten auf data.sparkfun pushed, gleichzeitig mit Strom versorgt wird.
Die Struktur der html-Datei erkläre ich hier, damit sollte die Anpassung auf einen eigenen Stream recht einfach sein.
Zuoberst findet man die üblichen HTML Tags im Kopfbereich einer html-Datei und die Angabe der Quelle für 2 JavaScript-Module, hier als hosted Version, die auskommentierten Zeilen nutzt man, wenn die Dateien lokal im gleichen Verzeichnis wie die html-Datei liegt. Es folgt eine einfache farbliche Formatierung der Webseite.
Die nachfolgenden Zeilen sind in eine onload() Funktion eingebettet und definieren die Größe der späteren Grafik, die über die id=graphdiv2 später befüllt wird.
Dies ist die Funktion zum Download der Daten von data.sparkfun. An dieser Stelle muss der public Key der eigenen Daten eingetragen werden. Über eine jquery Funktion werden die Daten von Sparkfun geladen. An dieser Stelle kann ein Filter definiert werden, entweder man lädt die Daten page-weise ( data: {page: 1}; ) oder nutzt einen der hier beschriebenen möglichen Filter zur Einschränkung der Zahl der Daten. Der hier benutzte Filter „today“ für den timestamp ist nirgendwo dokumentiert und habe ich zufällig herausgefunden, also nicht wundern, wenn mit meinem Script eine leere Grafik erscheint. Für einen ersten Test kann man die Zeile auskommentieren.
Die json-Daten müssen in ein Array überführt werden, dabei werte ich aus, wie groß der zeitliche Abstand der Daten ist und entscheide darüber, ob die Datenpunkte mit Linien verbunden werden (hier 250.000 Sekunden, also etwas über 4 Minuten) . Hier müssen die Namen der Datenfelder eingesetzt werden , in meinem Fall ubatt und ucc.
Damit die zeitliche Darstellung der Daten korrekt ist, wird das gesamte Array in der Reihenfolge umgekehrt. Das Array wird dann an Dygraph übergeben mit einigen Parametern zur Darstellung und den Labeln für die Daten.
Die vollständige html-Datei liegt auf meinem Git zusammen mit dem Arduino Programm zum pushen der Daten.
Ausblick
Ich habe inzwischen den phant Server auch auf einem RaspberryPi laufen, funktioniert hervorragend. Leider habe ich den mqtt-output noch nicht ans laufen gebracht. Dieser Service ist bei Sparkfun leider sehr unzuverlässig, so dass ich davon – zumindest bei der hosted Version – erst mal abraten würde.
Speziell für den Arduino Uno werde ich einen Stream erstellen, wo alle analogen Eingänge als Integer und die digitalen Eingänge als Boolean 1/0 gepushed werden können. Auf einer Webseite soll man dann wählen können, welche Daten grafisch aufbereitet werden sollen. Auch das zeitliche filtern der Daten will ich auf der Webseite eingebbar machen.