Beiträge anzeigen

Diese Sektion erlaubt es ihnen alle Beiträge dieses Mitglieds zu sehen. Beachten sie, dass sie nur solche Beiträge sehen können, zu denen sie auch Zugriffsrechte haben.


Themen - ADS_0x1

Seiten: [1]
1
SPS-Hardware / 1wire embedded data systems / eds OW-Server an Beckhoff
« am: 18. Dezember 2017, 23:23:07 »
Hallo zusammen,

nachdem ich gerade den Eintrag zur Anbindung einer Wetterstation geschrieben habe, dachte ich, ich schreibe auch noch meine Erfahrungen mit meinem onewire Interface nieder. Ich habe mir dazu den OW-Server von embedded data systems zugelegt, da es nach ein wenig Recherche einer der günstigsten 'von der Stange' - Interfaces ist. Ich habe mich an dem Beispiel vom yahoo weatherstation Baustein von oscat orientiert. Prinzipiell funktioniert das ganze sehr gut, aber es gibt ein paar Nachteile:

  • Lediglich 22 1wire devices möglich - und das, obwohl 3 Linien aufgezogen werden können
  • 5 V Spannungsversorgung von extern benötigt, leider keine Unterstützung für 24 V
  • 7.205 Bytes bei 10 DS18B20 und einem iButton - bei den maximal möglichen 22 devices also doppelt so viel Speicher notwendig - das machen nicht alle CPUs mit

Dafür hat er aber auch einige Vorteile:

  • Web-Interface, das auch einfach zum Daten-Logging genutzt werden kann - ganz ohne Steuerung oder Software
  • Konfiguration und Inbetriebnahme sehr einfach durchführbar

In TwinCAT habe ich dazu folgenden Programmcode genutzt:

Datenstrukturen:
TYPE st_oneWireTemperatur :
STRUCT
romID : STRING[16];
name : STRING[32];
temperatur : REAL;
Zeit : DT;
END_STRUCT
END_TYPE

TYPE st_oneWireButton :
STRUCT
romID : STRING[16];
name : STRING[32];
active : BOOL;
Zeit : DT;
END_STRUCT
END_TYPE

Einen Baustein, der den romIDs Namen zuweist:

IF i_b_enable THEN
i := 0;
FOR i:=1 TO 30 BY 1 DO
IF io_a_Temperaturen[i].romID = '83041ABCD434FF28' THEN
io_a_Temperaturen[i].name := '1';
ELSIF io_a_Temperaturen[i].romID = '130417ABCD66FF28' THEN
io_a_Temperaturen[i].name := '2';
ELSIF io_a_Temperaturen[i].romID = 'FD041ABCD542FF28' THEN
io_a_Temperaturen[i].name := '3';
ELSIF io_a_Temperaturen[i].romID = '9F051ABCDAA7FF28' THEN
io_a_Temperaturen[i].name := '4';
ELSIF io_a_Temperaturen[i].romID = 'C9051ABCD840FF28' THEN
io_a_Temperaturen[i].name := '5';
ELSIF io_a_Temperaturen[i].romID = '1A051ABCD127FF28' THEN
io_a_Temperaturen[i].name := '6';
ELSIF io_a_Temperaturen[i].romID = 'C1041ABCD151FF28' THEN
io_a_Temperaturen[i].name := '7';
ELSIF io_a_Temperaturen[i].romID = '21041ABCD26CFF28' THEN
io_a_Temperaturen[i].name := '8';
ELSIF io_a_Temperaturen[i].romID = '3C051ABCD464FF28' THEN
io_a_Temperaturen[i].name := '9';
ELSIF io_a_Temperaturen[i].romID = '530517ABCD9FF28' THEN
io_a_Temperaturen[i].name := 'A';
ELSE
io_a_Temperaturen[i].name := 'kein Name vergeben';
END_IF;
END_FOR;
END_IF;

Und dann noch den Baustein für das Auslesen an sich:
Deklarationspart:
FUNCTION_BLOCK fb_onewireXML
VAR_IN_OUT
IP_C : IP_C; (* IP_Control Verwaltungsstruktur *)
S_BUF : NETWORK_BUFFER;
R_BUF : NETWORK_BUFFER;
io_a_Temperaturen : ARRAY[1..30] OF st_oneWireTemperatur;
io_iButton : st_oneWireButton;
END_VAR
VAR_INPUT
ACTIVATE : BOOL;
IP : DWORD;
i_DayTime : DATE_AND_TIME;
END_VAR
VAR_OUTPUT
BUSY : BOOL;
DONE : BOOL;
ERROR_C : DWORD;
ERROR_T : BYTE;
END_VAR
VAR
CTRL : XML_CONTROL;
XML_READER : XML_READER;
URL_DATA : url;
HTTP_GET : HTTP_GET;
last_state : BOOL;
value_int : INT;
value_real : REAL;
v_real : REAL;
state : INT;
s_n_runNo : USINT;
s_b_dataBlockActive : BOOL;
s_b_iButton_detected : BOOL;
END_VAR

Programmpart:
CASE state OF

00: IF ACTIVATE AND NOT last_state THEN
state := 20;
DONE := FALSE;
BUSY := TRUE;
ERROR_C := DWORD#0;
ERROR_T := BYTE#0;
s_n_runNo := 1; (* Zählvariable zurücksetzen *)
s_b_iButton_detected := FALSE;
END_IF;

20: (* URL for DNS UND HTTP-GET *)
(* Example: http://weather.yahooapis.com/forecastrss?p=94089&u=c *)
URL_DATA:=STRING_TO_URL(STR:='http://10.1.1.15/details.xml',
DEFAULT_PROTOCOL:='',
DEFAULT_PATH:=''
);
  URL_DATA.QUERY := '';
state := 60;

60: IF HTTP_GET.DONE THEN
state := 80;
CTRL.START_POS := HTTP_GET.BODY_START;
CTRL.STOP_POS := HTTP_GET.BODY_STOP;
CTRL.COMMAND := WORD#2#10000000_00001100; (* ONLY TEXT AND ATTRIBUTE *)
CTRL.WATCHDOG  := T#1ms;

ELSIF (HTTP_GET.ERROR > DWORD#00) THEN
(* Fehlerbehandlung *)
ERROR_C := HTTP_GET.ERROR;
ERROR_T := BYTE#02;
state := 100;
END_IF;

80: XML_READER(CTRL:=CTRL,BUF:=R_BUF.BUFFER); (* XML Daten seriell lesen *)
IF CTRL.TYP < 98 THEN (* nur auswerten wenn kein Watchdog durchlauf *)
value_int := 0;
value_real := 0.0;
(* automatische konvertierung in real und int ausführen *)
IF LEN(CTRL.VALUE) <= 20 THEN
v_real:=FLOAT_TO_REAL(FLT:=CTRL.VALUE);
IF CHK_REAL(v_real) = 0 THEN (* !!! prüft auf gültigen REAL Wert, ansonsten stürzt Twincat ab !!!!  *)
value_real := v_real;
value_int := REAL_TO_INT(value_real);
END_IF;
END_IF;

(* Daten aus XML holen: *)
IF s_n_runNo >30 THEN
s_n_runNo := 1;
END_IF;

IF CTRL.ELEMENT = 'Name' AND CTRL.VALUE = 'DS18B20' THEN
s_b_dataBlockActive := TRUE;
END_IF;

IF s_b_dataBlockActive AND CTRL.ELEMENT = 'ROMId' THEN
io_a_Temperaturen[s_n_runNo].romID := CTRL.VALUE;
io_a_Temperaturen[s_n_runNo].Zeit := i_DayTime;
END_IF;

IF s_b_dataBlockActive AND CTRL.ELEMENT = 'Temperature' THEN
io_a_Temperaturen[s_n_runNo].temperatur := value_real;
s_b_dataBlockActive := FALSE;
s_n_runNo := s_n_runNo + 1;
END_IF;

IF NOT s_b_dataBlockActive AND CTRL.ELEMENT = 'Family' AND CTRL.VALUE = '01' THEN
s_b_iButton_detected := TRUE;
END_IF;

IF s_b_iButton_detected AND CTRL.ELEMENT = 'ROMId' THEN
io_iButton.active := TRUE;
io_iButton.romID := CTRL.VALUE;
io_iButton.Zeit := i_DayTime;
END_IF


ELSIF CTRL.TYP = 99 THEN (* EXIT - letztes Element gelesen *)
DONE  := TRUE;

IF NOT s_b_iButton_detected AND io_iButton.active THEN
io_iButton.active := FALSE;
io_iButton.romID := '';
END_IF;

state := 100;
END_IF;

100:
(* UNLOCK HTTP DATA *)
IF (NOT HTTP_GET.DONE) THEN
state := 0;
BUSY := FALSE;
DONE := ERROR_T = BYTE#0;
END_IF;

END_CASE;

(* ------------- HTTP_GET --------------- *)
HTTP_GET( IP_C:=IP_C,
S_BUF:=S_BUF,
R_BUF:=R_BUF,
IP4:=IP,
GET:=state=60,
MODE:=BYTE#2, (* HTTP 1.1 mit persistenter Verbindung *)
UNLOCK_BUF:=state=100,
URL_DATA:=URL_DATA
);

(* nicht oder nicht direkt verwendete Parameter *)
(* STRING := HTTP_GET.HTTP_STATUS;
UINT :=HTTP_GET.HEADER_START;
UINT :=HTTP_GET.HEADER_STOP;
UINT :=HTTP_GET.BODY_START;
UINT :=HTTP_GET.BODY_STOP;
BOOL :=HTTP_GET.DONE;
BYTE :=HTTP_GET.ERROR;
*)
(* -------------------------------------*)

last_state := ACTIVATE;

Ist halt noch ein wenig Misch-Masch aus Vorlage und meiner Art zu Programmieren.

In der Vorlage wird ja immer mit den festen Offsets gearbeitet, das habe ich nicht gemacht, da ich nicht weiß, in welcher Reihenfolge die jeweiligen 1wire devices ausgelesen werden, unterschiedliche Familien haben unterschiedliche viele Elemente, daher bin ich jetzt auf den Element-Vergleich gegangen. Ich habe bei dieser Implementierung keine besondere Erhöhung der Zykluszeit festgestellt, dennoch glaube ich, dass ich nicht gerade optimal programmiert habe (durch die Textvergleiche). Gibt es hier eine Möglichkeit, das ganze sauberer bzw. Ressourcen-schonender zu machen? Ich habe keine Probleme mit der Performance oder Zykluszeit, aber dennoch interessiere ich mich für die Verbesserungen.

Wie groß kann ich auf der CX9020 den Empfangsbuffer einstellen? Ich habe den jetzt auf 8192 Byte hochgeschraubt, damit ich das XML komplett eingelesen bekomme, wenn ich noch 11 Sensoren dran hänge, habe ich Angst, dass irgendwann in die Knie geht.

Könnt ihr eventuell ein anderes Interface empfehlen, das etwas sparsamer mit dem Datenhandling umgeht? Was natürlich mal wieder toll war: Eine Woche, nachdem ich den eds erhalten habe kommt die E-Mail, dass der 1-Wire Controller 1 / intelligente Systemschnittstelle von esera wieder verfügbar ist. der kostet ~60 € mehr, kann dafür allerdings 30 1wire Geräte, hat eine 24 V Versorgung und es existiert eine Bibliothekt für Wago.

Kommentare gerne gesehen - vielen Dank!

Viele Grüße!

2
SPS-Hardware / ELTAKO Multisensor MS über TCP Gateway an Beckhoff SPS
« am: 18. Dezember 2017, 22:18:57 »
Hallo zusammen,

da ich noch keinerlei Erfahrungen im Internet gefunden habe, dachte ich, ich lasse euch mal an meinen teilhaben - nicht ganz uneigennützig, denn für den geschriebenen Baustein stellt sich am Ende die Frage: Was kann man daran besser machen?

Aber zum 'Projekt' und der Historie:

Mein Architekt teilte mir beim Berechnen des sommerlichen Wärmeschutzes mit, dass mein Wunsch nach großen Fenstern einen gehörigen Nachteil hat (diese blöde Physik, wer hätte das gedacht...): Die Sonne heizt im Sommer mein Wohnzimmer-Esszimmer-Küche auf und es kann Schweine-warm werden. Aber ich solle mir keine Sorgen machen, man kann von Wa**** eine Steuerung für die Rollos kaufen und die fährt bei hoher Sonneneinstrahlung eben jene runter. Mööööp: Das Teil kostete mehr, als eine CX9020 bei ebay und ich hatte keinen Bock, meine Rollos runter zu fahren. Also habe ich die Rollos gegen Raffstoren 'geupgraded' und mir ne Beckhoff Steuerung gekauft. Anschließend habe ich dann eine gefühlte Ewigkeit nach einer Lösung gesucht, um irgendwie Sonneneinstrahlung, Wind, Regen und Temperatur messen zu können.

Dabei stößt man (auch dank dem Forum hier ;) ) schnell auf Komponenten wie die Elsner Wetterstation P03 oder die Thies clima Wetterstation Compact WSC11. Dazu benötigt man entweder eine SPS, die anstelle der RS232 Schnittstelle eine RS485/422 Buchse verbaut hat oder man hängt eine entsprechende Karte (bspw. KL6041) dran. Um darüber mit den Wetterstationen kommunizieren zu können, kommt noch die TwinCAT PLC Lib Serial Communication hinzu und nach dem Ende des Baus stellt man fest: Dafür ist jetzt kein Geld mehr da.

Ich hatte dann durch Zufall einen Adapter RS232/422/485 auf TCP im Internet gefunden, obwohl ich eigentlich nach einem DALI to Ethernet Gateway gesucht habe - denn die TCP Lizenz habe ich schon gekauft und wollte darüber DALI einbinden. Nach einigem Überlegen habe ich mir diesen bestellt und unter einer anderen Anforderung (Barcodeleser via RS232 über TCP auslesen) getestet. Und das lief so gut, dass ich mich an die RS485/422 Schnittstelle gewagt habe und mal die Wetterstation von Eltako bestellt habe - das ist glaube ich faktisch die Elsner P03 basic.

Jetzt habe ich folgende Komponenten erfolgreich im Einsatz:

- USR-TCP232-306 (~38 € bei Amazon, $36 in China)
- Eltako Multisensor MS (~200€)
- TCP/IP Bibliothek (kostet je nach CPU-Modell)

Wichtig ist, dass der TCP/IP Server auch installiert ist und nicht nur die Lizenz übertragen wird ( ::) - hat etwas gedauert, bis ich das herausgefunden habe) - das habe ich nach einigem Herumprobieren herausgefunden, ich habe dann den zuletzt funktionierenden Code genommen (was dazu geführt hat, dass ich weder den OSCAT-Baustein für die Socketverbindung genommen habe, noch den FB_LocalClient von Beckhoff, denn ich dachte zu diesem Zeitpunkt, dass es an meiner Programmierung liegt und nicht an einem fehlendem Paket von Beckhoff).

Im Programmcode sieht es nun wie folgt aus:

Datenstruktur:
TYPE st_Wetterstation :
STRUCT
f_Temperatur : REAL; (* +/- Temperatur in Grad Celcius *)
n_SonneSued : USINT; (* Sonneneinstrahlung SÜD in klx *)
n_SonneWest : USINT; (* Sonneneinstrahlung WEST in klx *)
n_SonneOst : USINT; (* Sonneneinstrahlung OST in klx *)
b_Daemmerung : BOOL; (* Dämmerung erkannt ja/nein *)
n_Daemmerungswert : UINT; (* Tageslichtwert  in lx *)
f_Windgeschwindigkeit: REAL; (* Windgeshwindigkeit in m/s *)
b_Regen : BOOL; (* Regen erkannt ja/nein *)
END_STRUCT
END_TYPE

Deklarationspart
FUNCTION_BLOCK fb_Wetterstation
VAR_IN_OUT
io_Wetterdaten : st_Wetterstation;
END_VAR
VAR_INPUT
 END_VAR
VAR_OUTPUT
END_VAR
VAR

enable : BOOL;
impulsgenerator : CLK_PRG;

nStep : USINT := 5;

h_Socket : T_HSOCKET;
s_b_connect : BOOL;
s_b_receive : BOOL;

s_s_RecBuffer : STRING[255];
s_n_AnzahlZeichen : UDINT;

s_b_disconnect : BOOL;

s_s_Wetterdatenstring : STRING[21];

(* Instanzen bilden: *)

TCP_Connect : FB_SocketConnect;
TCP_Receive : FB_SocketReceive;
TCP_Close : FB_SocketClose;

(* Fehlerbits und -Nummern *)
s_b_ConnectBusy : BOOL;
s_b_ConnectError : BOOL;
s_n_ConnectError : UDINT;


s_b_ReceiveBusy : BOOL;
s_b_ReceiveError : BOOL;
s_n_ReceiveError : UDINT;


s_b_DisconnectBusy : BOOL;
s_b_DisconnectError : BOOL;
s_n_DisconnectError : UDINT;

t_f_tempREAL : REAL;
END_VAR

Und der Coding-Part:
impulsgenerator( PT := T#1750ms,
Q => enable);

CASE nStep OF
5:
(* Idle *)
IF enable AND h_Socket.handle = 0 THEN
nStep := 10;
ELSIF enable AND h_Socket.handle <> 0 THEN
nStep := 20;
END_IF;
10:
(* Verbindungsstatus prüfen *)
IF h_Socket.handle = 0 OR TCP_Connect.bError THEN
IF s_b_connect THEN
s_b_connect := FALSE;
ELSE
s_b_connect := TRUE;
END_IF;
ELSIF h_Socket.handle > 0 THEN
nStep := 20;
END_IF;

20:
(* Verbunden, Receive toggeln *)
IF NOT s_b_receive THEN
(* Receive-Flanke bilden *)
s_b_receive := TRUE;
ELSE
s_b_receive := FALSE;
END_IF;

s_b_connect := FALSE;

(* Wenn etwas Empfangen wurde, weiterschalten *)
IF s_n_AnzahlZeichen > 60 THEN
(* Daten wurden empfangen, in den Re-Init springen *);
nStep := 30;
ELSIF TCP_Receive.bError THEN
(* In den disconnect springen *)
nStep := 100;
END_IF;
30:
(* es wurden Daten empfangen *)
nStep := 5;
100:
(* Fehler während des Empfangens, socket schließen und neu verbinden *)
s_b_disconnect := TRUE;
nStep := 5;

END_CASE;



TCP_Connect(
sSrvNetId := '',
sRemoteHost := '10.1.1.16' ,
nRemotePort := 4000,
bExecute := s_b_connect,
tTimeout := T#45s,
bBusy => s_b_ConnectBusy,
bError => s_b_ConnectError,
nErrId => s_n_ConnectError,
hSocket => h_Socket);

TCP_Receive(
sSrvNetId := '',
hSocket := h_Socket,
cbLen := 255,
pDest := ADR(s_s_RecBuffer),
bExecute := s_b_receive,
tTimeout := T#5s,
bBusy => s_b_ReceiveBusy,
bError => s_b_ReceiveError,
nErrId => s_n_ReceiveError,
nRecBytes => s_n_AnzahlZeichen);

TCP_Close(
sSrvNetId := '',
hSocket := h_Socket,
bExecute := s_b_disconnect,
tTimeout := T#5s,
bBusy => s_b_DisconnectBusy ,
bError => s_b_DisconnectError,
nErrId => s_n_DisconnectError);

s_b_disconnect := FALSE;

(* Auswertung der empfangenen Daten *)

IF FIND(s_s_RecBuffer, 'W') > 0 AND enable THEN
(* 'W' als Startzeichen für Wetterdaten gefunden *)
s_s_Wetterdatenstring := MID(s_s_RecBuffer, 21, FIND(s_s_RecBuffer, 'W'));

(* Werte zuweisen *)
(* W+22.8000000N11000.0N *)

io_Wetterdaten.f_Temperatur := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,5,2 ));

t_f_tempREAL := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,2,7 ));
IF CHK_REAL( t_f_tempREAL) = 0 THEN
io_Wetterdaten.n_SonneSued := INT_TO_USINT(REAL_TO_INT( t_f_tempREAL  ));
END_IF;

t_f_tempREAL := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,2,9 ));
IF CHK_REAL( t_f_tempREAL) = 0 THEN
io_Wetterdaten.n_SonneWest :=  INT_TO_USINT(REAL_TO_INT( t_f_tempREAL  ));
END_IF;

t_f_tempREAL := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,2,11 ));
IF CHK_REAL( t_f_tempREAL) = 0 THEN
io_Wetterdaten.n_SonneOst :=  INT_TO_USINT(REAL_TO_INT( t_f_tempREAL  ));
END_IF;

IF MID(s_s_Wetterdatenstring,1,13 ) = 'J' THEN
io_Wetterdaten.b_Daemmerung := TRUE;
ELSE
io_Wetterdaten.b_Daemmerung := FALSE;
END_IF;

t_f_tempREAL := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,3,14 ));
IF CHK_REAL( t_f_tempREAL) = 0 THEN
io_Wetterdaten.n_Daemmerungswert :=  REAL_TO_INT( t_f_tempREAL  );
END_IF;

io_Wetterdaten.f_Windgeschwindigkeit := FLOAT_TO_REAL( FLT := MID(s_s_Wetterdatenstring,4,17 ));

IF MID(s_s_Wetterdatenstring,1,21 ) = 'J' THEN
io_Wetterdaten.b_Regen := TRUE;
ELSE
io_Wetterdaten.b_Regen := FALSE;
END_IF;

END_IF;

Was mir nicht gefällt, ist die Zerhackung des Strings, um an die einzelnen Werte zu kommen. Habt ihr hier einen Verbesserungsvorschlag?

Ansonsten kann ich berichten, dass dieser Setup mit dem Code auf meiner CX9020 läuft  ;)

Viele Grüße!

3
Off Topic Diskussionen / "Lebende" Dokumentation - WIKI
« am: 20. Juni 2017, 23:37:27 »
Hallo Forum,

während dem Arbeiten mit der Bibliothek ist mir aufgefallen, dass an manchen Stellen die Dokumentation etwas der Lib hinterherhinkt, Bilder von alten Bausteinen verwendet werden und diverse Rechtschreibfehler auftreten. Mir kam daher die Idee, dass man die Doku ja auch als Wiki ausführen könnte, um das Ganze etwas interaktiver, besser vernetzt und vielleicht auch u.a. mit Beispielen etwas praxisnäher zu gestalten.

Bitte keine Klagen der Leiter hier - ich weiß, dass das wieder einiges an Mehrarbeit bedeuten würde, ich schätze die Arbeit, die bisher hier hinein geflossen ist, eine Wiki frisst Ressourcen und würde an anderer Stelle binden. Vielleicht habt ihr ja schon einmal darüber nachgedacht und es aus gutem Grund verworfen. Das hat wehemente Vor- und extrem viele Nachteile, dennoch könnte man darüber hier einmal diskutieren.

Viele Grüße,

Andreas

Seiten: [1]