Java ist eine weit verbreitete, einfach zu erlernende und plattformunabhängige
Programmiersprache. Sie beinhaltet bereits in der Standardversion sämtliche
Klassen und Methoden, die für die Programmierung von TCP/IP-Anwendungen
benötigt werden. Das zeichnet Java als beliebtes Werkzeug aus, um
Anwendungen zu erstellen, die mit dem Web-IO
Digital kommunizieren.
Neben einer aktuellen Java-Umgebung werden keine weiteren Resourcen für
die Ausführung benötigt.
Das folgende Programmbeispiel ermöglicht
das Überwachen und Steuern eines Web-IO 2x Digital und stellt dafür
folgende Funktionen zur Verfügung:
Schalten der Outputs
manuelles oder automatisches Lesen von Inputs, Outputs und Counters
Lesen und Löschen einzelner oder aller Counter
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
PoE gerne kostenlos für 30 Tage zu 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.
mit einer IP-Adresse versehen - mit WuTility kein Problem.
Zusammenstellung der verschiedenen Bedien- und Anzeigeelemente
In der folgenden Abbildung sind die unterschiedlichen Bedien- und Anzeigenelemente
mit dem im Programmcode verwendeten Namen betitelt. Die Zuordnung soll als
Referenz dienen und das Nachvollziehen des nachfolgenden Beispiels erleichtern.
Bei der Benennung der einzelnen Objekte ist es hilfreich, sinngebende
Namen zu verwenden. In diesem Beipiel beschreibt der erste Teil des Namens
die Art des Objektes und der zweite Teil die Funktion.
1. Import der benötigten Sourcen
Zu Beginn eines jeden Programms müssen alle benötigten Quellen
importiert werden.
Die Klasse "Client" erbt alle Eigenschaften der Klasse "JFrame",
um das Programmfenster darzustellen, und bindet die Interfaces "ActionListener"
(Erkennung von Buttonereignissen), "KeyListener" (Erkennung
von Änderungen in Textfeldern) und "Runnable" (ermöglichen
von Threads) durch Import deren Methoden ein.
public class Client extends JFrame implements ActionListener,
KeyListener, Runnable {
...
}
3. Instanziierung der grafischen Elemente
Für die grafischen Elemente (Buttons, CheckBoxes, Label, Panel,
TextFields) werden globale Instanzen erzeugt, damit sie in jeder Methode
des Programms zur Verfügung stehen
private JButton jbConnect = new JButton("Connect");
private JButton jbDisconnect = new JButton("Disconnect");
private JButton jbOutputsRead = new JButton("Read all");
private JButton jbInputsRead = new JButton("Read all");
private JButton jbCounterRead0 = new JButton("Read");
private JButton jbCounterRead1 = new JButton("Read");
private JButton jbCounterReadAll = new JButton("Read");
private JButton jbCounterClear0 = new JButton("Clear");
private JButton jbCounterClear1 = new JButton("Clear");
private JButton jbCounterClearAll = new JButton("Clear");
private JCheckBox jcbCounterPolling = new JCheckBox("Polling");
private JCheckBox jcbOutput0 = new JCheckBox("Output 0");
private JCheckBox jcbOutput1 = new JCheckBox("Output 1");
private JCheckBox jcbOutputPolling = new JCheckBox("Polling");
private JCheckBox jcbInput0 = new JCheckBox("Input 0");
private JCheckBox jcbInput1 = new JCheckBox("Input 1");
private JCheckBox jcbInputPolling = new JCheckBox("Polling");
private JLabel jlStatusBar = new JLabel("No connection");
private JPanel jpIO = new JPanel();
private JTextField jtfInterval = new JTextField("250");
private JTextField jtfCounter0 = new JTextField("0");
private JTextField jtfCounter1 = new JTextField("0");
private JTextField jtfIP = new JTextField();
private JTextField jtfPort = new JTextField("80");
private JTextField jtfPassword = new JTextField();
4. Elemente für die Datenübertragung und das Zeitverhalten
Für die Datenübertragung über eine Socketschnittstelle
werden in diesem Beispiel Instanzen der Klassen "InputStream",
"OutputStream" und "Socket" verwendet.
Der Timer, der das Polling steuert, ist von der Klasse "Timer"
(javax.swing.Timer) abgeleitet.
private Timer tiPoll;
5. Die "main"-Methode
Die "main"-Methode wird automatisch beim Programmstart ausgerufen.
Sie ruft den Konstruktor der Klasse auf.
public static void main(String[] args) {
new Client();
}
6. Der Konstruktor
Im Konstruktor werden Größe, Position und Verhalten von
Anzeigeelementen und dem Programmfenster definiert. Der Konstruktoraufruf
erfolgt ohne die Übergabe von Parametern.
public Client() {
...
}
Im Folgenden wird das JPanel "jpIO" erstellt. Dafür
werden die Anzeigeelement dimensioniert, ggf. inaktiv geschaltet, um
den Ausgangszustand zu erzeugen, und mit einem Listener versehen, damit
auf Eingaben vom Benutzer reagiert werden kann. Abschließend werden
die Elemente dem JPanel hinzugefügt.
Das folgende Programmfragment erstellt die Bedien- und Anzeigeelemente,
die auf der Oberfläche unter dem Begriff "Connection Control"
zusammengefasst sind. Das JPanel, dem diese Elemente hinzugefügt
werden, ist lokal instanziiert, da außerhalb des Konstruktors
nicht mehr darauf zugegriffen wird.
JLabel jlIP = new JLabel("IPAddress");
jlIP.setBounds(20, 40, 120, 20);
Die Statusleiste informiert während des Programmablaufs über
den Status der Socketverbindung zu einem Web-IO. Bereits während
der Instanziierung wurde ihr der Defaultwert "No Connection"
zugewiesen. Im Konstruktor wird die Darstellung in Form von Größe,
Position und Art des Rahmens definiert.
Abschließend werden die Eigenschaften des Fensters bestimmt
und die zuvor erstellten Elemente hinzugefügt. Als letzter Schritt
wird das Programmfenster sichtbar gemacht. Die Initialisierungsphase
ist damit abgeschlossen und das Programm ist nun betriebsbereit.
Ereignisverarbeitung (allgemein): Wurde bei Anzeige- und Bedienelementen
(z. B. Buttons und Textfelder) ein ActionListener registriert, wird
bei ausgelösten Ereignissen die Methode "actionPerformed"
aufgerufen. Der übergebene Parameter enthält unter anderem
die Information, welche Komponente das Ereignis ausgelöst hat.
public void actionPerformed(ActionEvent arg0) {
...
}
Verbindungsaufbau: Durch Betätigen der Schaltfläche
"Connect" wird ein Verbindungsaufbau zu dem angegebenen Web-IO
gestartet. Die Anzeige- und Bedienelemente werden dem Zustand entsprechend
aktiv oder inaktiv geschaltet und, die Statusleiste gibt den Fortschritt
des Verbindungsaufbaus wieder. Wurde der Socket erfolgreich geöffnet
und die Streams für Dateneingabe und Datenausgabe erfolgreich generiert,
startet ein Thread, der eintreffende Daten verarbeitet. Ein Timer, der
entsprechend des geforderten Pollingverhaltens Daten anfordert, wird
gestartet. Tritt während des Verbindungsaufbaus ein Fehler auf,
erfolgt der Ausstieg über eine Exception.
if (arg0.getSource() == jbConnect) {
jbConnect.setEnabled(false);
jlStatusBar.setText("Trying to connect
to " + jtfIP.getText());
new Thread(this).start();
tiPoll = new Timer(Integer.parseInt(jtfInterval.getText()),
this);
tiPoll.start();
for (int i = 0; i < jpIO.getComponentCount();
i++) {
((JComponent) jpIO.getComponent(i)).setEnabled(true);
}
jbDisconnect.setEnabled(true);
jlStatusBar.setText("Connected
to " + jtfIP.getText());
jbConnect.setEnabled(true);
jlStatusBar.setText("Error - No Connection");
}
gewollter Verbindungsabbau: Das Betätigen der Schaltfläche
"Disconnect" ruft die Methode "disconnect" auf.
Diese enthält Anweisunge, welche die Verbindung kontrolliert abbauen
und das Programm für die Aufnahme einer neuen Verbindung vorbereiten.
Da der Verbindungsabbau auch über eine Exception engeleitet werden
kann, erfolgt der Verbindungsabbau in einer separaten Methode und nicht
innerhalb der Methode "actionPerformed".
else if (arg0.getSource() == jbDisconnect) {
disconnect();
}
Outputs schalten: Durch Manipulation der Checkboxen "jcbOutput0"
und "jcbOutput1" kann der jeweilige Ausgang geschaltet werden.
Löst eine der Checkboxen ein Ereignis aus, wird in Abhängigkeit
des gesetzten Status ein String über den Socket an das Web-IO gesendet,
der die Selektion umsetzt.
Outputs und Inputs lesen, Counter lesen und löschen: Die
Out- und Inputs können jeweils über eine Schaltfläche
komplett gelesen werden. Dazu wird ein entsprechender Kommandostring
an das Web-IO gesendet. Genauso verhält es sich mit den Countern.
Jeder Schaltfläche ist ein Kommandostring hinterlegt, der das entsprechende
Ergebnis auslöst.
Polling: Der Timer, der mit dem Verbindungsaufbau gestartet
wurde, löst ebenfalls zyklisch ein Ereignis aus. Je nach Status
der drei Polling-Checkboxen werden bei jedem Zyklus die Outputs, die
Inputs und die Counter angefragt.
else if (arg0.getSource() == tiPoll) {
if (jcbOutputPolling.isSelected()) {
write("GET /output?PW="
+ jtfPassword.getText() + "&");
}
if (jcbInputPolling.isSelected()) {
write("GET /input?PW="
+ jtfPassword.getText() + "&");
}
if (jcbCounterPolling.isSelected()) {
write("GET /counter?PW="
+ jtfPassword.getText() + "&");
}
}
8. Änderungen in Textfeldern verarbeiten
Änderung des Pollingintervalls: Änderungen des Pollingintervalls
werden ohne Bestätigung aktiv. Die Erkennung einer Änderung
erfolgt mittels eines KeyListeners, der dem Textfeld "tfInterval"
hinzufefügt wurde. Dazu muss die Klasse die Methoden des Interfaces
("keyPressed"," KeyReleased" und "keyTyped")
implementieren. Bei Änderung des Inhalts des Textfelds wird in
der Methode "keyPressed" der aktualisierte Wert in das int-Format
konvertiert und an den Timer übergeben. Ist der Inhalt des Textfeldes
nicht in eine Zahl zu konvertieren, erfolgt der Ausstieg über die
Exception. Die Pollingrate wird in diesem Fall nicht geändert.
Die Methode "run" wird bei Zustandekommen einer Socketverbindung
gestartet und läuft als Thread "quasi"-parallel zum eigentlichen
Programm. In der Methode wird der Socket in einer while-Schleife dauerhaft
auf eintreffende Daten überprüft. Bei Verbindungsabbruch wird
der Wert -1 gelesen, was das Abbruchkriterium der Schleife ist.Bei Empfang
eines Wertes >0, wird dieser laut ASCII-Tabelle in ein Zeichen konvertiert
und dem Empfangsstring hinzugefügt. Bei Empfang einer 0 ist der
String komplett empfangen und die Verarbeitung beginnt. Der Anfang jeden
Strings ("output;", "input;", "counter;"
und "counter") gibt Aufschlussüber die Zuordnung der
Daten. Nach der Identifikation der Informationen werden diese extrahiert
und dargestellt.
public void run() {
int iInput, iState;
String sIn = "";
StringTokenizer stToken;
try {
while
((iInput = isInStream.read()) != -1) {
if
(iInput > 0) {
sIn
+= (char) iInput;
}
else {
if
(sIn.startsWith("input")) {
iState
= Integer.parseInt(sIn.replaceFirst("input;", ""));
jcbInput0.setSelected(((iState
& 1) > 0) ? true : false);
jcbInput1.setSelected(((iState
& 2) > 0) ? true : false);
}
else if (sIn.startsWith("output")) {
iState
= Integer.parseInt(sIn.replaceFirst("output;",""));
jcbOutput0.setSelected(((iState
& 1) > 0) ? true : false);
jcbOutput1.setSelected(((iState
& 2) > 0) ? true : false);
}
else if (sIn.startsWith("counter")) {
if
(sIn.startsWith("counter;")) {
sIn
= sIn.replaceFirst("counter;", "");
stToken
= new StringTokenizer(sIn, ";");
jtfCounter0.setText(stToken.nextToken());
jtfCounter1.setText(stToken.nextToken());
}
else {
stToken
= new StringTokenizer(sIn, ";");
if
(stToken.nextToken().equals("counter0")) {
jtfCounter0.setText(stToken.nextToken());
}
else {
jtfCounter1.setText(stToken.nextToken());
}
sIn
= sIn.replaceFirst("counter", "");
}
}
sIn
= "";
}
}
disconnect();
} catch
(IOException e) {
}
}
10. Kommandostrings senden
Mit der Methode "write" werden die zu übermittelnden
Kommandostrings auf den Socket geschrieben. Das Schreiben erfolgt in
zwei Schritten. Die Anweisung "write" übergibt den String
als Bytekette an den Socket. Die Anweisung "flush" sendet
die Bytes an die Gegenstelle. Tritt bei diesem Vorgang ein Fehler auf,
erfolgt der Abbruch über die Exception.
Die Anweisungen für einen ordentlichen Verbindungsabbau sind
in einer separaten Methode ausgelagert, die zum einen über die
Schaltfläche "Disconnect" und zum anderen bei einem Verbindungsabbruch
aufgerufen wird. Zuerst wird die Schaltfläche "Disconnect"
inaktiv geschaltet, die Mitteilung über den Verbindungsabbau in
der Statuszeile ausgegeben und der Pollingtimer gestoppt. Anschließend
erfolgt das Schließen des Sockets. Ist dieser Vorgang erfolgreich,
werden alle Eingabe- und Anzeigeelemente in den Ausgangszustand gebracht.
Schlägt das Schließen fehl, wird die Oberfläche wieder
für eine bestehende Verbindung eingestellt.
private void disconnect() {
jbDisconnect.setEnabled(false);
jlStatusBar.setText("Trying
to disconnect from " + jtfIP.getText());
try {
tiPoll.stop();
soClientSocket.close();
for
(int i = 0; i < jpIO.getComponentCount(); i++) {
((JComponent)
jpIO.getComponent(i)).setEnabled(false);
}
jbConnect.setEnabled(true);
jlStatusBar.setText("No
connection");
jbDisconnect.setEnabled(true);
jlStatusBar.setText("Error
- Connected to " + jtfIP.getText());
}
Das Beispielprogramm 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 Anpassungen 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.