Aufgrund von Wartungsarbeiten (am Versorgungsnetz) ist W&T am 19.04.2024 geschlossen!

Unsere Technikhotline erreichen Sie unter:
0202 / 2680 - 110 oder unter der Notfallnummer: 0179 / 4317651


Ab Montag den 22.04.2024 sind wir in gewohnter Weise wieder erreichbar.

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 zum Web-IO Digital:

Web-IO Digital mit C++ unter Linux steuern und überwachen


Die im folgenden beschriebene Applikation erlaubt es, das Web-IO unter Linux zu steuern Die Bedienoberfläche wurde mit dem Qt-Designer in Version 3.3.5 zusammengestellt.

Web-IO unter Linux

Mit dem folgenden C++ Programmbeispiel können Sie Ihr Web-IO Digital mit seinen Inputs und Outputs in einer Linux-Anwendung abbilden. Darüber hinaus können Sie die Outputs des Web-IO schalten.

Vorbereitungen

Sie haben Ihr Web-IO Digital bereits

1. Zusammenstellen der verschiedenen Bedienelemente und Anzeigeobjekte im Qt-Designer

Qt-Designer

Um diese Applikation zu erstellen wird im Qt-Designer ein neues C++ Projekt erzeugt dem ein belibieger Name gegeben werden kann. Wir haben uns hier für "Client" entschieden. Im nächsten Schritt wird ein Mainwindow erzeugt, in dem die graphischen Elemente platziert und in deren Eigenschaftenbenannt werden.

Um sich unter einem Unix System das komplieren der Applikation zu erleichtern, erzeugen wir zwei Dateien namens "Uic" und "Qmake". So erhalten wir funktionsfähigen C++ Quelltext, denn man mit einem Makefile problemlos compilieren kann.

Man erstellt eine neue Textdatei und schreibt die beiden folgenden Zeilen hinein. Daraufhin speichert man die Datei unter "Uic".

Hinweis: Diese Zeilen funktionieren nur insofern qt3 verwendet wird und es an der selben Stelle im System installiert ist. Sonst bitte die beiden Zeilen anpassen.

/usr/lib/qt3/bin/uic $1.ui -o $1.h
/usr/lib/qt3/bin/uic -impl $1.h $1.ui -o $1.cpp
		

Nun erstellt man noch eine Textdatei, die anschließend unter "Qmake" gespeichert wird.

/usr/lib/qt3/bin/qmake -o Makefile $1.pro

Sind beide Dateien erstellt, legt man beide in das Verzeichnis, in dem auch das Projekt liegt. Darauf hin konfiguriert man die beiden Dateien so, dass sie ausführbar werden. Dafür kann man das chmod Kommando verwenden.

-> chmod a+x Dateiname

Im folgenden kann das Projekt erfolgreich in C++ Quellcode umgewandelt und compiliert werden. Dazu sind nur noch drei Schritte notwendig.

./Uic Dateiname(.ui)
./Qmake Projekt(.pro)
make

Nun wurde erfolgreich eine ausführbare Datei erstellt, welche genauso wie das Projekt heisst.

2. Erstellen der Quellcode Datei der graphischen Oberfläche

Wenn der Qt-Designer gestartet ist und man das Mainwindow geöffnet vor sich hat, wird durch ein Doppelklick auf das Window eine .ui.h Quellcode Datei erzeugt. Im folgenden wird Ihnen in einzelnen beschrieben, welche Schritte notwendig sind um diese Applikation zum laufen zu bekommen.

Um neue Slots zu erzeugen kann man durch einen Rechtsklick auf das Mainwindow ein Eigenschaften Fenster öffnen und dort den Eintrag Slots wählen. Dort erzeugt man sich die benötigten Slots, welche dann mit einem leeren Rumpf automatisch in der erzeugten .ui.h Datei erscheinen.

Nun includieren wir noch einige benötigte Header-Dateien. Die clientDlg.h und die clientDlg.cpp werden nach der Anwendung mit ./Uic aus der .ui Datei (Mainwindow) erzeugt. Um aúf die Elemente zugreifen zu können wird die Header Datei hier includiert.


#include "clientDlg.h"
#include <qsocket.h>
#include <qstring.h>
#include <qtextstream.h>
#include <qtimer.h>
#include <iostream>

Anschließend werden globale Variablen für eine TCP-Verbindung und das Polling deklariert und somit jeder Methode der Klasse zugänglich gemacht. Da wir keine eigenen Namensraum definieren, wird der Standardnamensraum verwendet.

using namespace std;

QSocket* client;
QTimer* output;
QTimer* input;
QTimer* counter;

3. Programmstart

Einrichten der Bedienelemente

Die Gruppe mit den Bedienelementen für das Web-IO wird zunächst für die Bedienung gesperrt. Sobald eine Verbindung zu Stande kommt, werden alle benötigten Elemente freigeschalte. Dazu kann man in den Eigenschaften der GroupBox cb_io_control, sowie bei allen anderen Elementen, die Enable-Funktion auf 'false' oder 'true' setzen.

Der Name des jeweiligen Bedienelementes ist dem Kontext nach von dem Element selbst abgeleitet. Die beiden ersten Zeichen im Namen stehen für den Typ des Elemente (cb -> Checkbox, bt -> Button, gb -> Groupbox und le -> LineEdit).

4. Die Verbindungskontrolle

Einleiten der Verbindung

Nach Eingabe der IP-Adresse des Web-IO in das Textfeld le_ip und dem Port 80 in das Textfeld le_port kann durch Betätigung des Buttons bt_connect eine Verbindung aufgebaut werden. Falls keine IP-Adresse oder kein Port eingetragen wird, folgt eine Meldung durch die Applikation in der Statusleiste.

Verbindungsaufbau

Um eine TCP-Verbindung aufbauen zu können wird die bereits deklarierte Socketvariable initialisiert. Ist alles korrekt eingegeben worden beginnt jetzt der Versuch eine Verbindung zu erzeugen. Um zu erfahren ob die Verbindung auch realisiert worden ist wird auf ein Signal des Sockets gewartet, welches einen erfolgreichen Verbindungsaufbau meldet.

void clientDlg::onConnect()
{
	client = new QSocket(this);
	bool ok;
	connect(client,
	SIGNAL(connected()), SLOT(connectDone()));
	connect(client,
	SIGNAL(error(int)),
	SLOT(connectError(int)));

	if(le_ip->text() == "")
	le_statusBar->setText("No IP entered!");
	else if(le_port->text() == "")
	le_statusBar->setText("No port entered!");
	else
	client->connectToHost(le_ip->text(), (le_port->text()).toInt(&ok,10));
} 

Im Falle eines Fehlers beim Verbindungsaufbau wird in der Statusleiste eine Meldung ausgegeben.

void clientDlg::connectError(int)
{
	le_statusBar->setText("Error while connecting!");
}
		
Verbindung kommt zustande

Nach erfolgreichem Verbindungsaufbau werden alle nützlichen Bedienelemente der Applikation freigschalten und der Connect Button deaktiviert. Ausserdem beginnt die Applikation sofort damit, in Empfangsbereitschaft zu gehen.

void clientDlg::connectDone()
{
	le_statusBar->setText("Connected to " + le_ip->text());
	connect(client, SIGNAL(readyRead()), SLOT(onReceive()));

	output = new QTimer(this);
	connect(output,
	SIGNAL(timeout()),
	SLOT(timerEvent()));

	input = new
	QTimer(this);
	connect(input,
	SIGNAL(timeout()),
	SLOT(timerEvent()));

	counter = new
	QTimer(this);
	connect(counter,
	SIGNAL(timeout()),
	SLOT(timerEvent()));

	gb_io_control->setEnabled(true);
	bt_disconnect->setEnabled(true);
	bt_connect->setEnabled(false);
}
Trennen der Verbindung

Die Verbindung bleibt solange bestehen, bis sie vom Benutzer durch Klick auf den Disconnect-Button oder duch das Web-IO beendet wird. Nach Betätigung des Buttons wird eine Meldung ausgegeben, dass die Verbindung beendet wird.

void clientDlg::onDisconnect()
{
	client->close();

	output->stop();
	input->stop();
	counter->stop();

	gb_io_control->setEnabled(false);
	bt_disconnect->setEnabled(false);
	bt_connect->setEnabled(true);
	le_statusBar->setText("Disconnected!");
}

Bei Verbindungsende werden alle Elemente wieder für die Bedienung gesperrt.

5. Bedienung und Kommunikation von Client-Seite

Sobald eine Verbindung mit dem Web-IO zustande gekommen ist, kann der Anwender durch Bedienung der entsprechenden Programmelemente Kommandos an das Web-IO senden

Beim Senden einer Nachricht an das Web-IO wird asynchron gearbeitet. Dadurch ist die Applikation bei einem Sende-/Empfangsvorgang nicht blockiert.

Die Outputs des Web-IO können mit Hilfe der beiden Checkboxen cb_output0 und cb_output1 geschaltet werden.

Im nächsten Schritt wird geprüft ob die Checkbox bereits gesetzt ist und entsprechende Output auf ON oder OFF gesetzt.

void clientDlg::onOutput0()
{
	QString message1 = "GET /outputaccess0?PW=" + le_password->text() + "&State=ON&";
	QString message2 = "GET /outputaccess0?PW=" + le_password->text() + "&State=OFF&";

	if(cb_output0->isChecked())
	client->writeBlock(message1, message1.length());
	else
	client->writeBlock(message2, message2.length());

	client->flush();
} 
void clientDlg::onOutput1()
{
	QString message1 = "GET /outputaccess1?PW=" + le_password->text() + "&State=ON&";
	QString message2 = "GET /outputaccess1?PW=" + le_password->text() + "&State=OFF&";

	if(cb_output1->isChecked())
	client->writeBlock(message1, message1.length());
	else
	client->writeBlock(message2, message2.length());

	client->flush();
} 

Output/Input-Status abfragen

void clientDlg::onReadallOutputs()
{
	QString message = "GET /output?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
} 
void clientDlg::onReadallInputs()
{
	QString message = "GET /input?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
} 

Counter abfragen

void clientDlg::onReadCounter0()
{
	QString message = "GET /counter0?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
} 
void clientDlg::onReadCounter1()
{
	QString message = "GET /counter1?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
}

Natürlich können alle Counterzustände auch mit einem Kommando abgefragt werden.

void clientDlg::onReadallCounter()
{
	QString message = "GET /counter?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
}
Counter zurücksetzen

Es ist außerdem möglich, die Zähler nach dem Lesen auf 0 zu setzen.

void clientDlg::onClearCounter0()
{
	QString message = "GET /counterclear0?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
}
		
void clientDlg::onClearCounter1()
{
	QString message = "GET /counterclear1?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
} 

Natürlich lassen sich auch alle Counter zusammen mit einem Kommando zurücksetzen.

void clientDlg::onClearallCounter()
{
	QString message = "GET /counterclear?PW=" + le_password->text() + "&";
	client->writeBlock(message, message.length());
	client->flush();
} 

Da alle Counterzustände mit einem Kommando gelesen oder zurückgesetzt werden können, muss noch eine Methode implementiert werden, welche den Antwortstring des Web-IO bearbeitet und jedem Counter in der Applikation seinen spezifischen Zustand zuordnet.

void clientDlg::readAndClearCounter(QString data)
{
	QString counter[12];
	int j = 0;
	int length = data.length();
	for(int i = 0; i < length; i++)
	{
	if(data[i] == ';')
			j++;
	else
			counter[j] += data[i];
	}
	le_counter0->setText(counter[0]);
	le_counter1->setText(counter[1]);
}

6. Datenempfang vom Web-IO

Auswerten und Anzeigen der empfangenen Daten
  • Alle Kommandos und Anfragen an das Web-IO werden mit einem Antwort-String quittiert. Dabei haben die Antworten je nach Type einen spezifischen Aufbau:
  • Für die Outputs: output;<Binärwert des Outputstatus im hexadezimalen Format>
  • Für einen speziellen Output: outputx;<ON oder OFF>
  • Für die Inputs: input;<Binärwert des Outputstatus im hexadezimalen Format>
  • Für einen speziellen Input: inputx;<ON oder OFF>
  • Dann gibt es noch den Antwortstring für einen Counter der folgendermassen aussieht.
  • Counter: counterx;<dezimaler Zählerstand>
  • oder counter;<dezimaler Zählerstand 0 >; <dezimaler Zählerstand 0 >; ... wenn alle Counter auf einmal gelesen werden sollen.
  • Alle Antwort-Strings sind mit einem 0-Byte abgeschlossen.

In unserer Applikation wird zum Empfang einer solchen Nachricht die Methode onReceive() aufgerufen. In dieser Methode wird der Antwortstring empfangen und verarbeitet. Das besondere ist hier, dass sich diese Funktion die ganze Zeit selbst aufruft und somit ununterbrochen nach Nachrichten schaut, welche möglicherweise vom Web-IO gesendet worden sind. Festgelegt haben wir dass ja direkt nach dem eine Verbindung erfolgreich initialisiert worden ist (connectDone()).

void clientDlg::onReceive()
{
	char buffer[50];
	client->readBlock(buffer, 49);
	QString rcv = buffer;
	bool ok;
	if(rcv[0] == 'o')
	{
		int value = rcv.right(rcv.length()-7).toInt(&ok, 16);
	if((value & 1) == 1)
		cb_output0->setChecked(true);
	else
		cb_output0->setChecked(false);
	if((value & 2) == 2)
		cb_output1->setChecked(true);
	else
		cb_output1->setChecked(false);
	}
	if(rcv[0] == 'i')
	{
		int value = rcv.right(rcv.length()-6).toInt(&ok, 16);
		if((value & 1) == 1)
		cb_input0->setChecked(true);
	else
		cb_input0->setChecked(false);
	if((value & 2) == 2)
	cb_input1->setChecked(true);
	else
	cb_input1->setChecked(false);
	}
	if(rcv[0] == 'c')
	{
	if(rcv[7] == '0') le_counter0->setText(rcv.right(rcv.length()-9));
	if(rcv[7] == '1') le_counter1->setText(rcv.right(rcv.length()-9));
	if(rcv[7] == ';') readAndClearCounter(rcv.right(rcv.length()-8));
	}
}
Zyklisches Abfragen bestimmter Werte

Es ist wünschenwert, dass sich der Status einer einzelnen Komponente von selbst aktualisiert. Dazu wird in diesem Progamm ein Timer verwendet, der in einem vom User betimmten Zeitintervall zyklisch Abfragen an das Web-IO sendet.

Due Zykluszeit wird in das Feld le_interval eingegeben.

Natürlich wird auch abgefangen falls der Nutzer eine unsinnige Angabe, wie z.B. einen negativer Zeitwert macht. Darauf folgt sofort eine Meldung und der Wert wird nicht übernommen.

7. Polling

bool clientDlg::checkRange()
{
	bool ok;
	int tmp = (le_interval->text()).toInt(&ok, 10);
	if(ok && tmp > 0)
	{
		le_statusBar->setText("Range changed to " + le_interval->text() + " ms!");
		return
		true;
	}
	else
	{
		le_statusBar->setText("Please type a positive integer value for the range of polling!");
		return
		false;
	}
} 

Um nun auch das zyklische Abfragen der Zustände des Web-IO durchzuführen, was auch als Polling bezeichnet wird, besteht die Auswahlmöglichkeit zwischen dem Polling der Outputs, der Inputs oder der Counter.

Betätigt man die Checkbox cb_polling_outputs so wird das Polling auf die Outputs angewendet.

void clientDlg::onPollingOutputs()
{
if(checkRange())
	{
	if(cb_polling_outputs->isChecked())
		output->start((le_interval->text()).toInt(), false);
	else
		output->stop();
	}
}

Beim Betätigen der Checkbox cb_polling_inputs wird das Polling auf die Inputs angewendet.

void clientDlg::onPollingInputs()
{
	if(checkRange())
	{
	if(cb_polling_inputs->isChecked())
		input->start((le_interval->text()).toInt(), false);
	else
		input->stop();
	}
}

Sollen nun auch noch die Counter unter Polling abgefragt werden, so kann man dazu die Checkbox cb_polling_counter verwenden.

void clientDlg::onPollingCounter()
{
	if(checkRange())
	{
	if(cb_polling_counter->isChecked())
	counter->start((le_interval->text()).toInt(),false);
	else
	counter->stop();
	}
}

Es wurden drei verschiedene Timer initialisiert, die im eingetragenen Intervall eine Aktion auslösen. Um die Events zu fangen muss noch eine Methode implementiert werden.

void clientDlg::timerEvent()
{
	if(output->isActive()) onReadallOutputs();
	if(input->isActive()) onReadallInputs();
	if(counter->isActive()) onReadallCounter();
}

Das Beispiel Programm unterstützt alle gängigen Funktionen des Web-IO im Kommando-String Modus, optimiert für das Web-IO 2x Digital Input, 2x Digital Output. Für die anderen Web-IO Modelle müssen ggf. Anpassung am Programm vorgenommen werden. Weitere Programmbeispiele zur Socket-Programmierung finden Sie auf den Tool-Seiten zum Web-IO. Eine Detaillierte Beschreibung zur Socketschnittstelle der Web-IO Digital Modelle finden Sie im Referenzhandbuch.

Programmbeispiel herunterladen

Sie haben noch kein Web-IO und möchten das vorgestellte Beispiel einfach mal ausprobieren?

Kein Problem: Wir stellen Ihnen das Web-IO Digital 2xInput, 2xOutput gerne kostenlos für 30 Tage zur Verfügung. Einfach Musterbestellung ausfüllen, wir liefern das Web-IO zum Test auf offene Rechnung. Wenn Sie das Gerät innerhalb von 30 Tagen zurück schicken, schreiben wir die Rechnung komplett gut.

Zur Musterbestellung