 |
|
 |
 |
W&T Interfaces für TCP/IP, Ethernet, RS-232, RS-485, USB, 20mA Glas- und Kunststoff-LWL, http, SNMP, OPC, I/O digital, I/O analog, ISA, PCI, ...? |
 |
|
 |
|
|
|
.net DLL für Com-Server und Web-IO Produkte
W&T-Produkte einfach aus VB.Net oder C# ansprechen
|
Die DLL für WuT-Geräte
|
VB-Beispiel Com-Server
|
VB-Beispiel Web-IO
|
VB-Beispiel Web-Therm.
|
|
Beschreibung der DLL
|
C#-Beispiel Com-Server
|
C#-Beispiel Web-IO
|
C#-Beispiel Web-Therm.
|
Programmieren in .net - Com-Server und Web-IO Unterstützung mit WuT-DLL
Mit dem Dot-Net Framework und Visualstudio stellt Microsoft bereits
mit den kostenlosen Express-Versionen eine leistungsfähige Entwicklungsumgebung
für Windows-Anwendugen zur Verfügung. Per Drag&Drop
lassen sich mit wenigen Klicks anspruchsvolle Programmoberflächen
zusammenstellen.
Geht es allerdings darum, aus der eigenen Anwendung mit einem anderen
Netzwerkteilnehmer zu kommunizieren, wird die Sache etwas schwieriger.
Das Verwalten verschiedener Threads oder Callback-Prozeduren für
Verbinden, Senden und Empfangen ist für den ungeübten Programmierer
eine echte Herausforderung.
Die WuT-DLL ist genau auf die Software-Schnittstellen von Com-Servern
und Web-IO Produkten zugeschnitten und nimmt dem Programmierer das
Verbindungs- und Daten-Handling komplett ab.
Unterstützt werden folgende Geräte und Funktionen:
| Com-Server |
Web-IO Digital |
Web-IO Analog |
Web-Thermographen |
- Lesen serieller Daten
- Schreiben serieller Daten
- Setzen von Baudrate, Datenbits, Stopbits, Parität und
Handshake-Verfahren
|
- Lesen der Input-Zustände
- Lesen der Counter
- Lesen der Output-Zustände
- Setzen der Outputs
- Löschen der Counter
|
- Lesen der Input-Werte
- Setzen der Output-Werte
|
- Lesen von Temperaturen
- Lesen von Luftfeuchte
- Lesen von Luftdruck
|
|
Com-Server: serielle Daten übers Netzwerk
austauschen mit VB 2008 .net
Dieses Beispiel zeigt Schritt für Schritt, wie mit VB.net (2008)
und der wutdevice.dll ein am Com-Server angeschlossenes serielles
Endgerät angesprochen werden kann. Neben dem Austausch serieller
Daten, zeigt das Beispiel, wie die seriellen Parameter des Com-Servers
aus der eigenen Anwenung voreingestellt werden können.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues VB Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf Imports WiesemannTheis.NetworkIO eingefügt
werden.
Imports WiesemannTheis.NetworkIO
Außerdem müssen die Objekte für die Verbindung auf
den seriellen Port des Com-Servers und das Schnittstellen-Handling
deklariert werden.
Public Class Form1
Dim devData As SerialNetDevice
Dim devCtrl As SerialSetupNetDevice
Dim connPending As Boolean
Schritt 4: Formularobjekte bei Programmstart mit Inhalten füllen
In der Prozedur Form1_Load werden die dev -Objekte für
das Verbindungs-Handling initialisiert. Darüber hinaus werden
ComboBoxen für Auswahl der seriellen Parameter mit Werten gefüllt.
Die möglichen Baudraten werden dazu über SerialSetupNetDevice.GetBaudRates
aus der WuTdevices.dll gelesen.
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
Dim baudRates As Integer()
Dim i As Integer
devData = New SerialNetDevice()
devCtrl = New SerialSetupNetDevice()
devData.Timeout = 2
devCtrl.Timeout = 2
connPending = False
SerialSetupNetDevice.GetBaudRates(baudRates)
For i = 0 To baudRates.Length - 1
co_baudrate.Items.Add(baudRates(i))
Next
co_databits.Items.Add("5")
co_databits.Items.Add("6")
co_databits.Items.Add("7")
co_databits.Items.Add("8")
co_stopbits.Items.Add("1")
co_stopbits.Items.Add("2")
' parity values:
' same order as in SerialSetupNetDevice.Parities
co_parity.Items.Add("none")
co_parity.Items.Add("odd")
co_parity.Items.Add("even")
' handshake values:
' same order as in SerialSetupNetDevice.FlowControl
co_handshake.Items.Add("none")
co_handshake.Items.Add("Xon/Xoff")
co_handshake.Items.Add("RTS/CTS")
For i = 0 To 3
tb_DataPort.Items.Add(8000 + 100 * i)
tb_ControlPort.Items.Add(9094 + 100 * i)
Next
UIStateOffline()
sb_status.Text = "Not connected"
End Sub
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zu den Ports für Daten und Kontrollverbindung aus. Dabei wird
zunächst geprüft, ob die Felder für IP-Adresse und
die benötigten TCP-Ports Informationen enthalten. Über die
Methode SetTarget werden jeweils für Daten- und Kontroll-Verbindung
die IP-Adresse und die entsprechende Portnummer übergeben. Durch
Aufruf der Methode Connectasync wird dann jeweils der Verbindungsaufbau
gestartet. Die Buttons Connect und Disconnect werden
dem Verbindungsstatus entsprechend bedienbar bzw. nicht bedienbar
gesetzt.
Private Sub bt_connect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_connect.Click
If (tb_RemoteIP.Text = "" Or tb_DataPort.Text
= "" Or tb_ControlPort.Text = "") Then
sb_status.Text = "IP and Ports needed!"
ElseIf (devCtrl.SetTarget(tb_RemoteIP.Text, tb_ControlPort.Text,
tb_Password.Text) And devCtrl.ConnectAsync()) Then
If (devData.SetTarget(tb_RemoteIP.Text, tb_DataPort.Text,
"") And devData.ConnectAsync()) Then
sb_status.Text = "Connecting..."
connPending = True
bt_connect.Enabled = False
bt_disconnect.Enabled = True
Else
sb_status.Text = devData.ErrorMessage
devCtrl.Disconnect()
End If
Else
sb_status.Text = devCtrl.ErrorMessage
End If
End Sub
Ein Klick auf den Disconnect Button beendet über die
Methode Disconnect beide Verbindungen. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
Private Sub bt_disconnect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_disconnect.Click
connPending = False
devData.Disconnect()
devCtrl.Disconnect()
UIStateOffline()
sb_status.Text = "Not connected"
End Sub
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob sowohl die Daten- als auch die Kontroll-Verbindung im Status Ready,
also verbunden sind. Durch Aufruf der Prozedur UIStateOnline
bzw. UIStateOffline werden die die Bedienelemente des Formulars
bedienbar bzw. nicht bedienbar gesetzt. Darüber hinaus wird über
die Eigenschaft HasData geprüft, ob Daten vom Com-Server
empfangen wurden. Empfangene Daten werden über die ReceiveString
Methode gelesen und in das Textfeld tb_receiveData geschrieben
Private Sub tm_fixed_Tick(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tm_fixed.Tick
If connPending Then
If (devCtrl.ConnectState = ConnectState.Ready)
And (devData.ConnectState = ConnectState.Ready) Then
connPending = False
sb_status.Text = "Connected!"
UIStateOnline()
AdjustLineState()
tb_receiveData.Text = ""
End If
If (devCtrl.ConnectState = ConnectState.Offline)
Then
connPending = False
sb_status.Text = devCtrl.ErrorMessage
devData.Disconnect()
UIStateOffline()
ElseIf (devData.ConnectState = ConnectState.Offline)
Then
connPending = False
sb_status.Text = devData.ErrorMessage
devCtrl.Disconnect()
UIStateOffline()
End If
End If
If (devData.ConnectState = ConnectState.Ready) Then
If devData.HasData() Then
Dim msg As String
devData.ReceiveString(msg)
tb_receiveData.Text += msg
End If
End If
End Sub
Die Prozeduren UIStateOnline und UIStateOffline setzten
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
Private Sub UIStateOnline()
bt_connect.Enabled = False
bt_disconnect.Enabled = True
bt_set.Enabled = True
bt_sendData.Enabled = True
tb_RemoteIP.Enabled = False
tb_DataPort.Enabled = False
tb_ControlPort.Enabled = False
tb_Password.Enabled = False
End Sub
Private Sub UIStateOffline()
bt_connect.Enabled = True
bt_disconnect.Enabled = False
bt_set.Enabled = False
bt_sendData.Enabled = False
tb_RemoteIP.Enabled = True
tb_DataPort.Enabled = True
tb_ControlPort.Enabled = True
tb_Password.Enabled = True
End Sub
Das Versenden von Daten an den Com-Server wird über ein Klick
auf den Send Button ausgelöst. Für das Senden selbst
kommt die Methode SendString zum Einsatz. Durch Setzen der
cb_CRLF CheckBox kann den Sendedaten ein Carriage-Return-Line-Feed
angehängt werden.
Private Sub bt_sendData_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_sendData.Click
Dim success As Boolean
If cb_CRLF.Checked Then
success = devData.SendStringCRLF(tb_sendData.Text)
Else
success = devData.SendString(tb_sendData.Text)
End If
If Not success Then
sb_status.Text = devData.ErrorMessage
End If
End Sub
Schritt 6: Einstellung der seriellen Übertragungsparameter
Über die Kontrollverbindung können die seriellen Übertragungsparameter
des Com-Servers (Baudrate, Datenbits, Stopbits, Parity und Handshakeverfahren)
eingestellt bzw. ausgelesen werden. Das Setzen wird über ein
Klick auf den Set Button ausgelöst.
Private Sub bt_set_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_set.Click
devCtrl.AllSettingsRead()
devCtrl.BaudRate = CInt(co_baudrate.SelectedItem.ToString())
devCtrl.DataBits = co_databits.SelectedIndex + 5
devCtrl.StopBits = co_stopbits.SelectedIndex + 1
devCtrl.Parity = co_parity.SelectedIndex
devCtrl.Handshake = co_handshake.SelectedIndex
If Not devCtrl.AllSettingsWrite() Then
sb_status.Text = devCtrl.ErrorMessage
Return
End If
' Refresh the display, in case any settings
were not accepted
AdjustLineState()
End Sub
Die Prozedur AdjustLineState kann aufgerufen werden, um die
Anzeigeobjekte des Formulars mit den Einstellungen des Com-Servers
zu aktualisieren.
Private Sub AdjustLineState()
co_baudrate.SelectedIndex = co_baudrate.FindStringExact(devCtrl.BaudRate.ToString())
co_databits.SelectedIndex = devCtrl.DataBits - 5
co_stopbits.SelectedIndex = devCtrl.StopBits - 1
co_parity.SelectedIndex = CInt(devCtrl.Parity)
co_handshake.SelectedIndex = CInt(devCtrl.Handshake)
End Sub
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
VB2010 zu verwenden sein.
|
Web-IO Digital: digitale Inputs und Outputs steuern
und überwachen mit VB 2008 .net
Dieses Beispiel zeigt Schritt für Schritt, wie mit VB.net (2008)
und der wutdevice.dll ein Web-IO Digital 2xIn, 2xOut angesprochen
werden kann.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues VB-Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf Imports WiesemannTheis.NetworkIO eingefügt
werden.
Imports WiesemannTheis.NetworkIO
Außerdem müssen die Objekte und Variablen für die
Verbindung zum Web-IO deklariert werden.
Public Class Form1
Dim dev As DigitalNetDevice
Dim connPending As Boolean
Schritt 4: Formularobjekte bei Programmstart voreinstellen
In der Prozedur Form1_Load wird das dev -Objekt
für das Verbindungs-Handling initialisiert. Darüber hinaus
werden die Bedienelemente so voreingestellt, dass nur der Connect-Button
bedienbar ist. Dazu wird die Prozedur UIStateOffline aufgerufen.
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
dev = New DigitalNetDevice()
dev.Timeout = 2
connPending = False
UIStateOffline()
sb_status.Text = "Not connected"
End Sub
Die Prozeduren UIStateOnline und UIStateOffline setzten
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
Private Sub UIStateOnline()
gb_io.Enabled = True
bt_disconnect.Enabled = True
bt_connect.Enabled = False
End Sub
Private Sub UIStateOffline()
gb_io.Enabled = False
gb_conn.Enabled = True
bt_disconnect.Enabled = False
bt_connect.Enabled = True
End Sub
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zum gewählten Port aus. Dabei wird zunächst geprüft,
ob die Felder für IP-Adresse und die benötigten TCP-Ports
Informationen enthalten. Über die Methode SetTarget werden
die IP-Adresse und die Portnummer übergeben. Durch Aufruf der
Methode Connectasync wird dann der Verbindungsaufbau gestartet.
Die Buttons Connect und Disconnect werden dem Verbindungsstatus
entsprechend bedienbar bzw. nicht bedienbar gesetzt.
Private Sub bt_connect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_connect.Click
If (tb_ip.Text = "" Or tb_port.Text = "")
Then
sb_status.Text = "IP and Port needed!"
ElseIf (dev.SetTarget(tb_ip.Text, tb_port.Text, tb_password.Text)
And dev.ConnectAsync()) Then
sb_status.Text = "Connecting..."
connPending = True
bt_connect.Enabled = False
bt_disconnect.Enabled = True
Else
sb_status.Text = dev.ErrorMessage
End If
End Sub
Ein Klick auf den Disconnect-Button beendet über die
Methode Disconnect die Verbindung. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
Private Sub bt_disconnect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_disconnect.Click
connPending = False
dev.Disconnect()
UIStateOffline()
sb_status.Text = "Not connected"
End Sub
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob die Verbindung im Status Ready - also verbunden ist. Durch
Aufruf der Prozedur UIStateOnline bzw. UIStateOffline
werden die Bedienelemente des Formulars bedienbar bzw. nicht bedienbar
gesetzt. Darüber hinaus wird über die Eigenschaft HasData
geprüft, ob Daten vom Web-IO empfangen wurden. Empfangene Daten
werden über die ReceiveString Methode gelesen und in das
Textfeld tb_receiveData geschrieben.
Private Sub tm_fixed_Tick(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tm_fixed.Tick
If connPending Then
Select dev.ConnectState
Case ConnectState.Ready
connPending = False
sb_status.Text = "Connected!"
UIStateOnline()
ClearDisplay()
Case ConnectState.Offline
connPending = False
sb_status.Text = dev.ErrorMessage
UIStateOffline()
End Select
End If
End Sub
Über die Prozedur ClearDisplay können bei Bedarf
alle Anzeigeobjekte zurückgesetzt werden.
Private Sub ClearDisplay()
For i = 0 To 1
CType(Controls.Find("cb_input"
+ i.ToString, True)(0), CheckBox).Checked = False
CType(Controls.Find("cb_output"
+ i.ToString, True)(0), CheckBox).Checked = False
CType(Controls.Find("tb_counter"
+ i.ToString, True)(0), TextBox).Text = "0"
Next
End Sub
Sollte im Laufe der Verbindung ein Fehler auftreten, wird über
Aufruf der Prozedur DeviceError die Verbindung geschlossen
und die Anzeigeobjekte werden zurückgesetzt
Private Sub DeviceError()
sb_status.Text = dev.ErrorMessage
dev.Disconnect()
UIStateOffline()
End Sub
Die Prozedur TestForDisconnect Error wird von einigen anderen
Prozeduren aufgerufen, um zu überprüfen ob noch eine Verbindung
zum Web-IO besteht. Ist das nicht der Fall, werden die Anzeigeobjekte
entsprechend zurückgesetzt.
Private Sub TestForDisconnect()
If dev.ConnectState <> ConnectState.Ready Then
sb_status.Text = "Connection closed"
UIStateOffline()
End If
End Sub
Schritt 6: IO-Status abfragen, anzeigen und setzen
Über die WuTdevices.dll kann mittels einfacher Funktionsaufrufe
auf Outputs, Inputs und Counter zugegriffen werden.
Setzen der Outputs über die Methode SetOutputState:
Private Sub cb_output_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles cb_output1.Click, cb_output0.Click
Dim Cur_cb As CheckBox
Cur_cb = sender
If Not dev.SetOutputState(Integer.Parse(Cur_cb.Name.Substring(9)),
Cur_cb.Checked) Then
DeviceError()
End If
End Sub
Lesen der Outputs und Inputs über die Methoden ReadOutputs
und ReadInputs:
Private Sub bt_outputs_read_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_outputs_read.Click
If Not dev.ReadOutputs() Then
DeviceError()
Else
ShowIoStates(False, True)
End If
End Sub
Private Sub bt_inputs_read_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_inputs_read.Click
If Not dev.ReadInputs() Then
DeviceError()
Else
ShowIoStates(True, False)
End If
End Sub
Die Prozedur ShowIOState wird aufgerufen, um die IO-Anzeigeobjekte
des Formulars mit den Ergebnissen der ReadInputs- bzw. ReadOutputs-Methode
zu aktualisieren.
Private Sub ShowIoStates(ByVal input As Boolean,
ByVal output As Boolean)
For i = 0 To 1
If (input) Then CType(Controls.Find("cb_input"
+ i.ToString, True)(0), CheckBox).Checked = dev.Inputs(i)
If (output) Then CType(Controls.Find("cb_output"
+ i.ToString, True)(0), CheckBox).Checked = dev.Outputs(i)
Next
TestForDisconnect()
End Sub
Lesen eines Counters über die Methode ReadCounter:
Private Sub bt_counter_read_Click(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles bt_counter_read1.Click,
bt_counter_read0.Click
Dim btn As Button
btn = sender
Dim index As Integer
index = Integer.Parse(btn.Name.Substring(15))
If Not dev.ReadCounter(index) Then
DeviceError()
Else
ShowCounters()
End If
End Sub
Löschen eines Counters über die Methode ClearCounter:
Private Sub bt_counter_clear_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_counter_clear1.Click, bt_counter_clear0.Click
Dim btn As Button
btn = sender
Dim index As Integer
index = Integer.Parse(btn.Name.Substring(16))
If Not dev.ClearCounter(index) Then
DeviceError()
Else
ShowCounters()
End If
End Sub
Lesen aller Counters über die Methode ReadAllCounters:
Private Sub bt_conter_readall_Click(ByVal sender
As System.Object, ByVal e As System.EventArgs) Handles bt_conter_readall.Click
If Not dev.ReadAllCounters() Then
DeviceError()
Else
ShowCounters()
End If
End Sub
Löschen aller Counters über die Methode ClearAllCounters:
Private Sub bt_counter_clearall_Click(ByVal sender
As System.Object, ByVal e As System.EventArgs) Handles bt_counter_clearall.Click
If Not dev.ClearAllCounters() Then
DeviceError()
Else
ShowCounters()
End If
End Sub
Die Prozedur ShowCounters wird aufgerufen, um die Counter-Anzeigeobjekte
des Formulars mit den Ergebnissen der Counterlese-Methoden zu aktualisieren.
Private Sub ShowCounters()
For i = 0 To 1
If dev.Valid(i) Then
CType(Controls.Find("tb_counter"
+ i.ToString, True)(0), TextBox).Text = dev.Counters(i).ToString()
End If
Next
TestForDisconnect()
End Sub
Über die CheckBoxen cb_output_polling, cb_input_polling
und cb_counter_polling kann der Anwender festlegen, dass die
Anzeige zyklisch aktualisiert wird. Die nötigen Abfragen werden
über den Timer tm_variable ausgelöst.
Private Sub tm_variable_Tick(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tm_variable.Tick
If dev.ConnectState = ConnectState.Ready And cb_output_polling.Checked
Then
If (dev.ReadOutputs()) Then
ShowIoStates(False, True)
Else
DeviceError()
End If
End If
If dev.ConnectState = ConnectState.Ready And cb_input_polling.Checked
Then
If (dev.ReadInputs()) Then
ShowIoStates(True, False)
Else
DeviceError()
End If
End If
If dev.ConnectState = ConnectState.Ready And cb_counter_polling.Checked
Then
If (dev.ReadAllCounters()) Then
ShowCounters()
Else
DeviceError()
End If
End If
End Sub
In die TextBox tb_interval kann der Anwender eintragen in
welchem Zyklus das Polling erfolgen soll.
Private Sub tb_interval_TextChanged(ByVal sender
As System.Object, ByVal e As System.EventArgs) Handles tb_interval.TextChanged
tm_variable.Interval = Integer.Parse(tb_interval.Text)
End Sub
Um das gezeigte Beispiel auf mehr als 2 IOs zu ändern, muss
lediglich das Formular um die entsprechenden Bedienelemente (fortlaufend
nummeriert) erweitert werden und im Quelltext müssen die Grenzen
der Indizierung angepasst werden.
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
C# 2010 zu verwenden sein.
|
Web-IO Analog: Temperaturen, Luftfeuchte, Luftdruck
und andere analoge Signale überwachen und steuern mit VB 2008
.net
Dieses Beispiel zeigt Schritt für Schritt, wie mit VB.net (2008)
und der wutdevice.dll Web-IO Analog oder Web-Thermograph angesprochen
werden können.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues VB-Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf Imports WiesemannTheis.NetworkIO eingefügt
werden.
Imports WiesemannTheis.NetworkIO
Außerdem müssen die Objekte und Variablen für die
Verbindung zum Web-IO deklariert und initialisiert werden.
Public Class Form1
Dim dev As AnalogNetDevice
Dim connPending As Boolean
Dim maxio
Schritt 4: Formularobjekte bei Programmstart voreinstellen
In der Prozedur Form1_Load wird das dev -Objekte
für das Verbindungs-Handling initialisiert. Darüber hinaus
werden die Bedienelemente so voreingestellt, dass nur der Connect-Button
bedienbar ist. Dazu wird die Prozedur UIStateOffline aufgerufen.
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
dev = New AnalogNetDevice()
dev.Timeout = 2
connPending = False
UIStateOffline()
sb_status.Text = "Not connected"
maxio = 8
End Sub
Die Prozeduren UIStateOnline und UIStateOffline setzten
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
Private Sub UIStateOnline()
gb_io.Enabled = True
bt_disconnect.Enabled = True
bt_connect.Enabled = False
End Sub
Private Sub UIStateOffline()
gb_io.Enabled = False
bt_disconnect.Enabled = False
bt_connect.Enabled = True
End Sub
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zum gewähtlen Port aus. Dabei wird zunächst geprüft,
ob die Felder für IP-Adresse und die benötigten TCP-Ports
Informationen enthalten. Über die Methode SetTarget werden
die IP-Adresse und die Portnummer übergeben. Durch Aufruf der
Methode Connectasync wird dann der Verbindungsaufbau gestartet.
Die Buttons Connect und Disconnect werden dem Verbindungsstatus
entsprechend bedienbar bzw. nicht bedienbar gesetzt.
Private Sub bt_connect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_connect.Click
If (tb_ip.Text = "" Or tb_Port.Text = "")
Then
sb_status.Text = "IP and Port needed!"
ElseIf (dev.SetTarget(tb_ip.Text, tb_Port.Text, tb_Password.Text)
And dev.ConnectAsync()) Then
sb_status.Text = "Connecting..."
connPending = True
bt_connect.Enabled = False
bt_disconnect.Enabled = True
Else
sb_status.Text = dev.ErrorMessage
End If
End Sub
Ein Klick auf den Disconnect-Button beendet über die
Methode Disconnect die Verbindung. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
Private Sub bt_disconnect_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_disconnect.Click
connPending = False
dev.Disconnect()
UIStateOffline()
sb_status.Text = "Not connected"
End Sub
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob die Verbindung im Status Ready - also verbunden ist. Durch
Aufruf der Prozedur UIStateOnline bzw. UIStateOffline
werden die Bedienelemente des Formulars bedienbar bzw. nicht bedienbar
gesetzt.
Private Sub tm_fixed_Tick(ByVal sender As System.Object, ByVal
e As System.EventArgs) Handles tm_fixed.Tick
If connPending Then
Select Case dev.ConnectState
Case ConnectState.Ready
connPending = False
sb_status.Text =
"Connected!"
UIStateOnline()
ClearDisplay()
Case ConnectState.Offline
connPending = False
sb_status.Text =
dev.ErrorMessage
UIStateOffline()
End Select
End If
End Sub
Über die Prozedur ClearDisplay können bei Bedarf
alle Anzeigeobjekte zurückgesetzt werden.
Private Sub ClearDisplay()
For i = 0 To maxio - 1
CType(Controls.Find("tb_value"
+ i.ToString, True)(0), TextBox).Text = ""
CType(Controls.Find("tb_unit"
+ i.ToString, True)(0), TextBox).Text = ""
CType(Controls.Find("tb_descr"
+ i.ToString, True)(0), TextBox).Text = ""
Next
End Sub
Sollte im Laufe der Verbindung ein Fehler auftreten, wird über
Aufruf der Prozedur DeviceError die Verbindung geschlossen
und die Anzeigeobjekte werden zurückgesetzt
Private Sub DeviceError()
sb_status.Text = dev.ErrorMessage
dev.Disconnect()
UIStateOffline()
End Sub
Schritt 6: Signalstatus abfragen, anzeigen und setzen
Über die WuTdevices.dll kann mittels einfacher Funktionsaufrufe
auf die analogen Signale zugegriffen werden.
Lesen aller verfügbaren analogen Signale über die Methode
ReadAllValues:
Private Sub bt_readall_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_readall.Click
If Not dev.ReadAllValues() Then
DeviceError()
Else
ShowValues()
End If
End Sub
Soll gezielt ein einzelnes Signal gelesen werden, erfolgt das über
die Methode ReadValue und Übergabe des entsprechenden
Index :
Private Sub bt_read_Click(ByVal sender As System.Object, ByVal
e As System.EventArgs) Handles bt_read7.Click, bt_read6.Click, bt_read5.Click,
bt_read4.Click, bt_read3.Click, bt_read2.Click, bt_read1.Click,
bt_read0.Click
Dim btn As Button
btn = sender
Dim index As Integer
index = Integer.Parse(btn.Name.Substring(7))
If Not dev.ReadValue(index) Then
DeviceError()
Else
ShowValues()
End If
End Sub
Die Prozedur ShowValues wird aufgerufen, um die Anzeigeobjekte des
Formulars mit den Ergebnissen der ReadValue- bzw. ReadAllValues-Methode
zu aktualisieren.
Private Sub ShowValues()
For i = 0 To maxio - 1
CType(Controls.Find("tb_value" +
i.ToString, True)(0), TextBox).Text = dev.Values(i).ToString()
CType(Controls.Find("tb_unit" +
i.ToString, True)(0), TextBox).Text = dev.Units(i)
CType(Controls.Find("tb_descr" +
i.ToString, True)(0), TextBox).Text = dev.Descriptions(i)
Next
If (dev.ConnectState <> ConnectState.Ready) Then
sb_status.Text = "Connection closed"
UIStateOffline()
End If
End Sub
Das Setzen eines analogen Signals (nur #57661, #57662) erfolgt über
die Methode SetValue. Gesetzt wird bei Klick der einen der
Write-Buttons der in das entsprechende Value-Feld eingetragene
Wert.
Private Sub bt_write_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles bt_write7.Click, bt_write6.Click,
bt_write5.Click, bt_write4.Click, bt_write3.Click, bt_write2.Click,
bt_write1.Click, bt_write0.Click
Dim btn As Button
btn = sender
Dim index As Integer
Dim value As Double
index = Integer.Parse(btn.Name.Substring(8))
Try
value = Double.Parse(CType(Controls.Find("tb_value"
+ index.ToString, True)(0), TextBox).Text)
Catch ex As FormatException
sb_status.Text = ex.Message
Return
If Not dev.SetValue(index, value) Then
DeviceError()
Else
ShowValues()
End If
End Try
End Sub
Über die CheckBoxen cb_polling, cb_input_polling kann
der Anwender festlegen, dass die Anzeige zyklisch aktualisiert wird.
Die nötigen Abfragen werden über den Timer tm_variable
ausgelöst.
Private Sub tm_variable_Tick(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tm_variable.Tick
If (dev.ConnectState = ConnectState.Ready) And cb_counter_polling.Checked
Then
If (dev.ReadAllValues()) Then
ShowValues()
Else
DeviceError()
End If
End If
End Sub
In die TextBox tb_interval kann der Anwender eintragen, in
welchem Zyklus das Polling erfolgen soll.
private void tb_interval_TextChanged(object
sender, EventArgs e)
{
this.tm_variable.Interval = int.Parse(this.tb_interval.Text);
}
Um das gezeigte Beispiel auf die gewünschte Anzahl von Signalen
zu ändern muss lediglich das Formular um die entsprechenden Bedienelemente
(fortlaufend nummeriert) reduziert werden und im Quelltext muss die
Variable maxio angepasst werden.
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
C# 2010 zu verwenden sein.
|
.net-Klassenbibliothek für W&T-Geräte
Die in WuTdevices.dll enthaltene Klassenbibliothek ist zur Einbindung
in Ihre eigenen .net-Programme gedacht. Der Entwurf legt vor allem Wert auf
einfache Benutzung in einfachen Szenarien, und insbesondere die Beispielprogramme
sind auf den einfachsten Anwendungsfall, die Ansteuerung jeweils eines einzigen
Gerätes, ausgelegt.
Der gemeinsame Namensraum der Klassenbibliothek, also das, was man z. B. in
einer using-Anweisung angibt, ist:
Die enthaltenen Klassen sind NetDevice sowie,
davon abgeleitet:
AnalogNetDevice
DigitalNetDevice
SerialNetDevice
SerialSetupNetDevice
Klasse NetDevice
Gemeinsame (abstrakte) Basisklasse, deren Methoden und Datenelemente in allen
abgeleiteten speziellen Geräteklassen wiederverwendet werden.
Methoden
bool
SetTarget(string address,
ushort port,string password)
bool
SetTarget(string address,
string port,string password)
bool Connect()
bool ConnectAsync()
void Disconnect()
Typen
enum
ConnectState
{ Offline, Pending, Ready }
Eigenschaften
int Timeout
ConnectState ConnectState
int SocketError
string ErrorMessage
Übliche Verwendung
- Objekt einer abgeleiteten Klasse anlegen, evtl.
Timeout
anpassen (Einheit ist Sekunden)
SetTarget() aufrufen
Connect()
- spezielle E/A-Methoden der abgeleiteten Klasse verwenden
- bei Problemen (E/A-Methode gibt
false zurück)
steht eine Fehlerbeschreibung in ErrorMessage. Ggf. erlaubt der
Fehlercode in SocketError eine weitergehende Behandlung des
Fehlers.
Disconnect()
Dabei ist es entweder möglich, die Netzwerkverbindung ständig offen zu halten.
Das ist empfehlenswert bei eher häufigen E/A-Zugriffen (im Abstand von einigen
Sekunden oder weniger). Oder man kann die Verbindung für jeden E/A-Zugriff neu
öffnen und wieder schließen. Das hat vor allem den Vorteil, dass die Behandlung
von Verbindungsstörungen eine weniger aufwendige Programmlogik erfordert.
Statt des einfachen blockierenden Verbindungsaufbaus mit Connect()
verwenden die Beispielprogrammen übrigens ConnectAsync(). Diese
Methode kehrt sofort zurück, und das Ergebnis des Verbindungsaufbaus erfährt man
durch Beobachten der ConnectState-Eigenschaft, was in den
Beispielprogrammen innerhalb eines 100ms-Timers geschieht.
Klasse AnalogNetDevice
Geräteklasse für Web-IO Klima und Web-IO Analog universell.
Methoden
bool
SetValue(int index,
double value)
bool
ReadValue(int index)
bool ReadAllValues()
Eigenschaften
int ItemCount
bool[] Valid
double[] Values
string[] Units
string[] Descriptions
Die Datenfelder sind mit einer konstanten Feldgröße von 8 angelegt.
Wieviele Analogkanäle das Gerät tatsächlich besitzt, wird beim Aufruf von
ReadAllValues() ermittelt und in ItemCount eingetragen.
Welche Felder innerhalb Values[] gültig sind, wird außerdem in
Valid[] festgehalten. Das ist vor allem bedeutsam für Sensoreingänge,
die überhaupt keinen gültigen Wert liefern können (weil z. B. gar kein Sensor
angeschlossen ist).
Bitte beachten Sie: Die Beschreibungstexte in Descriptions[]
werden durch ReadAllValues() nicht aktualisiert, nur durch
ReadValue().
Klasse DigitalNetDevice
Geräteklasse für Web-IO Digital.
Methoden
bool
ReadCounter(int index)
bool ReadAllCounters()
bool
ClearCounter(int index)
bool ClearAllCounters()
bool ReadInputs()
bool ReadOutputs()
bool
SetOutputState(int index,
bool state)
bool TestPassword()
Eigenschaften
int ItemCount
bool[] Valid
int[] Counters
bool[] Inputs
bool[] Outputs
Die Datenfelder sind mit einer konstanten Feldgröße von 24 angelegt und
allesamt nur lesbar. Der Inhalt von Inputs[], Outputs[]
und Counters[] wird durch ReadInputs(),
ReadOutputs() bzw. ReadAllCounters() aktualisiert.
Letztere Funktion ermittelt außerdem die Anzahl der Zähler (und damit der
Eingänge) des Gerätes und trägt sie in ItemCount ein.
Zuweisungen an das Outputs[]-Feld sind, wie bereits erwähnt,
nicht möglich. Um Ausgänge zu setzen, verwenden Sie stattdessen die Methode
SetOutputState().
Das Valid[]-Feld ist normalerweise bedeutungslos und höchstens
dann von Interesse, wenn Sie zwar ReadCounter(), nicht aber
ReadAllCounters() aufrufen. Anders als bei AnalogNetDevice (wo
ReadValue() immerhin mehr Informationen als
ReadAllValues() liefert) gäbe es aber keinen vernünftigen Grund,
das zu tun.
Eine separate Funktion TestPassword() existiert, weil die
eigentlichen E/A-Zugriffe leider keine direkte Rückmeldung über ein ungültiges
Passwort geben. Das Gerät liefert in dem Fall einfach überhaupt keine Antwort,
und der E/A-Zugriff schlägt erst nach Ablauf der in der
Timeout-Eigenschaft festgelegten Zeitschranke fehl.
Klasse SerialNetDevice
Geräteklasse für Com-Server, zum Senden und Empfangen von seriellen Daten.
Methoden
bool
SendString(string text)
bool
SendStringCRLF(string text)
bool HasData()
bool
WaitForData(int milliSeconds)
bool
ReceiveString(out string text)
Bitte führen Sie ReceiveString() nur aus, wenn tatsächlich
Daten bereitstehen, wenn also HasData() oder
WaitForData() true zurückgegeben
haben. Das Beispielprogramm ruft zu diesem Zweck HasData() in
einem 100ms-Timer auf.
Klasse SerialSetupNetDevice
Geräteklasse für den Zugriff auf die Schnittstellenparameter eines
seriellen Com-Servers.
Methoden
bool AllSettingsRead()
bool AllSettingsWrite()
static void
GetBaudRates(out
int[] baud)
Typen
enum
Parity
{ None, Odd, Even }
enum
Handshake
{ None, Software, Hardware }
Eigenschaften
int BaudRate
int DataBits
int StopBits
Parity Parity
Handshake Handshake
Die Eigenschaften gehören zu einem lokalen Abbild der Geräteparameter, das
durch AllSettingsRead() aktualisiert und durch
AllSettingsWrite() ins Gerät zurückgeschrieben wird.
GetBaudRates() liefert eine sortierte Liste der von
Com-Servern grundsätzlich unterstützen Baudraten. Das Beispielprogramm
benutzt sie, um die Combobox einstellbarer Baudraten auszufüllen.
Hilfsklassen
Die "Arrays" unter den oben aufgeführten Klasseneigenschaften sind über
Hilfsklassen implementiert, hauptsächlich über die generische Klasse
ReadOnlyArray<T>. So ist z. B.
für Descriptions der tatsächliche Datentyp
ReadOnlyArray<string>.
Der Zweck dieses Aufwands ist es, unsinnige Veränderung der Daten
durch den Benutzer der Klassenbibliothek verhindern zu können.
Über die übliche Array-Syntax Descriptions[0] etc. ist damit
lesender, nicht aber schreibender Zugriff auf die einzelnen Datenfelder möglich,
ebenso steht die gewohnte Array-Eigenschaft Length zur Verfügung,
sowie Iterationen mit foreach().
|
Com-Server: serielle Daten übers Netzwerk
austauschen mit C#
Dieses Beispiel zeigt Schritt für Schritt, wie mit C# (2008)
und der wutdevice.dll ein am Com-Server angeschlossenes serielles
Endgerät angesprochen werden kann. Neben dem Austausch serieller
Daten, zeigt das Beispiel, wie die seriellen Parameter des Com-Servers
aus der eigenen Anwenung voreingestellt werden können.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues C#-Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf using WiesemannTheis.NetworkIO eingefügt
werden.
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using WiesemannTheis.NetworkIO; // must reference WuTdevices.dll!
Auserdem müssen die Objekte für die Verbindung auf den
seriellen Port des Com-Servers und das Schnittstellen-Handling deklariert
und Initialisiert werden.
namespace cs_terminal
{
public partial class Form1 : Form
{
SerialNetDevice devData;
SerialSetupNetDevice devCtrl;
bool connPending;
public Form1()
{
InitializeComponent();
devData = new SerialNetDevice();
devCtrl = new SerialSetupNetDevice();
devData.Timeout = 2;
devCtrl.Timeout = 2;
connPending = false;
}
Schritt 4: Formularobjekte bei Programmstart mit Inhalten füllen
In der Prozedur Form1_Load werden die ComboBoxen für
Auswahl der seriellen Parameter mit Werten gefüllt. Die möglichen
Baudraten werden dazu über SerialSetupNetDevice.GetBaudRates
aus der WuTdevices.dll gelesen.
private void Form1_Load(object
sender, EventArgs e)
{
int[] baudRates;
int i;
SerialSetupNetDevice.GetBaudRates(out
baudRates);
for (i = 0; i < baudRates.Length;
i++)
co_baudrate.Items.Add(baudRates[i]);
co_databits.Items.Add("5");
co_databits.Items.Add("6");
co_databits.Items.Add("7");
co_databits.Items.Add("8");
co_stopbits.Items.Add("1");
co_stopbits.Items.Add("2");
// parity values:
// same order as in SerialSetupNetDevice.Parities
co_parity.Items.Add("none");
co_parity.Items.Add("odd");
co_parity.Items.Add("even");
// handshake values:
// same order as in SerialSetupNetDevice.FlowControl
co_handshake.Items.Add("none");
co_handshake.Items.Add("Xon/Xoff");
co_handshake.Items.Add("RTS/CTS");
for (i = 0; i < 4; i++)
{
tb_DataPort.Items.Add(8000
+ 100 * i);
tb_ControlPort.Items.Add(9094
+ 100 * i);
}
UIStateOffline();
sb_status.Text = "Not connected";
}
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zu den Ports für Daten und Kontrollverbindung aus. Dabei wird
zunächst geprüft, ob die Felder für IP-Adresse und
die benötigten TCP-Ports Informationen enthalten. Über die
Methode SetTarget werden jeweils für Daten- und Kontroll-Verbindung
die IP-Adresse und die entsprechende Portnummer übergeben. Durch
Aufruf der Methode Connectasync wird dann jeweils der Verbindungsaufbau
gestartet. Die Buttons Connect und Disconnect werden
dem Verbindungsstatus entsprechend bedienbar bzw. nicht bedienbar
gesetzt.
private void bt_connect_Click(object
sender, EventArgs e)
{
if (tb_RemoteIP.Text == ""
|| co_DataPort.Text == "" || co_ControlPort.Text == "")
sb_status.Text = "IP
and Ports needed!";
else if (devCtrl.SetTarget(tb_RemoteIP.Text,
co_ControlPort.Text, tb_Password.Text)
&& devCtrl.ConnectAsync())
{
if (devData.SetTarget(tb_RemoteIP.Text,
co_DataPort.Text, "")
&& devData.ConnectAsync())
{
sb_status.Text
= "Connecting...";
connPending
= true;
bt_connect.Enabled
= false;
bt_disconnect.Enabled
= true;
}
else
{
sb_status.Text
= devData.ErrorMessage;
devCtrl.Disconnect();
}
}
else
sb_status.Text = devCtrl.ErrorMessage;
}
Ein Klick auf den Disconnect Button beendet über die
Methode Disconnect beide Verbindungen. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
private void bt_disconnect_Click(object
sender, EventArgs e)
{
connPending = false;
devData.Disconnect();
devCtrl.Disconnect();
UIStateOffline();
sb_status.Text = "Not connected";
}
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob sowohl die Daten- als auch die Kontroll-Verbindung im Status Ready
also verbunden sind. Durch Aufruf der Prozedur UIStateOnline
bzw. UIStateOffline werden die die Bedienelemente des Formulars
bedienbar bzw. nicht bedienbar gesetzt. Darüber hinaus wird über
die Eigenschaft HasData geprüft, ob Daten vom Com-Server
empfangen wurden. Empfangene Daten werden über die ReceiveString
Methode gelesen und in das Textfeld tb_receiveData geschrieben
private void tm_fixed_Tick(object
sender, EventArgs e)
{
if (connPending)
{
if(devCtrl.ConnectState
== ConnectState.Ready
&& devData.ConnectState
== ConnectState.Ready )
{
connPending
= false;
sb_status.Text
= "Connected!";
UIStateOnline();
AdjustLineState();
tb_receiveData.Text
= "";
}
if(devCtrl.ConnectState
== ConnectState.Offline)
{
connPending
= false;
sb_status.Text
= devCtrl.ErrorMessage;
devData.Disconnect();
UIStateOffline();
}
else if (devData.ConnectState
== ConnectState.Offline)
{
connPending
= false;
sb_status.Text
= devData.ErrorMessage;
devCtrl.Disconnect();
UIStateOffline();
}
}
if (devData.ConnectState == ConnectState.Ready
&& devData.HasData())
{
string msg;
devData.ReceiveString(out
msg);
tb_receiveData.Text
+= msg;
}
}
Die Prozeduren UIStateOnline und UIStateOffline setzen
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
private void UIStateOnline()
{
bt_connect.Enabled = false;
bt_disconnect.Enabled = true;
bt_set.Enabled = true;
bt_sendData.Enabled = true;
tb_RemoteIP.Enabled = false;
co_DataPort.Enabled = false;
co_ControlPort.Enabled = false;
tb_Password.Enabled = false;
}
private void UIStateOffline()
{
bt_connect.Enabled = true;
bt_disconnect.Enabled = false;
bt_set.Enabled = false;
bt_sendData.Enabled = false;
tb_RemoteIP.Enabled = true;
co_DataPort.Enabled = true;
co_ControlPort.Enabled = true;
tb_Password.Enabled = true;
}
Das Versenden von Daten an den Com-Server wird über ein Klick
auf den Send-Button ausgelöst. Für das Senden selbst
kommt die Methode SendString zum Einsatz. Durch Setzen der
cb_CRLF CheckBox kann den Sendedaten ein Carriage-Return-Line-Feed
angehängt werden.
private void cb_sendData_Click(object
sender, EventArgs e)
{
bool success;
if (cb_CRLF.Checked)
success = devData.SendStringCRLF(tb_sendData.Text);
else
success = devData.SendString(tb_sendData.Text);
if( !success )
sb_status.Text =
devData.ErrorMessage;
}
Schritt 6: Einstellung der seriellen Übertragungsparameter
Über die Kontrollverbindung können die seriellen Übertragungsparameter
des Com-Servers (Baudrate, Datenbits, Stopbits, Parity und Handshakeverfahren)
eingestellt bzw. ausgelesen werden. Das Setzen wird über ein
Klick auf den Set Button ausgelöst.
private void bt_set_Click(object
sender, EventArgs e)
{
devCtrl.AllSettingsRead();
devCtrl.BaudRate = int.Parse(co_baudrate.SelectedItem.ToString());
devCtrl.DataBits = co_databits.SelectedIndex
+ 5;
devCtrl.StopBits = co_stopbits.SelectedIndex
+ 1;
devCtrl.Parity = (Parity)co_parity.SelectedIndex;
devCtrl.Handshake = (Handshake)co_handshake.SelectedIndex;
if (!devCtrl.AllSettingsWrite())
{
sb_status.Text = devCtrl.ErrorMessage;
return;
}
// Refresh the display, in case
any settings were not accepted
AdjustLineState();
}
Die Prozedur AdjustLineState kann aufgerufen werden, um die
Anzeigeobjekte des Formulars mit den Einstellungen des Com-Servers
zu aktualisieren.
private void AdjustLineState()
{
co_baudrate.SelectedIndex = co_baudrate.FindStringExact(devCtrl.BaudRate.ToString());
co_databits.SelectedIndex = devCtrl.DataBits
- 5;
co_stopbits.SelectedIndex = devCtrl.StopBits
- 1;
co_parity.SelectedIndex = (int)devCtrl.Parity;
co_handshake.SelectedIndex = (int)devCtrl.Handshake;
}
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
C# 2010 zu verwenden sein.
|
Web-IO Digital: digitale Inputs und Outputs steuern
und überwachen mit C#
Dieses Beispiel zeigt Schritt für Schritt, wie mit C# (2008)
und der wutdevice.dll ein Web-IO Digital 2xIn, 2xOut angesprochen
werden kann.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues C#-Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf using WiesemannTheis.NetworkIO eingefügt
werden.
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using WiesemannTheis.NetworkIO; // must reference WuTdevices.dll!
Außerdem müssen die Objekte und Variablen für die
Verbindung zum Web-IO deklariert und initialisiert werden.
namespace webio_client
{
public partial class Form1 : Form
{
DigitalNetDevice dev;
bool connPending;
public Form1()
{
InitializeComponent();
dev = new DigitalNetDevice();
dev.Timeout = 2;
connPending = false;
}
Schritt 4: Formularobjekte bei Programmstart voreinstellen
In der Prozedur Form1_Load werden die Bedienelemente so voreingestellt,
dass nur der Connect-Button bedienbar ist. Dazu wird die Prozedur
UIStateOffline aufgerufen.
private void Form1_Load(object
sender, EventArgs e)
{
UIStateOffline();
sb_status.Text = "Not connected";
}
private void UIStateOffline()
{
gb_io.Enabled = false;
gb_conn.Enabled = true;
bt_disconnect.Enabled = false;
bt_connect.Enabled = true;
bt_test.Enabled = false;
}
Die Prozeduren UIStateOnline und UIStateOffline setzten
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
private void UIStateOnline()
{
gb_io.Enabled = true;
gb_conn.Enabled = false;
bt_disconnect.Enabled = true;
bt_connect.Enabled = false;
}
private void UIStateOffline()
{
gb_io.Enabled = false;
gb_conn.Enabled = true;
bt_disconnect.Enabled = false;
bt_connect.Enabled = true;
}
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zum gewählten Port aus. Dabei wird zunächst geprüft,
ob die Felder für IP-Adresse und die benötigten TCP-Ports
Informationen enthalten. Über die Methode SetTarget werden
die IP-Adresse und die Portnummer übergeben. Durch Aufruf der
Methode Connectasync wird dann der Verbindungsaufbau gestartet.
Die Buttons Connect und Disconnect werden dem Verbindungsstatus
entsprechend bedienbar bzw. nicht bedienbar gesetzt.
private void bt_connect_Click(object
sender, EventArgs e)
{
if (this.tb_ip.Text == ""
|| this.tb_Port.Text == "")
sb_status.Text = "IP
and Port needed!";
else if (dev.SetTarget(tb_ip.Text,
tb_Port.Text, tb_Password.Text)
&& dev.ConnectAsync())
{
sb_status.Text = "Connecting...";
connPending = true;
bt_connect.Enabled
= false;
bt_disconnect.Enabled
= true;
}
else
sb_status.Text = dev.ErrorMessage;
}
Ein Klick auf den Disconnect-Button beendet über die
Methode Disconnect die Verbindung. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
private void bt_disconnect_Click(object
sender, EventArgs e)
{
connPending = false;
devData.Disconnect();
UIStateOffline();
sb_status.Text = "Not connected";
}
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob die Verbindung im Status Ready - also verbunden ist. Durch
Aufruf der Prozedur UIStateOnline bzw. UIStateOffline
werden die Bedienelemente des Formulars bedienbar bzw. nicht bedienbar
gesetzt. Darüber hinaus wird über die Eigenschaft HasData
geprüft, ob Daten vom Web-IO empfangen wurden. Empfangene Daten
werden über die ReceiveString Methode gelesen und in das
Textfeld tb_receiveData geschrieben
private void tm_fixed_Tick(object
sender, EventArgs e)
{
if (connPending)
switch (dev.ConnectState)
{
case ConnectState.Ready:
connPending
= false;
sb_status.Text
= "Connected!";
UIStateOnline();
ClearDisplay();
break;
case ConnectState.Offline:
connPending
= false;
sb_status.Text
= dev.ErrorMessage;
UIStateOffline();
break;
}
}
Über die Prozedur ClearDisplay können bei Bedarf
alle Anzeigeobjekte zurückgesetzt werden.
private void ClearDisplay()
{
for (int i = 0; i < 2; i++)
{
((CheckBox)Controls.Find("cb_input"
+ i, true)[0]).Checked = false;
((CheckBox)Controls.Find("cb_output"
+ i, true)[0]).Checked = false;
((TextBox)Controls.Find("tb_counter"
+ i, true)[0]).Text = "";
}
}
Sollte im Laufe der Verbindung ein Fehler auftreten, wird über
Aufruf der Prozedur DeviceError die Verbindung geschlossen
und die Anzeigeobjekte werden zurückgesetzt
private void DeviceError()
{
sb_status.Text = dev.ErrorMessage;
dev.Disconnect();
UIStateOffline();
}
Die Prozedur TestForDisconnect Error wird von einigen anderen
Prozeduren aufgerufen, um zu Überprüfen ob noch eine Verbindung
zum Web-IO besteht. Ist das nicht der Fall, werden die Anzeigeobjekte
entsprechend zurückgesetzt.
private void TestForDisconnect()
{
if (dev.ConnectState != ConnectState.Ready)
{
sb_status.Text =
"Connection closed";
UIStateOffline();
}
}
Beim Schließen des Programms wird über die Methode Disconnect
des dev Objektes die Verbindung geschlossen.
private void Form1_FormClosing(object
sender, FormClosingEventArgs e)
{
dev.Disconnect();
}
Schritt 6: IO-Status abfragen and Aneigen und setzen
Über die WuTdevices.dll kann mittels einfacher Funktionsaufrufe
auf Outputs, Inputs und Counter zugegriffen werden.
Setzen der Outputs über die Methode SetOutputState:
private void cb_output_Click(object
sender, EventArgs e)
{
CheckBox Cur_cb = (CheckBox)sender;
if (!dev.SetOutputState(int.Parse(Cur_cb.Name.Substring(9)),
Cur_cb.Checked))
DeviceError();
}
Lesen der Outputs und Inputs über die Methoden ReadOutputs
und ReadInputs:
private void bt_outputs_read_Click(object
sender, EventArgs e)
{
if (!dev.ReadOutputs())
DeviceError();
else
ShowIoStates(false,
true);
}
private void bt_inputs_read_Click(object
sender, EventArgs e)
{
if (!dev.ReadInputs())
DeviceError();
else
ShowIoStates(true,
false);
}
Die Prozedur ShowIOState wird aufgerufen, um die IO-Anzeigeobjekte
des Formulars mit den Ergebnissen der ReadInputs- bzw. ReadOutputs-Methode
zu aktualisieren.
private void ShowIoStates(bool
input, bool output)
{
for (int i = 0; i < 2; i++)
{
if (input)
((CheckBox)Controls.Find("cb_input"
+ i, true)[0]).Checked = dev.Inputs[i];
if (output)
((CheckBox)Controls.Find("cb_output"
+ i, true)[0]).Checked = dev.Outputs[i];
}
TestForDisconnect();
}
Lesen eines Counters über die Methode ReadCounter:
private void bt_counter_read_Click(object
sender, EventArgs e)
{
Button btn = (Button)sender;
int index;
index = int.Parse(btn.Name.Substring(15));
if (!dev.ReadCounter(index))
DeviceError();
else
ShowCounters();
}
Löschen eines Counters über die Methode ClearCounter:
private void bt_counter_clear_Click(object
sender, EventArgs e)
{
Button btn = (Button)sender;
int index;
index = int.Parse(btn.Name.Substring(15));
if (!dev.ClearCounter(index))
DeviceError();
else
ShowCounters();
}
Lesen aller Counters über die Methode ReadAllCounters:
private void bt_counter_readall_Click(object
sender, EventArgs e)
{
if (!dev.ReadAllCounters())
DeviceError();
else
ShowCounters();
}
Löschen aller Counters über die Methode ClearAllCounters:
private void bt_counter_clearall_Click(object
sender, EventArgs e)
{
if (!dev.ClearAllCounters())
DeviceError();
else
ShowCounters();
}
Die Prozedur ShowCounters wird aufgerufen, um die Counter-Anzeigeobjekte
des Formulars mit den Ergebnissen der Counterlese-Methoden zu aktualisieren.
private void ShowCounters()
{
for (int i = 0; i < 2; i++)
((TextBox)Controls.Find("tb_counter"
+ i, true)[0]).Text
= dev.Valid[i]
? dev.Counters[i].ToString() : "";
TestForDisconnect();
}
Über die CheckBoxen cb_output_polling, cb_input_polling
und cb_counter_polling kann der Anwender festlegen, dass die
Anzeige zyklisch aktualisiert wird. Die nötigen Abfragen werden
über den Timer tm_variable ausgelöst.
private void tm_variable_Tick(object
sender, EventArgs e)
{
if (dev.ConnectState == ConnectState.Ready
&& cb_output_polling.Checked)
{
if (dev.ReadOutputs())
ShowIoStates(false,
true);
else
DeviceError();
}
if (dev.ConnectState == ConnectState.Ready
&& cb_input_polling.Checked)
{
if (dev.ReadInputs())
ShowIoStates(true,
false);
else
DeviceError();
}
if (dev.ConnectState == ConnectState.Ready
&& cb_counter_polling.Checked)
{
if (dev.ReadAllCounters())
ShowCounters();
else
DeviceError();
}
}
In die TextBox tb_interval kann der Anwender eintragen in
welchem Zyklus das Polling erfolgen soll.
private void tb_interval_TextChanged(object
sender, EventArgs e)
{
this.tm_variable.Interval = int.Parse(this.tb_interval.Text);
}
Um das gezeigte Beispiel auf mehr als 2 IOs zu ändern muss lediglich
das Formular um die entsprechenden Bedienelemente (fortlaufend nummeriert)
erweitert werden und im Quelltext müssen die Grenzen der Indizierung
angepasst werden.
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
C# 2010 zu verwenden sein.
|
Web-IO Analog: Temperaturen, Luftfeuchte, Luftdruck
und andere analoge Signale überwachen und steuern mit C#
Dieses Beispiel zeigt Schritt für Schritt, wie mit C# (2008)
und der wutdevice.dll Web-IO Analog oder Web-Thermograph angesprochen
werden können.
Schritt 1: Einbinden der WuTdevices.dll
Nachdem ein neues C#-Projekt angelegt wurde, muss zunächst die
WuTdevices.dll als Verweis hinzugefügt werden. Kopieren Sie die
Datei "WuTdevices.dll" in das Projektverzeichnis. Klicken
Sie dann mit der rechten Maustaste im Projektmappenmanager auf das
aktuelle Projekt und wählen "Verweis hinzufügen".
Es erscheint ein Dialogfenster. Wechseln Sie dort in das Register
"Durchsuchen" und öffnen Sie die Datei "WuTdevices.dll".
Schritt 2 : Erstellen der Programmoberfläche
Erstellen Sie für dieses Beispiel ein Programmformular mit folgenden
Elementen:

Schritt 3: Einbinden und Initialisieren der WuTdevices.dll im Programmquelltext.
Dazu muss im Kopf using WiesemannTheis.NetworkIO eingefügt
werden.
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using WiesemannTheis.NetworkIO; // must reference WuTdevices.dll!
Außerdem müssen die Objekte und Variablen für die
Verbindung zum Web-IO deklariert und initialisiert werden.
namespace webio_client
{
public partial class Form1 : Form
{
AnalogNetDevice dev;
bool connPending;
int maxio;
public Form1()
{
InitializeComponent();
dev = new AnalogNetDevice();
dev.Timeout = 2;
connPending = false;
maxio = 8;
}
Schritt 4: Formularobjekte bei Programmstart voreinstellen
In der Prozedur Form1_Load werden die Bedienelemente so voreingestellt,
dass nur der Connect-Button bedienbar ist. Dazu wird die Prozedur
UIStateOffline aufgerufen.
private void Form1_Load(object
sender, EventArgs e)
{
UIStateOffline();
sb_status.Text = "Not connected";
}
Die Prozeduren UIStateOnline und UIStateOffline setzten
die Bedienelemente des Formulars passend zum Verbindungsstatus bedienbar
bzw. nicht bedienbar.
private void UIStateOnline()
{
gb_io.Enabled = true;
gb_conn.Enabled = false;
bt_disconnect.Enabled = true;
bt_connect.Enabled = false;
}
private void UIStateOffline()
{
gb_io.Enabled = false;
gb_conn.Enabled = true;
bt_disconnect.Enabled = false;
bt_connect.Enabled = true;
}
Schritt 5: Verbindungskontrolle
Hier beginnt der eigentliche Einsatz der WuTdevices.dll.
Ein Klick auf den Connect-Button löst den Verbindungsaufbau
zum gewählten Port aus. Dabei wird zunächst geprüft,
ob die Felder für IP-Adresse und die benötigten TCP-Ports
Informationen enthalten. Über die Methode SetTarget werden
die IP-Adresse und die Portnummer übergeben. Durch Aufruf der
Methode Connectasync wird dann der Verbindungsaufbau gestartet.
Die Buttons Connect und Disconnect werden dem Verbindungsstatus
entsprechend bedienbar bzw. nicht bedienbar gesetzt.
private void bt_connect_Click(object
sender, EventArgs e)
{
if (this.tb_ip.Text == ""
|| this.tb_Port.Text == "")
sb_status.Text = "IP
and Port needed!";
else if (dev.SetTarget(tb_ip.Text,
tb_Port.Text, tb_Password.Text)
&& dev.ConnectAsync())
{
sb_status.Text = "Connecting...";
connPending = true;
bt_connect.Enabled
= false;
bt_disconnect.Enabled
= true;
}
else
sb_status.Text = dev.ErrorMessage;
}
Ein Klick auf den Disconnect-Button beendet über die
Methode Disconnect die Verbindung. Durch Aufruf der Prozedur
UIStateOffline werden die Bedienelemente des Formulars bedienbar
bzw. nicht bedienbar gesetzt.
private void bt_disconnect_Click(object
sender, EventArgs e)
{
connPending = false;
devData.Disconnect();
UIStateOffline();
sb_status.Text = "Not connected";
}
Die eigentliche Verbindungsüberwachung erfolgt über den
Timer tm_fixed. In einem Intervall von 100ms wird geprüft,
ob die Verbindung im Status Ready - also verbunden ist. Durch
Aufruf der Prozedur UIStateOnline bzw. UIStateOffline
werden die Bedienelemente des Formulars bedienbar bzw. nicht bedienbar
gesetzt.
private void tm_fixed_Tick(object
sender, EventArgs e)
{
if (connPending)
switch (dev.ConnectState)
{
case ConnectState.Ready:
connPending
= false;
sb_status.Text
= "Connected!";
UIStateOnline();
ClearDisplay();
break;
case ConnectState.Offline:
connPending
= false;
sb_status.Text
= dev.ErrorMessage;
UIStateOffline();
break;
}
}
Über die Prozedur ClearDisplay können bei Bedarf
alle Anzeigeobjekte zurückgesetzt werden.
private void ClearDisplay()
{
for (int i = 0; i < maxio;
i++)
{
((TextBox)Controls.Find("tb_value"
+ i, true)[0]).Text = "";
((TextBox)Controls.Find("tb_unit"
+ i, true)[0]).Text = "";
((TextBox)Controls.Find("tb_descr"
+ i, true)[0]).Text = "";
}
}
Sollte im Laufe der Verbindung ein Fehler auftreten, wird über
Aufruf der Prozedur DeviceError die Verbindung geschlossen
und die Anzeigeobjekte werden zurückgesetzt
private void DeviceError()
{
sb_status.Text = dev.ErrorMessage;
dev.Disconnect();
UIStateOffline();
}
Beim Schließen des Programms wird über die Methode Disconnect
des dev Objektes die Verbindung geschlossen.
private void Form1_FormClosing(object
sender, FormClosingEventArgs e)
{
dev.Disconnect();
}
Schritt 6: Signalstatus abfragen, anzeigen und setzen
Über die WuTdevices.dll kann mittels einfacher Funktionsaufrufe
auf die analogen Signale zugegriffen werden.
Lesen aller verfügbaren analogen Signaleüber die Methode
ReadAllValues:
private void bt_readall_Click(object
sender, EventArgs e)
{
if (!dev.ReadAllValues())
DeviceError();
else
ShowValues();
}
Soll gezielt ein einzelnes Signal gelesen werden, erfolgt das über
die Methode ReadValue und Übergabe des entsprechenden
Index :
private void bt_readx_Click(object sender,
EventArgs e)
{
Button btn = (Button)sender;
int index;
index = int.Parse(btn.Name.Substring(7));
if (!dev.ReadValue(index))
DeviceError();
else
ShowValues();
}
Die Prozedur ShowValues wird aufgerufen, um die Anzeigeobjekte des
Formulars mit den Ergebnissen der ReadValue- bzw. ReadAllValues-Methode
zu aktualisieren.
private void ShowValues()
{
for (int i = 0; i < maxio;
i++)
{
((TextBox)Controls.Find("tb_value"
+ i, true)[0]).Text
= dev.Valid[i]
? dev.Values[i].ToString() : "";
((TextBox)Controls.Find("tb_unit"
+ i, true)[0]).Text
= dev.Units[i];
((TextBox)Controls.Find("tb_descr"
+ i, true)[0]).Text
= dev.Descriptions[i];
}
if (dev.ConnectState != ConnectState.Ready)
{
sb_status.Text = "Connection
closed";
UIStateOffline();
}
}
Das Setzen eines analogen Signals (nur #57661, #57662) erfolgt über
die Methode SetValue. Gesetzt wird bei Klick auf eins der eines
der Write-Buttons der in das entsprechende Value-Feld
eingetragene Wert.
private void bt_writex_Click(object
sender, EventArgs e)
{
Button btn = (Button)sender;
int index;
double value;
index = int.Parse(btn.Name.Substring(8));
try
{
value = double.Parse(((TextBox)Controls.Find("tb_value"
+ index, true)[0]).Text);
}
catch (FormatException ex)
{
sb_status.Text =
ex.Message;
return;
}
if (!dev.SetValue(index, value))
DeviceError();
else
ShowValues();
}
Über die CheckBoxen cb_polling, cb_input_polling kann
der Anwender festlegen, dass die Anzeige zyklisch aktualisiert wird.
Die nötigen Abfragen werden über den Timer tm_variable
ausgelöst.
private void tm_variable_Tick(object
sender, EventArgs e)
{
if (dev.ConnectState == ConnectState.Ready
&& cb_polling.Checked)
{
if (dev.ReadAllValues())
ShowValues();
else
DeviceError();
}
}
In die TextBox tb_interval kann der Anwender eintragen in
welchem Zyklus das Polling erfolgen soll.
private void tb_interval_TextChanged(object
sender, EventArgs e)
{
this.tm_variable.Interval = int.Parse(this.tb_interval.Text);
}
Um das gezeigte Beispiel auf die gewünschte Anzahl von Signalen
zu ändern muss lediglich das Formular um die entsprechenden Bedienelemente
(fortlaufend nummeriert) reduziert werden und im Quelltext muss die
Variable maxio angepasst werden.
Mit wenigen Anpassungen sollte das hier gezeigte Programm auch in
C# 2010 zu verwenden sein.
|
|
|
|
|
|
|
|
|
| |
|
|
| Wir sind gerne persönlich für Sie da! |
Wiesemann & Theis GmbH |
Tel.: 0202/2680-110 (Mo-Fr. 8-17 Uhr) |
| Porschestr. 12 |
Fax: 0202/2680-265 |
| 42279 Wuppertal |
individuelle E-Mail |
|
|
© Wiesemann & Theis GmbH, Irrtum und Änderungen vorbehalten:
Da wir Fehler machen können, darf keine unserer Aussagen ungeprüft verwendet werden.
Bitte melden Sie uns alle Ihnen bekannt gewordenen Irrtümer oder Mißverständlichkeiten, damit
wir diese so schnell wie möglich erkennen und beseitigen können.
|
|
|
|
|