plclog ist eine Lösung, wie Sie einfach Nachrichten einer SPS in einer zentralen Datenbank auf einem Server oder PC in Ihrem Netzwerk speichern können. Zugriff auf die abgelegten Daten haben Sie von jedem PC aus mittels Webbrowser.
In diesem Artikel möchten wir die Lösung kurz vorstellen. Ausführliche Informationen und den Download aller notwendigen Komponenten finden Sie unter plclog.io
Vorteile
Unsere Lösung bietet gegenüber OPC Lösungen (WinCC) für das Datenlogging die folgenden Vorteile:
- Kostenfreie Lösung
- Das Logging muss nur auf der SPS vorgenommen werden. Es ist keine weitere Software zu parametrieren / programmieren.
- Kein Netzwerktraffic, wenn nicht geloggt wird. Unsere Serverkomponente wartet passiv auf das Eingehen von Nachrichten, die nur bei Aufruf der Log-Schnittstelle generiert werden. Alle anderen bekannten Lösungen greifen permanent auf die SPS zu und überprüfen die definierten Variabeln auf Änderungen.
- Das Programm kann auf jedem beliebigen Rechner schnell installiert werden und ist sehr klein. Daher eignet es sich auch für ein RaspberryPi, einem Miniaturrechner für ca. 30.- EUR, der temporär bei Kunden im Einsatz stehen kann, bei dem es um Fehlersuche geht.
Wir sind sicher, dass unsere Lösung noch viele weitere Vorteile hat. Finden Sie sie.
plclog Komponenten
Die komplette Software besteht aus 3 Komponenten: Einer Loggingkomponenten, die zusammen mit dem Webserver in einer Datei heruntergeladen werden kann und auf dem LoggingPC/Server läuft. Und der SPS Komponente, die die Standardbausteine von Siemens zum Initialisieren von Verbindungen und versenden von UDP Nachrichten, als auch die eigentliche Loggingkomponente, die aus einem FB, einem FC und 2 Datenbausteinen enthält. Sie können das Projekt, das sämtliche Komponenten als auch die SCL Source enthält unter plclog.io herunterladen.
Alternativ finden sie die reine SCL Source der SPS Komponente am Ende des Artikels.
Erweiterungen
Wir planen die Erweiterung der Loggingkomponente, wenn sie genügend Anklang findet. Wir würden uns sehr über eine Nachricht unter unserer Google+ Seite freuen.
SCL Sourcefile:
function_block log_master
title = 'logging utility master'
family : 'logutil'
author : 'wisol'
// This function must be included for cyclic execution ex. OB1
// The following siemens built-in functions must be avaliable in the version
// belonging to the hardware:
var
dest_ip_settings: struct
ip: struct
field_1 : int := 192;
field_2 : int := 168;
field_3 : int := 1;
field_4 : int := 10;
end_struct;
// Local Device ID: Depending of the used hardware to communicate:
// Select: #0 for Communication processors (CP)
// #2 for CPU315-2PN/DP and CPU317-2PN/DP
// #3 for CPU319-3PN/DP
// If using WinAC RTX, set the number of the IE Connection (#1 - #4)
localdeviceid : byte := B#16#2;
end_struct;
tcon_settings: struct
block_length : word := W#16#40; // lenght of this DB. Must be 64 bytes.
id : word := W#16#FFE; // Reference to this connection (default is 0xFFE)
connection_type : byte := B#16#13; // Connection type: 13 for UDP communication
active_est : bool := FALSE; // Enable active connection establishment. Not possible for UDP
// Local Device ID: Depending of the used hardware to communicate:
// Select: #0 for Communication processors (CP)
// #2 for CPU315-2PN/DP and CPU317-2PN/DP
// #3 for CPU319-3PN/DP
// If using WinAC RTX, set the number of the IE Connection (#1 - #4)
local_device_id : byte := B#16#2; // SET YOUR PLC OR CP TYPE ACCORDING TO THE COMMENTS ABOVE
local_tsap_id_len : byte := B#16#2; // length of the used part of the parameter 'local_tsap_id'
rem_subnet_id_len : byte := B#16#0; // Not used for UDP connections - must be 0
rem_staddr_len : byte := B#16#0; // Not used for UDP connections - must be 0
rem_tsap_id_len : byte := B#16#0; // Not used for UDP connections - must be 0
next_staddr_len : byte := B#16#0; // Not used for UDP connections - must be 0
// Port number to be used. Default is port 3000.
// Usage: local_tsap_id[1] is the high byte of the port number
// local_tsap_id[2] is the low byte of the port number
// if local_tsap_id_len is set to #2.
// Please note the following siemens restriction: if your CPU has the version
// 2.6 or lower, only ports 2000 - 5000 are allowed.
local_tsap_id : array [1..16] of byte := B#16#B, B#16#E7, B#16#0;
// Not used. Must be #0
rem_subnet_id : array [1..6] of byte := B#16#0;
rem_staddr : array [1..6] of byte := B#16#0;
rem_tsap_id : array [1..16] of byte := B#16#0;
next_staddr : array [1..6] of byte := B#16#0;
spare : word := W#16#0;
end_struct;
tsend_settings: struct
rem_ip_addr : array [1..4] of byte := B#16#0;
rem_port_nr : array [1..2] of byte := B#16#0; //Remote port number (2 byte big endianess)
reserved : array [1..2] of byte := B#16#0; //Unused; must be B#16#00
end_struct;
tdiscon_log : TDISCON; // Internal use only
tcon_base : TCON; // Internal use only
tusend_log : TUSEND;
end_var
var_input
startup : bool := false;
end_var
var_temp
i : int;
snd_src : int;
ret : bool;
end_var
begin
//*******************************************************************
// Startup in OB 100
//*******************************************************************
if startup then
log_buffer.init_done := false;
log_buffer.disc_done := false;
log_buffer.init_start := false;
end_if;
//*********************************************************************
// Reinit the connection if necessary
//*********************************************************************
// Check for updated IP settings
if (dest_ip_settings.ip.field_1 <> log_buffer.old_ip.field_1) or
(dest_ip_settings.ip.field_2 <> log_buffer.old_ip.field_2) or
(dest_ip_settings.ip.field_3 <> log_buffer.old_ip.field_3) or
(dest_ip_settings.ip.field_4 <> log_buffer.old_ip.field_4) or
(dest_ip_settings.localdeviceid <> log_buffer.old_ip.localdeviceid) then
log_buffer.init_done := false;
log_buffer.disc_done := false;
tsend_settings.rem_ip_addr[1] := int_to_byte(dest_ip_settings.ip.field_1);
tsend_settings.rem_ip_addr[2] := int_to_byte(dest_ip_settings.ip.field_2);
tsend_settings.rem_ip_addr[3] := int_to_byte(dest_ip_settings.ip.field_3);
tsend_settings.rem_ip_addr[4] := int_to_byte(dest_ip_settings.ip.field_4);
tcon_settings.local_device_id := dest_ip_settings.localdeviceid;
log_buffer.old_ip.field_1 := dest_ip_settings.ip.field_1;
log_buffer.old_ip.field_2 := dest_ip_settings.ip.field_2;
log_buffer.old_ip.field_3 := dest_ip_settings.ip.field_3;
log_buffer.old_ip.field_4 := dest_ip_settings.ip.field_4;
log_buffer.old_ip.localdeviceid := dest_ip_settings.localdeviceid;
end_if;
tdiscon_log(REQ := (log_buffer.init_start
and not log_buffer.disc_done
and not log_buffer.init_done), // terminate at rising edge
ID := W#16#FFE); // Connection identifier
if tdiscon_log.DONE or tdiscon_log.ERROR then
log_buffer.disc_done := true;
end_if;
tcon_base(REQ := (log_buffer.init_start
and not log_buffer.init_done
and log_buffer.disc_done), // Activate Conn at rising edge
ID := W#16#FFE, // Connection Identification
connect:= tcon_settings); // any pointer
if tcon_base.DONE then
log_buffer.init_done := true;
for i := 1 to 100 do
if (log_buffer.buf[i].process = 2) then
log_buffer.buf[i].process := 1;
end_if;
end_for;
log_buffer.buf[101].process := 0;
end_if;
log_buffer.init_start := true;
//*********************************************************************
// Sending part
//*********************************************************************
// Search for sending records:
if log_buffer.send_count > 100 then
log_buffer.send_count := 1;
end_if;
if (log_buffer.buf[log_buffer.send_count].process = 1) and
(log_buffer.buf[101].process = 0) then
log_buffer.send_start := true;
log_buffer.buf[log_buffer.send_count].process := 2;
snd_src := log_buffer.send_count;
elsif (log_buffer.buf[101].process = 1) and not
(log_buffer.buf[log_buffer.send_count].process = 2) then
log_buffer.send_start := true;
log_buffer.buf[101].process := 2; // send buf_overflow msg
snd_src := 101;
elsif (log_buffer.buf[101].process = 2) then
snd_src := 101;
else
snd_src := log_buffer.send_count;
end_if;
tusend_log(req := log_buffer.send_start, // send at rising edge
id := W#16#FFE, // Connection identification
len := 66, // length of data
data := log_buffer.buf[snd_src].data, // source data
addr := tsend_settings); // reciever address
log_buffer.send_start := false;
if tusend_log.DONE then
log_buffer.buf[snd_src].process := 0;
if (snd_src <> 101) then
log_buffer.send_count := log_buffer.send_count + 1;
end_if;
end_if;
if (log_buffer.send_count - log_buffer.write_count) >= 0 then
if (log_buffer.send_count - log_buffer.write_count) >= 10 then
log_buffer.buf[101].process := 0;
log_buffer.buf_overflow := false;
elsif log_buffer.buf[log_buffer.send_count].process = 0 then
log_buffer.buf[101].process := 0;
log_buffer.buf_overflow := false;
end_if;
else
if (log_buffer.send_count + 100 - log_buffer.write_count) >= 10 then
log_buffer.buf[101].process := 0;
log_buffer.buf_overflow := false;
elsif log_buffer.buf[log_buffer.send_count].process = 0 then
log_buffer.buf[101].process := 0;
log_buffer.buf_overflow := false;
end_if;
end_if;
end_function_block
//*******************************************************************
// Logging interface
//*******************************************************************
function log_interface : void
title = 'logging interface'
family : 'logutil'
author : 'wisol'
// This function writes the sending infomation given as parameter to
// the sending buffer.
var_input
machine : int;
part : int;
level : int;
msg : string [50];
par1 : real;
par2 : real;
end_var
var_temp
index : int;
end_var
begin
if log_buffer.buf_overflow then
return;
end_if;
if log_buffer.write_count > 100 then
log_buffer.write_count := 1;
end_if;
if log_buffer.buf[log_buffer.write_count].process = 0 then
log_buffer.buf[log_buffer.write_count].data.message := ' ';
log_buffer.buf[log_buffer.write_count].data.machine := machine;
log_buffer.buf[log_buffer.write_count].data.part := part;
log_buffer.buf[log_buffer.write_count].data.log_level := level;
log_buffer.buf[log_buffer.write_count].data.message := msg;
log_buffer.buf[log_buffer.write_count].data.parameter1 := par1;
log_buffer.buf[log_buffer.write_count].data.parameter2 := par2;
log_buffer.buf[log_buffer.write_count].process := 1;
log_buffer.write_count := log_buffer.write_count + 1;
else
log_buffer.buf_overflow := true;
log_buffer.buf[101].process := 1;
end_if;
end_function
//******************************************************************
//************** Datablock definition for Send-Buffer **************
//******************************************************************
data_block log_buffer
title = 'logging utility data buffer'
family : 'logutil'
author : 'wisol'
struct
// General Settings:
write_count : int := 1;
send_count : int := 1;
buf_overflow : bool := false;
send_start : bool := false;
init_done : bool := false;
init_start : bool := false;
disc_done : bool := false;
old_ip: struct
field_1 : int := 0;
field_2 : int := 0;
field_3 : int := 0;
field_4 : int := 0;
localdeviceid : byte := B#16#0;
end_struct;
// Buffer Data:
buf : array [1..101] of
struct
process : int;
data: struct
machine : int;
part : int;
log_level : int;
parameter1 : real;
parameter2 : real;
message : string [50];
end_struct;
end_struct;
end_struct
begin
// Message Definition for buffer overlow
buf[101].data.machine := 0;
buf[101].data.part := 0;
buf[101].data.log_level := 99;
buf[101].data.message := 'PLCLog send buffer overflow. Possible loss of data';
buf[101].data.parameter1 := 0.0;
buf[101].data.parameter2 := 0.0;
end_data_block
//********************************************************************
// Define the InstanceDB for LogMaster *
//********************************************************************
data_block log_masterInstance log_master
begin
end_data_block