s7netplus: Can't migrate to lattest version. Need an explanation.

Hello guys, untill now we had 0.1.8 version implemented in the project. And it worked fairly fine. It is time to make another application and we came accross with some challenges. Early versions allowed to read multiple DB as per example code below.

ReadMethod()
{   
            DB1 db1 = new DB1();
            plc.ReadClass(db1, 1); // reading DB1
          
            value0 = db1.varReal1;
            value1 = db1.varInt1;
            value2 = db1.varReal2;
            value3 = db1.varInt2;
            value4 = db1.varReal3;
            value5 = db1.varInt3;
            value6 = db1.varReal4;
            value7 = db1.varInt4;
            value8 = db1.varReal5;
            value9 = db1.varInt5;
            value10 = db1.varReal6;

            //DB2 db2 = new DB2();
            //plc.ReadClass(db2, 2); // reading DB2

            //db2_value1 = db2.Gear_0;  // Real
            //db2_value2 = db2.Gear_1; // Dint
            //db2_value3 = db2.Gear_2; // Int
            //db2_value4 = db2.Gear_3; // Bool
            //db2_value5 = db2.Gear_4; // Bool
            //db2_value6 = db2.Gear_5; // Bool
            //db2_value7 = db2.Gear_6; // Bool
            //db2_value8 = db2.Gear_7; // Bool
            //db2_value9 = db2.Gear_8; // Bool
            //db2_value10 = db2.Gear_9; // Bool
            //db2_value11 = db2.Gear_10; // Bool
}

Now, it seems not a workable way to read multiple data blocks. Or? Could someone help us to migrate to lattest version? Basically the question is about a way of reading multiple data blocks.

Thanks in advance!

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 39 (22 by maintainers)

Most upvoted comments

Good morning @all

maybe I can provide a little testprogram which uses lib-version 0.8.1 and a timer to read and write a) multiple variables using a item list and to read b) a class within another DB inside the plc. The plc is a S7-1515-2 PN which is in slot 1. It is running since nearly 6:30 o´clock this morning without any exceptions. Parallel to this little program my main logging application is also running and reads far more values from this plc but with lib-version 0.7.xx

The test program is a console application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using S7.Net;
using S7.Net.Types;
using libOptiplan;
using System.Timers;

namespace S7NetPlus_Testproject
{
    public class DB85
    {
        public int DINT { get; set; }
        public Int16 INT1 { get; set; }

        public Int16 INT2 { get; set; }
    }

    class Program
    {
        private static readonly string IPGlatt2 = "YOUR_IP";        
        private static readonly Plc myPLC = new Plc(CpuType.S71500, IPGlatt2, 0, 1);        
        private static byte[] byteArrayNonOptimized = new byte[100];
        private static System.DateTime start = new System.DateTime();
        private static System.DateTime end = new System.DateTime();
        private static TimeSpan durationRead = new TimeSpan();
        private static TimeSpan durationWrite = new TimeSpan();
        private static System.DateTime startOVerall = new System.DateTime();
        private static System.DateTime endOverall = new System.DateTime();
        private static TimeSpan durationOverall = new TimeSpan();

        // S7 ReadMultiple/WriteMultiple
        private static readonly List<DataItem> dataItemsRead = new List<DataItem>();
        private static readonly List<DataItem> dataItemsWrite = new List<DataItem>();

        static System.Timers.Timer ReadTimer;
        
        #region DataItems
        private static readonly DataItem varBit = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Bit,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 0,
            Value = new object()
        };

        private static readonly DataItem varByteArray = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Byte,
            DB = 83,
            BitAdr = 0,
            Count = 100,
            StartByteAdr = 0,
            Value = new object()
        };

        private static readonly DataItem varWord = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Word,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 100,
            Value = new object()
        };

        private static readonly DataItem varInt = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Int,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 102,
            Value = new object()
        };

        private static readonly DataItem varDWord = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.DWord,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 104,
            Value = new object()
        };

        private static readonly DataItem varDInt = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.DInt,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 108,
            Value = new object()
        };

        private static readonly DataItem varReal = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Real,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 112,
            Value = new object()
        };

        private static readonly DataItem varString = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.String,
            DB = 83,
            BitAdr = 0,
            Count = 20,         // max lengt of string
            StartByteAdr = 116,
            Value = new object()
        };

        private static readonly DataItem varDateTime = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.DateTime,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 138,
            Value = new object()
        };   
   
        private static readonly DataItem varWordWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Word,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 146,
            Value = new object()
        };

        private static readonly DataItem varIntWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Int,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 148,
            Value = new object()
        };

        private static readonly DataItem varDWordWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.DWord,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 150,
            Value = new object()
        };

        private static readonly DataItem varDIntWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.DInt,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 154,
            Value = new object()
        };

        private static readonly DataItem varRealWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.Real,
            DB = 83,
            BitAdr = 0,
            Count = 1,
            StartByteAdr = 158,
            Value = new object()
        };

        private static readonly DataItem varStringWrite = new DataItem()
        {
            DataType = DataType.DataBlock,
            VarType = VarType.String,
            DB = 83,
            BitAdr = 0,
            Count = 20,
            StartByteAdr = 164,
            Value = new object()
        };
       
        readonly static DB85 newDB = new DB85();

        #endregion

        

        static void Main()
        {
            SetTimer();

            // add data items to list of data items to read
            dataItemsRead.Add(varBit);
            dataItemsRead.Add(varByteArray);
            dataItemsRead.Add(varWord);
            dataItemsRead.Add(varInt);
            dataItemsRead.Add(varDWord);
            dataItemsRead.Add(varDInt);
            dataItemsRead.Add(varReal);
            dataItemsRead.Add(varString);
            dataItemsRead.Add(varDateTime);
                
            // asign values to the variable to be written                
            varWordWrite.Value = (ushort)67;
            varIntWrite.Value = (ushort)33;
            varDWordWrite.Value = (uint)444;
            varDIntWrite.Value = 6666;
            varRealWrite.Value = (float)77.89;
            varStringWrite.Value = "Writting";

            // add data items to list of data items to write                
            dataItemsWrite.Add(varWordWrite);
            dataItemsWrite.Add(varIntWrite);
            dataItemsWrite.Add(varDWordWrite);
            dataItemsWrite.Add(varDIntWrite);
            dataItemsWrite.Add(varRealWrite);
            dataItemsWrite.Add(varStringWrite);
                            
            // wait for input of the user
            Console.ReadLine();            
        }                

        static void Read()
        {
            // Read
            startOVerall = System.DateTime.Now;

            // open the connection to the plc
            try
            {
                myPLC.Open();
            }
            catch (PlcException e)
            {
                Console.WriteLine(e.ErrorCode.ToString());
            }

            start = System.DateTime.Now;

            myPLC.ReadMultipleVars(dataItemsRead);
            myPLC.ReadClass(newDB, 85, 0);

            end = System.DateTime.Now;
            durationRead = end - start;

            try
            {
                // Write
                // write multiple
                start = System.DateTime.Now;

                myPLC.Write(dataItemsWrite.ToArray());

                end = System.DateTime.Now;
                durationWrite = end - start;

                // close the connection
                myPLC.Close();

                endOverall = System.DateTime.Now;
                durationOverall = endOverall - startOVerall;


                Console.WriteLine("bit: " + dataItemsRead[0].Value);
                Console.Write("byte array: ");

                byteArrayNonOptimized = (byte[])dataItemsRead[1].Value;

                for (int i = 0; i < byteArrayNonOptimized.Length; i++)
                {
                    Console.Write(byteArrayNonOptimized[i]);
                }

                Console.WriteLine();
                Console.WriteLine("word: " + dataItemsRead[2].Value);
                Console.WriteLine("int: " + dataItemsRead[3].Value);
                Console.WriteLine("DWord:" + dataItemsRead[4].Value);
                Console.WriteLine("DInt: " + dataItemsRead[5].Value);
                Console.WriteLine("Real: " + dataItemsRead[6].Value);
                Console.WriteLine("String: " + dataItemsRead[7].Value);
                Console.WriteLine("DateTime: " + dataItemsRead[8].Value);
                Console.WriteLine("DINT DB85: " + newDB.DINT);
                Console.WriteLine("INT1 DB85: " + newDB.INT1);
                Console.WriteLine("INT1 DB85: " + newDB.INT2);
                Console.WriteLine();
                Console.WriteLine("duration ReadMultiple: " + durationRead.TotalMilliseconds + " ms");
                Console.WriteLine("duration Write: " + durationWrite.TotalMilliseconds + " ms");
                Console.WriteLine("duration Overall: " + durationOverall.TotalMilliseconds + " ms");
            }
            catch (PlcException e)
            {
                Console.WriteLine(e.ErrorCode.ToString());
            }                                  
        }

        private static void SetTimer()
        {
            // Create a timer with a two second interval.
            ReadTimer = new System.Timers.Timer(5000);
            // Hook up the Elapsed event for the timer. 
            ReadTimer.Elapsed += OnTimedEvent;
            ReadTimer.AutoReset = true;
            ReadTimer.Enabled = true;
        }

        private static void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
                              e.SignalTime);
            Read();
        }
    }
}

Btw I am using https://github.com/StephenCleary/AsyncEx for async locking of S7NetPlus. Works like a charm, and very easy to use with using/IDisposable.

OK, so there’s 2 things that made me reconsider concurrent requests (which would also handle concurrency in general):

  1. Issues like this one, it’s semi-hard to correctly use the library to begin with even though the fix doesn’t need to be large
  2. Performance

As for the first point, it’s pretty obvious, it would make usage of the library easier if it could handle concurrency internally. The second point is a different story. The past few days I’ve worked on implementing concurrent requests for Sally7. Today I performed my first tests and exclusive vs concurrent requests made only a minor difference, concurrent requests were something like 3% faster. However, that was in a lab environment, i.e. the PLC and PC were probably (I was testing remotely, so I’m not sure) separated by one or two switches only. Also, the PLC only had a single datablock available, with a single variable. I couldn’t change any of that, so I just put the same variable in a read request multiple times (and also tried with the single variable only).

So then I thought, let’s test this at a customer site, with a server room, large network etc. I was reading a single short[10] array and there the concurrent reads were over twice as fast! Probably this also was a faster PLC, because the total time was reduced as well, but still this is a huge improvement.

Long story short, I figured how to do concurrent requests with Sally7, which works pretty well. I still have to cleanup the code and verify some things, but from initial testing it’s looking really good. Now the thing is that the API surface of Sally7 is waaay smaller than S7NetPlus. Also, Sally7 only has an async api (because you know, network communication is inherently async), which is well suited for handling concurrency (because you can await available slots, freeing up threads that would otherwise be block by a sync operation). All in all I’m not sure if there’s any better way than implementing all of this async in S7NetPlus as well, with perhaps a wrapper that exposes the sync methods using .GetAwaiter().GetResult(), but at least that’s still an option.

I’m not sure yet when (or honestly, if, I’m still very time-constrained) I can add this to S7NetPlus, but at least if I want to take a stab at it the plan’s already laid out.

What type of timer exactly? There’s System.Timers.Timer, System.Threading.Timer, System.Windows.Forms.Timer and two others, they all start with System and end with Timer (but have very distinct behavior). Also, how many timer instances/events/callbacks are you using?

private readonly System.Timers.Timer _timer;
 _timer = new System.Timers.Timer(); 
 _timer.Interval = 250;          
_timer.Elapsed += OnTimerElapsed

if plc.IsConnected
_timer.Start();

Do you have only this single timer instance? Or do you create this per DB you’re reading or something similar?

Do you use a synchronization primitive when accessing the PLC instance?

I can’t answer because I don’t know what you mean by synchronization primitive .

I think this could provide some insight, but I’ll try to explain it really short. Let’s assume you’re reading data from the PLC and reading takes 1 second (I know it’s a lot faster, but the scale is not important for the example). Now let’s say you have two threads that need to read data. If you start reading DB1 at T0 the connection can only be used to wait for the response for DB1, which takes 1 second. If anywhere within that second the read for DB2 starts, you’re creating concurrent access to the PLC class. When that happens there will be two threads contending to receive responses for their requests, which the library doesn’t handle in it’s current form. A synchronization primitive can be used to keep the read for DB2 waiting, until it has been given the clear when the DB1 read completes. There are many approaches to solving this and it’s hard to decide for someone else what would be a good fit in their project, especially when you can’t see the project.

On the other hand, if you only use a single timer (System.Timers.Timer, because this doesn’t apply to all other timers) to read/write data you should normally not have to deal with concurrency, because System.Timers.Timer synchronizes it’s execution.

Is there any relation between this interval and the timer intervals?

That’s hard to say.

Yes, with 250ms interval that’s indeed hard to say… You could perhaps apply logging to get more exact figures.

I guess this is just a coincidence, which logically provides more reason to assume the error is in the library usage.

Nobody disputes, so I wrote at header “Can’t migrate… please help.” I believe that since 0.1.8 the lib has been changed somehow, so methods and conception I used while building of app don’t fit anymore. So I should do something with this.

There are changes, but my best guess is that the failures are handled differently, so maybe there always were issues that went unnoticed because they had different impact. I can’t say, but I’m very interested in how this turns out.

Again, you may not believe in that, but app based on 0.8.1 is running without any freezes and breaks when app based on 0.1.8 is also been launched previously. Already tried a few times.

I have no reason to doubt you. It’s strange that it works every time, that’s definitely more than a coincidence. However assuming it’s an issue with concurrency then switching the libraries might affect first execution of the application with regards to caching and optimization, I don’t think we can get any better than this educated guess… (Or perhaps we can when we figure out what’s going on).

Also, is there any chance you could provide the code for your application? That might make it a lot easier to identify the issue…

We are not allowed providing entire projects .

I’d be willing to provide help (free of charge) in a way that suits your employer, for instance using remote assistance software like TeamViewer or by means of an NDA to prevent me from ‘stealing’ your code.

Although I will try to replicate same issue on the sample, then upload.

I’m also interested in what happens when you perform the same actions in a simple command line application if you just do reads and a Thread.Sleep(250) to mimic the behavior of your UI. Let me know if you can replicate this in a sample that you can share though.

Hope all my efforts gonna turn out with real help. )

I guess all people need to do here is to ask for help and provide information when requested to be helped. We are missing a proper sample so that people can take a look at that, it’s something I want to add when I have time…

@mycroes,

What is your target platform?

it is 4.5.2 (btw, 0.8.1 is also targetting 4.5.2 by default)

What framework are you targeting?

WPF App (.Net Framework)

Are you using a timer to read data?

Yes I am. It’s System Timer.

What thread is accessing the PLC instance?

I created a class library and connected it to solution. This class library has a class where I make a connection to plc.

Do you have multiple threads accessing the PLC instance?

This is quite big application, so my answer -yes. Yet while troubleshooting others are disabled.

How do you update your UI?

An event subscribed to a timer, then it is being broadcasted to a method which is located in VM (used mvvm pattern). At this method values of fields are being assigned to VM properties. Further go bindings and so on.

Do you have exception handling in place around all PLC interactions?

0.1.8 has errorcode, I used that. 0.8.1 doesn’t have that. So right now is nothing.

Does it always freeze after a few seconds (or what is the maximum time you’ve seen it running)?

seems the time between first “living number” read/change and the last “living number” read/change is about 3 sec. it may say it has constant interval before break.

Also, not a technical issue, but could you describe how experienced you are as a C# programmer?

I am plc programmer, have some knowledge about XAML, C#, MVVM pattern. Consider me as a beginner.