plclog Einführung

26Jan 2015

plclog Einführung

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

Tags: Logging, SPS