Applikation für Web-IO und pure.box
Web-IO-Schaltzustände in MariaDB ablegen
mit Golang auf der pure.box
Dieses Tutorial zeigt ein einfaches Go-Programm für die pure.box. Über die REST-Schnittstelle eines Web-IO werden dessen Schaltzustände ausgelesen und in der MariaDB auf der pure.box abgelegt.
Vorbereitung
1. Datenbank auf der pure.box aktivieren
2. Datenbank aufsetzen
Die Maria-DB auf der pure.box enthälte eine vorkonfigurierte aber leere Datebank namens userdb auf die über den Gerätebenutzer zugegriffen werden kann.
CREATE TABLE `iostates` (
`id` int(11) NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`in0` int(11) NOT NULL,
`in1` int(11) NOT NULL,
`out0` int(11) NOT NULL,
`out1` int(11) NOT NULL,
`count0` int(11) NOT NULL,
`count1` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_german2_ci;
Um dieses Datenbankschema in der Datenbank ihrer purebox anzulegen, verwenden Sie den folgenden Befehl:
mysql -u username -h box-ip -p userdb userdb.sql
oder verwenden Sie ein Tool wie phpMyAdmin oder die Mysql-Workbench.
3. MySQL-Treiber für Go installieren
Um die Datenbank der pure.box aus Go heraus anzusprechen, wird zunächst ein Datenbanktreiber benötigt:
go get github.com/go-sql-driver/mysql
4. REST-Schnittstelle des Web-IO aktivieren
Kurzer Absatz
Kurz und knapp: REST und JSON
Das Web-IO 4.0 ist mit einer REST-Schnittstelle ausgestattet, die Systeminformationen und die aktuelle Zustände und Zählerstände über verschiedene Datenformate bereitstellt. Da Golang von Hause aus XML, JSON und Plaintext unterstützt und JSON zumindest für kleinere Datensätze besser lesbar ist als XML, wird im Rahmen dieser Anleitung auf JSON zurückgegriffen. Vertiefende Informationen zur REST-Unterstütztung der Web-IOs finden Sie in den Bedienungs- und in den Programmierhandbüchern.
Die Zustände des Web-IOs können über den folgenden URL aufgerufen werden:
http://ADRESSE/rest/json/iostate
Die Antwort (ohne Headerdaten) ist so aufgebaut:
{"iostate": {
"input": [{
"number": 0,
"state": 0
}, {
"number": 1,
"state": 0
}],
"output": [{
"number": 0,
"state": 0
}, {
"number": 1,
"state": 0
}],
"counter": [{
"number": 0,
"state": 0
}, {
"number": 1,
"state": 0
}]
}
}
Das Go-Programm
Das Paketencoding/json
liest einen JSON-String ein und bildet ihn
auf Typen ab. Daher werden zunächst Typen definiert, die den Aufbau der
JSONS abbilden:
package main
//Result ist the root element of the JSOBN
type Result struct {
Iostate IOState `json:"iostate"`
}
//IOState keeps collections of a device's inputs, outputs and counters
type IOState struct {
Input []IO `json:"input"`
Output []IO `json:"output"`
Counter []IO `json:"counter"`
}
//IO keeps the number if an IO and its current state
type IO struct {
Number int `json:"number"`
State int `json:"state"`
}
Hierbei sind zwei Dinge zu beachten: Beim Import berücksichtigte Felder beginnen entsprechend der Go-Konvention mit einem Großbuchstaben. Die JSON-Flags weisen einem Feld explizit einen JSON-Bezeichner zu.
Auslesen des Web-IO über HTTP
Um die I/O-Zustände über REST auszulesen, werden im Kopf der Datei drei Pakete importiert:
import(
"encoding/json"
"io/ioutil"
"net/http"
)
encoding/json: Dieses Paket stellt die Routinen bereit um schnell und elegant JSON zu parsen.
io/ioutil: Dieses Paket hilft dabei, die gesamten Daten eines Readers in ein Byte-Slice zu schreiben
net/http: Dieses stellt die benötigte HTTP-Get-Funktion zur Verfügung
IP und den DataURL werden als globale Variablen bereitgestellt:
var(
WebIOIP string = "10.40.38.100"
WebIODataURL string = "http://" + WebIOIP + "/rest/json/iostate"
)
Die Funktion getWebIOStates fragt die aktuellen Zustände über REST ab und gibt uns einen
Result
-Typ aus der jsontypes.go zurück:
func getWebIOStates() Result {
//get HTTP response via REST
response, err := http.Get(WebIODataURL)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
//use ioutil to get json data as byte slice from response
jsondata, err := ioutil.ReadAll(response.Body)
//use encoding/json to unmarshal JSON
var result Result
err = json.Unmarshal(jsondata, &result)
if err != nil {
log.Fatal("Could not decode JSON!", err)
}
return result
}
Zeile 3: Die Zustände werden über
http.Get()
abgefragt.Zeile 7: Mit dem Keyword
defer
wird sichergestellt, dass die Response-Resource beim Beenden der Funktion freigegeben wird.Zeile 10:
ioutil.ReadAll()
gibt den HTTP-Body als Byte-Slice zurück.Zeile 14:
json.Unmarshall()
parstjsondata
und schreibt das Ergebnis inresult
.
Anbindung an die Datenbank
Für die Anbindung an die Datenbank werden das Paket database/sql
und der im Vorfeld heruntergeladene Treiber benötigt.
Die Art der Einbindung mag auf den ersten Blick etwas verwirrend erscheinen: Das Paket database/sql/Driver
stellt ein Interface
bereit. Sobald eine Implementierung dessen importiert wird, kann das database/sql
dieses verwenden.
Da im Quellcode eines Programms aber üblicherweise nicht weiter auf diesen Treiber zugegriffen wird, wirft der Go-Compiler einen Fehler. Die Lösung ist der "Seiteneffekt-Import", mit dem das Paket beim Importieren in den leeren Bezeichner '_' umbenannt wird.
import(
"encoding/json"
"io/ioutil"
"net/http"
"database/sql
_ "github.com/go-sql-driver/mysql"
)
Auch für die Verbindung zur pure.box werden ein paar Parameter benötigt:
var (
PureBoxIP string = "192.168.100.25"
PureBoxDBPort string = "3306"
PureBoxDBName string = "userdb"
PureBoxUser string = "admin"
PureBoxPassword string = ""
WebIOIP string = "192.168.100.20"
WebIODataURL string = "http://" + WebIOIP + "/rest/json/iostate"
)
Nun kann die Funktion, die die Werte in die Datenbank schreibt, umgesetzt werden:
func writeDatabase(webiodata Result) {
//Shorten variable names for the IO states
i0 := webiodata.Iostate.Input[0].State
i1 := webiodata.Iostate.Input[0].State
o0 := webiodata.Iostate.Output[0].State
o1 := webiodata.Iostate.Output[0].State
c0 := webiodata.Iostate.Counter[0].State
c1 := webiodata.Iostate.Counter[0].State
//Create dsn
dsn := PureBoxUser + ":" + PureBoxPassword +
"@" + "tcp(" + PureBoxIP + ":" + PureBoxDBPort + ")" +
"/" + PureBoxDBName
//Connect to database
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal("No database connection!", err)
}
defer db.Close()
//insert data
sqlstring := "insert into iostates (in0, in1, out0, out1, count0, count1) values(?,?,?,?,?,?)"
_, err = db.Query(sqlstring, i0, i1, o0, o1, c0, c1)
if err != nil {
log.Fatal("Could not write to Database! ", err)
}
}
Zeilen 4-9: Hier werden lokale Variablen angelegt, eine Kursschreibweise für zu Zustände zu erhalten.
Zeilen 12-14: Über den Data-Source-Name wird die Verbindung zur Datenbank aufgebaut. In diesem Beispiel lautet er:
admin:foo@192.168.100.25:3306/userdb
Zeilen 17-19: Hier wird die Datenbankverbindung aufgebaut und beim Verlassen der Funktion geschlossen.
Zeilen 24-28:Hier erfolgt der Eintrag in die Datenbank. Die Funktion
db.Query()
ersetzt dabei die ? im SQL-String durch die übergebenen Parameter.
Das Hauptprogramm
Für eine vollständige Implementierung fehlt jetzt nur noch ein Parameter: Der zeitliche Abstand zwischen zwei Abfragen:
var (
LoggingIntervall time.Duration = 10 * time.Second
PureBoxIP string = "192.168.100.25"
PureBoxDBPort string = "3306"
PureBoxDBName string = "userdb"
PureBoxUser string = "admin"
PureBoxPassword string = "foo"
WebIOIP string = "192.168.100.20"
WebIODataURL string = "http://" + WebIOIP + "/rest/json/iostate"
)
Das Hauptprogramm ist unkompliziert:
func main() {
for {
iostates := getWebIOStates()
writeDatabase(iostates)
time.Sleep(LoggingIntervall)
}
}
Zeile 2: Eine
for
ohne Bedingungen leitet eine Endlosschleife ein.Zeile 3: In dieser werden zunächst die IOs ausgelesen
Zeile 4: dann in die Datenbank geschrieben
Zeile 5: und abschließend vor einem weiteren Schleifendurchlauf für ein
LoggingIntervall
abgewartet.
Das vollständige Programm
Als vollständiger Quelltext ergibt sich:
//purebox_webio project main.go
package main
import (
"log"
"time"
"encoding/json"
"io/ioutil"
"net/http"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
var (
LoggingIntervall time.Duration = 10 * time.Second
PureBoxIP string = "10.40.38.10"
PureBoxDBPort string = "3306"
PureBoxDBName string = "userdb"
PureBoxUser string = "admin"
PureBoxPassword string = ""
WebIOIP string = "10.40.38.100"
WebIODataURL string = "http://" + WebIOIP + "/rest/json/iostate"
)
//Result ist the root element of the JSOBN
type Result struct {
Iostate IOState `json:"iostate"`
}
//IOState keeps collections of a device's inputs, outputs and counters
type IOState struct {
Input []IO `json:"input"`
Output []IO `json:"output"`
Counter []IO `json:"counter"`
}
//IO keeps the number if an IO and its current state
type IO struct {
Number int `json:"number"`
State int `json:"state"`
}
func getWebIOStates() Result {
//get HTTP response via REST
response, err := http.Get(WebIODataURL)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
//use ioutil to get json data as byte slice from response
jsondata, err := ioutil.ReadAll(response.Body)
//use encoding/json to unmarshal JSON
var result Result
err = json.Unmarshal(jsondata, &result)
if err != nil {
log.Fatal("Could not decode JSON!", err)
}
return result
}
func writeDatabase(webiodata Result) {
//Shorten IO names fore readability
i0 := webiodata.Iostate.Input[0].State
i1 := webiodata.Iostate.Input[0].State
o0 := webiodata.Iostate.Output[0].State
o1 := webiodata.Iostate.Output[0].State
c0 := webiodata.Iostate.Counter[0].State
c1 := webiodata.Iostate.Counter[0].State
//Create dsn
dsn := PureBoxUser + ":" + PureBoxPassword +
"@" + "tcp(" + PureBoxIP + ":" + PureBoxDBPort + ")" +
"/" + PureBoxDBName
//Connect to database
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal("No database connection!", err)
}
defer db.Close()
//insert data
sqlstring := "insert into iostates (in0, in1, out0, out1, count0, count1) values(?,?,?,?,?,?)"
_, err = db.Query(sqlstring, i0, i1, o0, o1, c0, c1)
if err != nil {
log.Fatal("Could not write to Database! ", err)
}
}
func main() {
// Never stop doing
for {
iostates := getWebIOStates()
log.Println(iostates)
writeDatabase(iostates)
time.Sleep(LoggingIntervall)
}
}