# Lesen aus Stream



## Parallax (22 April 2008)

Hallo

ich habs nun fast geschafft und am Wochenende a weng rum Probiert und mir einige Befehle angeschaut und getestet....

so dein Beispiel hab ich mir auch angesehen allerdings habe ich nun anbei wieder eine frage


Also du nimmst den Stream den die in der Beckhoffanweisung angegeben hast und ließt daraus die daten... 

So wenn ich nun 60 Variablen habe wie kann ich auf den entsprechenden wert zurückgreifen...

brauch ich dann für jede Variable einen anderen Stream oder wie wird das gehandhabt....

Mfg
Andre Wagner


----------



## drfunfrock (22 April 2008)

Du kannst für jede Variable einen Stream (nur den ADSBinaryReader nehmen) aufmachen oder  - und jetzt wird es etwas umständlich - nur einen Stream aufmachen und gibst dann beim Lesen mit stream.Read Offset und Länge an. Das bedeutet aber, du musst den Kram genau verwalten. Bsp: 

1. Var ist Boolean, dann ist Offset=0 und Länge=1
2. Var ist INteger, dann ist Offset=1 und Länge=2
3. Var ist Boolean, dann ist Offset 1+2=3 und die Länge 2
usw. 

Da mach ich lieber für jeden Lesevorgang einen Stream auf. Bsp für Integer:


```
Dim reader As AdsBinaryReader
Me.adsStream = New AdsStream(2)

reader = New AdsBinaryReader(Me.Stream)
Me.adsClient.Read(Me.hVar, Me.adsStream)
Me.adsStream.Position = 0
Me.value = reader.ReadInt16()
```

Wenn das ganz in einem Objekt plaziert ist, schreibst du es nur einmal. Normalerweise liegt die Lese-Funktion in einem Thread, damit der Rest nicht blockiert ist. 

Das geht mit:


```
thread = New Thread(AddressOf DeineFunktion)
thread.Priority = ThreadPriority.BelowNormal
thread.IsBackground = True
thread.Start()
```


----------



## Parallax (22 April 2008)

aja... so in etwa hab ich mir das auch schon gedacht....

jetzt habe ich nur ein problem...

ich habe mich eingelesen das es ja möglichkeiten gibt einen Wert bei einem bestimmten ereigniss (zum beispiel Cmd_click) zu lesen....

allerdings is mir aus vb6 bekannt das lese operationen eine bestimmte zeit brauchen und somit wird bei 60-70 vars das programm sehr langsam....

bei VB6 konnte das einfach behoben werden ich dem ich die Vars einfach am Startanfang connecte und somit immer den aktuellen Wert habe..

das ist wichtig da ich ständig im hintergrund mit den Werten rechnen  muss um beispielsweise die Maschinengeschwindigkeit anzupassen.... daher is der einmalige Read befehl ungünstig...

nun hat mir ja beckhoff erzählt das ich mit CreateVarHandle auch connecten kann....

mein Code sieht bis her so aus (nur der ADS Code das andere is ja uninteresannt

bei der Allgemeinen Dimensionierung:


```
Private tcAds As New TcAdsClient 'Ads Client einem Objekt zuweisen

    Private sStream As New AdsStream(2) 'Stream mit der länge 2 (für Int) erstellen

    Dim binReader As New AdsBinaryReader(sStream) 'BinReader für den Stream wie es mir von beckhoff gesagt wurde
```

unter Form_Load


```
Try

            tcAds.Synchronize = True 'Verbinden mit Port und festlegen des Timeouts
            tcAds.Connect(301)
            tcAds.Timeout = 1000


            REM  tcAds.CreateVariableHandle("TEST.EINGÄNGE.TEST")
            tcAds.AddDeviceNotification(61472, 0, sStream, 0, 2, AdsTransMode.OnChange, 55, 100, varError)

           

        Catch
            MsgBox("Fehler")


        End Try
```
das in REM gelegte funktioniert genau so wie das mit andere nur ich arbeite lieber mit Ports....

so etz hab ich die device notification...

so laut beschreibung von beckhoff wird ja nun bei änderung ein event ausgeführt

wie kann ich dieses event mit Code versehen und meine variablen bsp "varInt16" mit dem wert befüllen ohne das ich alle 100 ms 60 lesebefehle ausführen muss....

bei VB6 hat das ja mit Connect super geklappt....

mfg
Parallax

PS: Catch wird nicht mehr angesprungen also müsste ja mein wirres gecode nun zumindest stimmen und arbeiten


----------



## drfunfrock (22 April 2008)

Ich arbeite zur Zeit nicht mit Notifications, sondern habe einen Timer, der die Funktion zum Lesen einmal ein der Sekunde startet. Damit die Anwendung nicht so träge ist, habe ich dafür einen Thread, der parallel zum Programm läuft.  


```
Imports System.Threading

    Private Sub thread_a()
        While RunThreads
             LeseAlleVariablen()
            Thread.Sleep(timeA)
        End While
    End Sub
```
Das passte mir zur Zeit besser in den Kram, weil ich für die Notifications die Verwaltung aufboren muss. Ich weiss noch nicht, wie ich das mache. 


Der Ablauf sieht so aus: 

1) Client einrichten (kennst du auch):


```
Me.tcADS = New TcAdsClient()
        Me.tcADS.Synchronize = True
        Me.tcADS.Connect(Me.ADSAdress, Me.ADSPort)
        Me.tcADS.Timeout = ADSTimeout
```
2) Variable connecten: 
2a) Stream initialisieren


```
Try
            Me.adsStream = New AdsStream(VarSize)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
```
2b) Connect Variable

```
Try
            Me.hVar = adsClient.CreateVariableHandle(VarPath)
        Catch ex As Exception
            Me.mConnect = False
        End Try
```
3a) Lesen

```
Dim reader As AdsBinaryReader

        reader = New AdsBinaryReader(Me.Stream)   ' stream aus 2a)
        Me.adsClient.Read(Me.hVar, Me.adsStream)
        Me.adsStream.Position = 0
        Me.value = reader.ReadInt16()
        reader = Nothing
```
3b) Schreiben 


```
Dim writer As AdsBinaryWriter
        writer = New AdsBinaryWriter(Me.adsStream)
        Me.adsStream.Position = 0
        writer.Write(CType(Me.value, Int16))
        Me.adsClient.Write(Me.hVar, Me.adsStream)
        writer = Nothing
```


----------



## drfunfrock (22 April 2008)

Wenn du mit Threads arbeitest und noch nicht die Hilfe gelesen hast, dann bringe das hier in der Window_Load-Rouotine unter: 


```
CheckForIllegalCrossThreadCalls = False
```


----------



## Parallax (22 April 2008)

Hi irgendwie geht das bei mir nicht.. ich gib dir mal wieder meinen Code, das ist einfacher

Allgemein:


```
Public Class frmMain


    Private tcAds As New TcAdsClient

    Private sStream As New AdsStream(2)

    Dim binReader As New AdsBinaryReader(sStream)
```

das bleibt ja gleich bei einer Testvariable


```
Try

            tcAds.Synchronize = True 'Verbinden mit Port und festlegen des Timeouts
            tcAds.Connect(301)
            tcAds.Timeout = 1000

            varTest = tcAds.CreateVariableHandle("TEST.EINGÄNGE.TEST")
            REM   varTest = tcAds.AddDeviceNotification(61472, 0, sStream, 0, 2, AdsTransMode.OnChange, 55, 100, varError)

            tcAds.Read(varTest, sStream)



            sStream.Position = 0
            REM   varTest = sStream.?
            sStream = Nothing

            Me.TextBox1.Text = varTest

        Catch
            MsgBox("Fehler")


        End Try
```

was ich nicht ganz verstehe ist, normal müsste doch in varTest schon der Wert drin stehen der ein "Eingänge.Test" vorhanden ist weil wo will er den wert sonst hin tun.... als was muss ich varTest deklarieren... ich habs etz als Integer

nur da steht a astronomisch hoher wert drin.....

das zweite: sStream. kennt kein ReadInt sonder nur Read und ReadAny...??

mfg
Andre Wagner...

mittlerweile hast net nur n Kaffee gut sondern a ganzen besäufniss (natürlich nur mit kaffee^^)


----------



## drfunfrock (22 April 2008)

Also erstmal:

bei catch...

schreibst du: 


```
catch ex as TwinCat.Ads.AdsException
  MessageBox.Show(ex.Messge)
```
Dann siehste, was schief geht. 

Dann steht da bei dir:

```
tcAds.Read(varTest, sStream)
```
Du nimmst den Rückgabewert nicht entgegen und packst in dein Editcontroll das Händle der Verbindung rein  


Appropo Kaffe: Davon habe ich hier massen. Aber schreib lieber mal, wenn du fertig bist.


----------



## Parallax (22 April 2008)

Also nervlich fertig seid 1,5 Monaten....

Mit der Anlage hoffentlich in 2 Wochen

so mit dem Satz 
"Du nimmst den Rückgabewert nicht entgegen und packst in dein Editcontroll das Händle der Verbindung rein"

kann ich etz mal gar nix anfangen... also das Handle wird doch durch 
  varTest = tcAds.CreateVariableHandle("TEST.EINGÄNGE.TEST")
erzeugt (wobei ich immer noch net ganz sicher bin was ein Handle ist....)

So für mich heißt das das "Test.Eingänge.Test"  überwacht wird....

so varTest ist ja dann im Prinzip der Rückgabewert der Funktion der ja im normalfall hier nichts über den Inhalt der Var aussagt...

also was is den das Handle und was soll ich anstatt
tcAds.Read(varTest, sStream)

sonst schreiben...

Also Read sagt ja das er da VarHandle nimmt (was ich immer noch net weiß was es sein soll) und das in den Stream schreibt.... 

??
und ich habs mit allen Readern probiert die ich gefunden hab ich hab immer noch kein ReadInt.... komisch..

das mit der Errormessage wollte ich später noch machen damit ich dann bei einem Fehler gleich auslesen kann was los ist, das hab ich bisher nur so simpel gemacht damit ich überhaupt seh ob catch angesprungen wird

mfg
Parallax


----------



## Parallax (22 April 2008)

ah ok hab den fehler gefunden... hab stream benutzt anstadt reader...


ok bekomm nun den Wert....

so die Handels create ich nun in Form_Load

und auslesen tu ich den Stream in einem Timer ereigniss

der reader sollte ja schnell genug sein um den stream von 60 vars einmal alle 50ms einmal abzudaten... oder...

so das ganze etz noch mit write probieren und dann kanns los gehn...

Ich kauf scho mal a Fass Bier... und Kaffee...^^


was machst du eigentlich genau beruflich weil du scheinbar ja net in nächster nähe arbeitest..


----------



## drfunfrock (22 April 2008)

Das mit den 60Var sollte klappen, wenn du sie einmal die Sekunde liesst, aber bestimmt nicht alle 50ms. Du kannst auch einen Teil schneller lesen, als den anderen Teil der Variablen

Wenn dann deine GUI etwas Träge wird, musst du dafür einen Thread einsetzen. Dann ist dein Fenster der 1. Thread wie du ihn schon hast und die Leseroutine der 2. Thread. Dann hast du sozusagen 2. Programme in einem und die behindern sich nicht gegenseitig und du bekommst Multitasking. 

Beruflich mache ich hier alles von Elektronikentwicklung für EOL-Testprozesse bis zur Automatisierung mit Beckhoff. Dann arbeite ich auch nicht in D, sondern in Norwegen und nach München sind es etwa 1900km.


----------



## Parallax (22 April 2008)

Ah norwegen....

Mein Vater is fanatiker wenn es um Norwegen geht...

Hat vor kurzem ne Motorradtour zum Nordcap und zurück gemacht...

Will er dieses oder nächstes Jahr wieder machen...


ok das mit dem Thread muss ich mir mal Anschauen aber ich brauch die daten schnell weil damit zum beispiel Temperaturen und geschwindigkeiten geregelt werden und alle sek is zu langsam....



aber nun ein neues Problem (wer hätte is gedacht)

so nun wollte ich das ganze einmal ausprobieren wie das in echt aussieht und habe die lese Routine in einen Timer (50ms) gepackt...

beim ersten durchlauf liest er den Wert aus und zeigt ihn an...

im 2ten durchlauf bekomme ich eine unbehandelte ausnahme bei 
tcads.Read

```
tcAds.Read(varTest, sStream)
            sStream.Position = 0
            varTest = binReader.ReadInt16()
            sStream = Nothing
            Me.TextBox1.Text = varTest
```
scheinbar macht er das nur einmal aber nicht öfter... hast du eine erklärung dafür oder muss ich einfach nur den Stream oder Reader neuladen...

mfg
Andre Wagner


----------



## drfunfrock (22 April 2008)

1) Wenn du mit Basic hier eine Regelung bauen willst, fällst du auf die Nase. :sm19ie Regelung gehört in die SPS. 

2) Die Exception solltest du dir Anzeigen lassen. 


3) Du setzt den Stream auf "nothing" und das ist die Ursache.


----------



## Parallax (22 April 2008)

ok konnte es beheben... neuinitieren des readers und der stream mit New blabla hat geholfen... gibt es da eine komfortabere lösung oder macht man das so... was passiert wenn ich den stream nicht auf nothing setze


arbeitet einwandfrei...

warum sollte man mit VB keine regelung schreiben können???

die mathematischen möglichkeiten zur berechnung von stellgrößen sind gegeben....

es ist klar das ein PID-Regler der vorkonfiguriert ist besser arbeitet es geht hier jedoch nur um das angleiche ein paar geringer werte wobei ein P und I anteil ausreichend wäre...

mfg


----------



## drfunfrock (22 April 2008)

1) Weil die Kopplung weder Realtime-Ansprüchen gerecht wird, noch ist sie sonderlich zuverlässig. 

2) Gesetzt den Fall, der PC hat ein Problem, was passiert auf der SPS? Du kannst auf der SPS alles mit ST machen und die sollte wesentlich zuverlässiger sein.

3) Damit trennst du auch die Steuerung von der Schnittstelle zum Menschen und das ist ein Merkmal guter Software. 


Du kannst es versuchen und es wird vielleicht sogar funktionieren, aber du hast dann 2 Komponenten, die die Maschine steuern. Und denk daran, VB ist kein Realtimesystem! Das kann Instabilitäten beim Regler nach sich ziehen.  Ich lasse die SPS immer die Komplette Maschine steuern und der PC setzt nur die Parameter.


----------



## Parallax (24 April 2008)

hm... naja ich habe einen Timer der mir im prinzip die zyklische funktion übernimmt... 

allerdings hab ich da ja im prinzip keine großen sachen... die regelung ist weniger eine regelung sondern ein vergleich 2er werte und eine reaktion darauf...


so nun habe ich gestern a weng mit meinem neuen Wissen zumgebastelt und funktioniert optimal... sollte ich dieses jahr nach norwegen kommen (was gar net so abwegig ist) bring ich viel bier mit....

ok next problem...

so das mit dem Threat....

ich habe nun mal so ca 30 vars eingebunden und es geht auch alles opti nur wie befürchtet wird die Anwendung sehr träge... im endzustand also nutzlos....

gut aber da ich ja einen profi hab sollte das mit dem Threat ja kein problem werden....


ok du hast mir ja gezeigt wie ich so nen threat erstelle... kann ich die Funktion die dazu gehört einfach mit in die Form Class schreiben oder muss ich da n modul oder sowas erstellen...

wenn ich threat.start aufrufe läuft dann die funktion die ganze zeit und den ganzen tag oder muss ich da auch noch irgendwie so a art zyklus vorgeben wie beim timer...

und dann noch die address of Funktion

angenommen die funktion heißt Private Function LesenSchreiben()

was is dann die addresse.. kann ich da einfach LesenSchreiben() reinschreiben oder muss ich da noch was beachten...

danke
mfg
Andre Wagner


----------



## drfunfrock (24 April 2008)

Ja der Thread ist nichts anderes, als eine Funktion, die als paralleles Programm läuft, bis sie an ihr Ende kommt. Dh. wenn du zyklich arbeiten willst, kannst du den Thread jedesmal neu starten oder eine Kombination von While-Schleife  und Pause im Thread einlegen. Der AdressOf-Operator benötigt nur den Namen der Routine!


----------



## Neals (24 April 2008)

*Protokol*

Wenn du dir mal solche Protokolle ansiehst, wird dir auffallen warum deine Anwendung langsam wird.

Es gibt eine mindestgröße für jedes Paket, wenn du also eine Variable per ADS abrufst dann wird immer das ganze Packet verschickt in dem dann 99% der Daten nur Müll sind um das Packet voll zu bekommen. Nun rufst du 30 Packete alle 100ms oder so ab. Ist klar das du damit unnötig viele Daten transportierst und verarbeitest die du garnicht brauchst.

Lösung:
Schreib dir in der SPS eine Struktur in der alle wichtigen Variablen enthalten sind die du abrufen willst. Nun rufst du diese Struktur ab und ließt die Daten einzelnd aus dem Stream, sprich ReadInt, ReadDouble, ReadSingle usw.
Du wirst sehen das deine Anwendung um einiges schneller wird, ohne das du Threads benötigst.


----------



## drfunfrock (24 April 2008)

Danke für den Tip, denn den kannte ich auch nicht.


----------



## Parallax (24 April 2008)

OK das mit dem Thread funzt einwandfrei... 


is echt ne super erfindung son Multitasking

etz hab ich noch ein Problem:

Ich beschreibe Variablen zyklisch mit werten welche aus dem Programm abgefragt werden...

beim beenden des Progs verwende ich .Dispose()

damit wird allerdings scheinbar nur die verbindung zur Steuerung getrennt also muss ich ja noch alle handels mit DeleteVariableHandle schließen oder sehe ich das falsch...

dabei ergibt sich ein neues Problem... die Werte bleiben im Sysmanager gespeichert und die Anlage läuft weiter auch wenn ich das Prog beende...

nun könnte ich alle vars auf 0 setzen aber das problem is nun das im nächsten Zyklus alle vars wieder mit den Aktuellen werten beschrieben werden...

gibt es da eine Funktion die die Verbindung und Handels trennt und dabei alles rücksetzt???

mfg
Wagner Andre


----------



## drfunfrock (24 April 2008)

Schreibe doch zum Schluss in eine Var, so dass deine PLS weiss, wann sie stoppen soll.

Ich gebe immer alle Variablen explizit frei, dh. vor dem client.Dispose, lösche ich die Handles mit DeleteVariableHandle. Das ist auch der Grund für meine Konstruktion mit der Liste. Leider hat Beckhoff nicht dokumentiert, ob mit client.Dispose auch alle Variablenhandles freigegeben werden. Zumindestens hilft die explizite Freigabe, so dass der der PC nicht nach einiger Zeit rebootet werden muss. So sind jedenfalls meine Erfahrungen.


----------



## Neals (24 April 2008)

Man kann die PLC auch im Programm stoppen, weiß nicht wie es unter VB gemacht wird, da ich immer in C# schreibe, aber hier mal nen Link:

http://infosys.beckhoff.com/content/1031/tcsample_net/html/twincat.ads.sample08.htm


----------



## drfunfrock (24 April 2008)

Dazu muss er aber die Info zur SPS schicken. Das was er benötigt, ist wohl einzig, dass die Anlage geordnet heruntergefahren wird und keinen Stop der SPS.


----------

