W&T verbindet
Interfaces für TCP/IP, Ethernet, RS-232, RS-485, USB, 20mA, Glas- und Kunststoff-LWL, http, SNMP, OPC, Modbus TCP, I/O digital, I/O analog, ISA, PCI

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 Paket encoding/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() parst jsondata und schreibt das Ergebnis in result.

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)
							}
						}