S7NetPlus / s7netplus

S7.NET+ -- A .NET library to connect to Siemens Step7 devices
MIT License
1.32k stars 586 forks source link

PLC POLLING STOP TO UPDATE MY CLASS PROPERTIES.. WHY??? #450

Closed LucaPannozzo closed 1 year ago

LucaPannozzo commented 1 year ago

Hello everyone,

I developed a Consolle App in c#. The app polling 7 siemens s7 1200. during the polling i update my classes properties and waiting some bits switch true for execute my job and the write a feedback bit to the plcs. I build a polling class like these:

`

public abstract class ServizioPlc
{

    public EventHandler OnValuesRefreshed;

    public EventHandler ReadValues;

    public EventHandler WriteValues;

    public string Port { get; private set; }
    public string IpAddress { get; private set; }

    internal System.Timers.Timer PollingTimer { get; set; }

    internal DateTime LastScan { get; set; }

    public TimeSpan ScanTime { get; set; }

    public bool ReadError { get; private set; }
    public bool WriteError { get; set; }
    public string LastError { get; internal set; }
    public TimeSpan WriteTime { get; private set; }
    public TimeSpan ReadTime { get; private set; }
    public TimeSpan UpdateTime { get; private set; }
    public EventHandler OnWriteError { get; set; }
    public EventHandler OnReadError { get; set; }

    public ServizioPlc(string ipaddress, string port)
    {
        IpAddress = ipaddress;
        Port = port;

        PollingTimer = new System.Timers.Timer();
        PollingTimer.Interval = 1000;
        PollingTimer.AutoReset = true;
        PollingTimer.Elapsed += PollingTimer_Elapsed;
    }

    public void EseguiCicloPolling()
    {
        PollingTimer_Elapsed(new object(), null);
    }

    internal void PollingTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        //PollingTimer.Stop();

        try
        {
            Execute();
        }
        catch(Exception ex)
        {
            Console.WriteLine("PLC SERVICE "+IpAddress+"- Eccezione non gestita durante esecuzione Pollig. " +ex.Message + ex.InnerException?.Message + ex.StackTrace + ex.InnerException?.StackTrace);

            if (OnReadError != null)
                OnReadError.Invoke(ex, new EventArgs());
        }

        ScanTime = DateTime.Now - LastScan;
        LastScan = DateTime.Now;

        //PollingTimer.Start();
    }

    private void Execute()
    {
        if (ReadError || WriteError)
            Connect();

        Stopwatch sw = new Stopwatch();
        sw.Start();

        try
        {
            if (WriteValues != null)
                WriteValues.Invoke(new object(), new EventArgs());

            WriteError = false;
        }
        catch (Exception ex)
        {
            WriteError = true;
            LastError = ex.Message + "-" + ex.InnerException?.Message;

            if (OnWriteError != null)
                OnWriteError.Invoke(ex, new EventArgs());
        }
        sw.Stop();

        WriteTime = sw.Elapsed;

        sw.Reset();
        sw.Start();

        try
        {
            if (ReadValues != null)
                ReadValues.Invoke(new object(), new EventArgs());

            ReadError = false;
        }
        catch (Exception ex)
        {
            ReadError = true;

            LastError = ex.Message + "-" + ex.InnerException?.Message;

            if (OnReadError != null)
                OnReadError.Invoke(ex, new EventArgs());
            return;
        }

        sw.Stop();
        ReadTime = sw.Elapsed;

        sw.Reset();
        sw.Start();

        if (OnValuesRefreshed != null)
            OnValuesRefreshed.Invoke(new object(), new EventArgs());

        sw.Stop();
        UpdateTime = sw.Elapsed;
    }

    public abstract void StartPolling();

    public abstract void Connect();

    public abstract void StopPolling();

    public void Close()
    {
       PollingTimer.Stop();
       PollingTimer.Dispose();
    }
}

`

I use this class for multiple drivers like Omron and Siemes. Siemens Service is like :

`using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers;

namespace Tracker_GUI.Controller.PLC.SIEMENS { public class ServizioPlcSiemens : ServizioPlc {

    internal BinsTracker.S7Client Client { get; private set; }

    public ServizioPlcSiemens(string ipaddress, string port):base(ipaddress,port)
    {

        Client = new BinsTracker.S7Client(S7.Net.CpuType.S71200,ipaddress,0,1);

    }

    public void ConnectClient()
    {
        if(!Client.Plc.IsConnected)
            Client.Plc.Open();
    }

    public void CloseClient()
    {
        Client.Plc.Close();

    }

    public override void StartPolling()
    {
        Client.Plc.Open();
        PollingTimer.Start();
    }

    public override void StopPolling()
    {
        PollingTimer.Stop();
        Client.Plc.Close();
    }

    public override void Connect()
    {
        Client.Plc.Close();

        Client = new BinsTracker.S7Client(S7.Net.CpuType.S71200, IpAddress, 0, 1);
    }
}

} `

My client contains the Driver s7.net:

`public class S7Client { public Plc Plc { get; set; } public string IndirizzoIp { get; } public CpuType CpuType { get; } public short Rack { get; } public short Slot { get; }

    public S7Client( CpuType cputype, string ip, short rack, short slot)
    {
        this.IndirizzoIp = ip;
        this.CpuType = cputype;
        this.Rack = rack;
        this.Slot = slot;

        Plc = new Plc(cputype,ip,rack,slot);

    }
}`

Now, the main class works on these way:

`using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Tracker_GUI.Controller.PLC.SIEMENS;

namespace Tracker_GUI.Controller.PLC { public class ServizioPlcPac200 : ServizioPlcSiemens {

    public uint StratiRealizzati { get; private set; }
    public uint StratiDaRealizzare { get; private set; }
    public uint PedaneRealizzate { get; private set; }
    public uint PedaneDaRealizzare { get; private set; }

    public uint NumeroCassePerStrato { get; private set; }
    public uint NumeroCasseAttuali { get; private set; }
    public uint NumeroCasseTotaliPerPallet { get; private set; }
    public uint NumeroCassePerPallet { get; private set; }

    public bool PalletTerminato { get; private set; }

    public ServizioPlcPac200(string ipaddress, string port) : base(ipaddress, port)
    {
        SetInterval(2000);
        ReadValues += LeggiStatoPallettizzatore;

        OnReadError += AllarmeLetturaDati;
        OnWriteError += AllarmeScritturaDati;
    }

    private void AllarmeScritturaDati(object sender, EventArgs e)
    {
        var ex = (Exception)sender;

        //TrackerApp.LogRegister.Warning("Errore PLC Navetta durante fase di lettura: " + ex.Message);
        Console.WriteLine("ERRORE SCRITTURA DATI NAVETTA COSMEC: " + ex.Message + ex.StackTrace);
    }

    private void AllarmeLetturaDati(object sender, EventArgs e)
    {
        var ex = (Exception)sender;

        //TrackerApp.LogRegister.Warning("Errore PLC Navetta durante fase di lettura: " + ex.Message);
        Console.WriteLine("ERRORE LETTURA DATI NAVETTA COSMEC: " + ex.Message + ex.StackTrace);
    }

    private void LeggiStatoPallettizzatore(object sender, EventArgs e)
    {
        if (!Client.Plc.IsConnected)
            Client.Plc.Open();

        this.PedaneRealizzate = (uint)Client.Plc.Read("DB5030.DBD18");
        this.PedaneDaRealizzare = (uint)Client.Plc.Read("DB5030.DBD14");

        this.StratiRealizzati = (uint) Client.Plc.Read("DB5030.DBD10");
        this.StratiDaRealizzare = (uint)Client.Plc.Read("DB5030.DBD6");

        this.NumeroCasseAttuali = (uint)Client.Plc.Read("DB5030.DBD30");
        this.NumeroCasseTotaliPerPallet = (uint)Client.Plc.Read("DB5030.DBD26");
        this.PalletTerminato = (bool)Client.Plc.Read("DB5030.DBX1.0");
    }

    internal void FeedbackPalletTerminato(object sender, EventArgs e)
    {
        if (!Client.Plc.IsConnected)
            Client.Plc.Open();

        Client.Plc.WriteBit(S7.Net.DataType.DataBlock, 5010, 1, 0, true);

    }

    internal void ResettaFeedBackPalletTerminato(object sender, EventArgs e)
    {
        if (!Client.Plc.IsConnected)
            Client.Plc.Open();

        Client.Plc.WriteBit(S7.Net.DataType.DataBlock, 5010, 1, 0, false);
    }
}

} `

NOW, the main problems is, after a long periodo maybe one hour, my classes no update the properties with plc values and i need to restart the application. After do that, system start to works properly. i suppose the problem is with connection /socket the application/GC dispose something or i don't know. I'm not a .NET expert so probably I'm not write correctly the code but with Omron Drivers works well, only with s7.net I have these issues! Please, someone understand what is happening?

Best regards everyone Thank you

Jason-Jelks commented 1 year ago

There are some factors that could have an effect on this. Depending on the interval/how often you are opening and closing the connection to the PLC, you could be consuming multiple connections internally into the PLC. From your code, it is possible you are opening "new" instances, but have not necessarily closed and disposed of previous ones.

The Siemens S7-1200 has a maximum number of external connections available, and once those are reached, it cannot accept any more. When you terminate your program, the TCP Sockets itself will terminate and release in the Siemens. Again, this is merely a possibility. If you have access to the Siemens PLC via TIA Portal, you can monitor the number of connections taken and available to see if this is actually occurring.

As an aside, you could make your PLC object a factory and hold the instance connection open and then close it only when you dispose of the factory instance. This would be a cleaner approach.

mycroes commented 1 year ago

I used to believe you can actually only have a single connection open for the specified local and remote TSAP, but I recently had a situation where multiple connections using the default TSAP actually seemed to work. Nevertheless @Jason-Jelks is absolutely right, you should manage your connection in whatever way to ensure they're not dangling.

LucaPannozzoIcoel commented 1 year ago

Hi @Jason-Jelks and thank you for your reply. I polling the with an interval of 1sec. I see the Plc Class have Dispose methods but not accessible for other classes. What do you think to create an extended class like these:

` internal class MyS7Plc : S7.Net.Plc { public MyS7Plc(CpuType cpu, string ip, short rack, short slot) : base(cpu, ip, rack, slot) { }

    public void Dispose()
    {
        Dispose(true);
    }

}`

So before create a new istance of Client i can call the Dispose method to the previous one.