S7NetPlus / s7netplus

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

Databinding Mismatch while reading Class Struct #352

Open shrepat opened 3 years ago

shrepat commented 3 years ago

Hello,

I am trying to access my DB from TIA to my WPF GUI c# Project. While binding the class I happen to notice mismatch of values while binding the class and accessing the structure. The TIA DB looks like this with their current values. Sample_DB

When binding it on my WPF app. The binding mismatch happens. I have tried multiple iterations but I noticed the change in value when I try to reshuffle the DB value location and come back to notice the values have been mismatched in a random pattern. Here is the output from the GUI. GUI_OP

Please let me know if I am missing something or if there is a solution for the same.

Thank you, Shreyas

scamille commented 3 years ago

Can you please share the code of how you read the data? Both the class and the function reading into it.

And do you have turned of data block optimization? See https://github.com/S7NetPlus/s7netplus/wiki/S7-1200-1500-Notes#s7-12001500-notes

shrepat commented 3 years ago

@scamille Hi,

Thanks for responding. Sure. The code is Apologies. Hopefully it is not too long to read. Do let me know if you would want to fix the code. TIA

 public class HMIDb1Read : ObservableModel
    {
        private double _Sample_1;        
        private double _Sample_2;
        private double _Sample_3;
        private double _Sample_4;
        private double _Sample_5;

        public double Sample_1
        {
            get
            {
                return _Sample_1;
            }
            set
            {
                if (_Sample_1!= value)
                {
                    _Sample_1= value;
                    OnPropertyChanged("Sample_1");
                }
            }
        }
        public double Sample_2
        {
            get
            {
                return _Sample_2;
            }
            set
            {
                if (_Sample_2!= value)
                {
                    _Sample_2= value;
                    OnPropertyChanged("Sample_2");
                }
            }
        }
        public double Sample_3
        {
            get
            {
                return _Sample_3;
            }
            set
            {
                if (_Sample_3!= value)
                {
                    _Sample_3= value;
                    OnPropertyChanged("Sample_3");
                }
            }
        }
        public double Sample_4
        {
            get
            {
                return _Sample_4;
            }
            set
            {
                if (_Sample_4!= value)
                {
                    _Sample_4= value;
                    OnPropertyChanged("Sample_4");
                }
            }
        }
        public double Sample_5
        {
            get
            {
                return _Sample_5;
            }
            set
            {
                if (_Sample_5!= value)
                {
                    _Sample_5= value;
                    OnPropertyChanged("Sample_5");
                }
            }
        }

The above code is my HMIDB Class. I read this on my ViewModel as follows:

public HMIDb1Read db1Read { get; set; }

        public HMIDb1Write db1Write { get; set; }

        public DataViewModel()
        {
            db1Read = new HMIDb1Read();
            db1Write = new HMIDb1Write();
        }

In my WPF xaml- I bind it using dataContext as

<TextBlock 
                    Grid.Row="25"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_1
                </TextBlock>
                <TextBlock 
                    Grid.Row="26"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_2
                </TextBlock>
                <TextBlock 
                    Grid.Row="27"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_3
                </TextBlock>
                <TextBlock 
                    Grid.Row="28"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_4
                </TextBlock>
                <TextBlock 
                    Grid.Row="29"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_5
                </TextBlock>
                 <TextBlock 
                    Grid.Row="0"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_1}"
                    VerticalAlignment="Center">
                </TextBlock>
                <TextBlock 
                    Grid.Row="1"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_2}"
                    VerticalAlignment="Center">
                </TextBlock>
                <TextBlock 
                    Grid.Row="2"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_3}"
                    VerticalAlignment="Center">
                </TextBlock>
                <TextBlock 
                    Grid.Row="3"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_4}"
                    VerticalAlignment="Center">
                </TextBlock>
                <TextBlock 
                    Grid.Row="4"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_5}"
                    VerticalAlignment="Center">
                </TextBlock>

The PLC Class code is: plcDriver.ReadClass(DataViewModel.Instance.db1Read, 61, 0);

shrepat commented 3 years ago

Also,

yes optimised data access is unchecked.

FalcoGoodbody commented 3 years ago

@shrepat ,

I am not sure if this can be the reason but as I can see you want to read multiple S7-Real values from the plc. According to the wiki the datatype in C# within the class needs to be float when using the "read-class"-method.

I use the "read-class"-method a lot but this behaviour you showed never ever occured....interresting

Maybe you can give it a try?

Alternatively you can try to use "read-multiple-items".

shrepat commented 3 years ago

Yes,

I tried float instead of double earlier. The problem remained even then. I am not sure how to use read multiple items. I will look into it and see if that fixes the issue.

Thanks.

scamille commented 3 years ago

I am pretty sure you need float / System.Single to match 32bit S7 Real data type. Please try again with this, and if it is still not working we can continue looking at other possibilities.

scamille commented 3 years ago

I am not sure if it is caused by only showing a snippet of your code, but I can't really match the grid.Rows between your labels and content. E.g

<TextBlock 
                    Grid.Row="25"
                    Grid.Column="0"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center">Sample_1
                </TextBlock>
                 <TextBlock 
                    Grid.Row="0"
                    Grid.Column="1"
                    Foreground="Black"
                    FontSize="24"
                    HorizontalAlignment="Center"
                    Text="{Binding db1Read.Sample_1}"
                    VerticalAlignment="Center">
                </TextBlock>

With Grid.Row=25 and Grid.Row=0

shrepat commented 3 years ago

Yeah sorry, I had to swap different grid values just for code. It is only a snippet of the entire code. Mine had around 30 samples I had to take only 5 due to space concerns.

shrepat commented 3 years ago

I just tried replacing the double with float type for the real values. Still the same error.

scamille commented 3 years ago

Hmm it might be related to #300 . In effect, the ordering of properties we get through reflection is not guaranteed to be the same order in which you declared those properties in your class. But that hasn't been a problem in practice so far.

Can you try creating a more simple struct. struct HMIDb1ReadData { public float Sample1; public float Sample2; ... } and read into that using Plc.ReadStruct ?

You can wrap that struct inside your HMIDb1Read class and use it as a backing field for your properties. If you update all variables in 1 go you can also just call NotifyPropertyChanged for all of them to get WPF to update it, no need to build those wrapper setters for every individual one.

FalcoGoodbody commented 3 years ago

I just would like to share my code how I am reading class from plc:

The class:

public class Heizung
    {
        public Heizung();

        public float Aussentemperatur { get; set; }
        public float TempPufferOben { get; set; }
        public float TempPufferMitte { get; set; }
        public float TempPufferUnten { get; set; }
        public float VLTempLufterhitzer { get; set; }
        public float VLTempWT23 { get; set; }
        public float BetriebsstundenGlattAufheizbetrieb { get; set; }
        public float BetriebsstundenGlattNachheizbetrieb { get; set; }
        public float BetiebsstundenFrostschutz { get; set; }
        public bool Sammelstörung { get; set; }
        public bool HeizungAUS { get; set; }
        public bool HeizungEIN { get; set; }
    }

the declaration:

        readonly Heizung DatenHeizung = new Heizung();

read class from plc:

 if (plcHeizungRNV1.IsConnected)
  {
      plcHeizungRNV1.ReadClass(DatenHeizung, PLC_DB_HEIZUNG, 0);
  }  

I then write the values of the class into a database and show them on a ASP.Net website:

DatenHeizungRNV1.Aussentemperatur = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "Aussentemperatur")]);
DatenHeizungRNV1.TempPufferOben = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "TempPufferOben")]);
DatenHeizungRNV1.TempPufferMitte = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "TempPufferMitte")]);
shrepat commented 3 years ago

Hello all,

Thank you for all the assistance. The issue has been identified to be coming from INotifyPropertyChanged and not from the ReadClass. When I bind with INotifyPropertyChanged, the values get jumbled and mismatched. I tried displaying the class as is with the notifyproperty and without it and noticed it. We would like to bind it to our xaml UI WPF program which is where this problem occurs. Do let me know if someone can assist with this.

shrepat commented 3 years ago

I just would like to share my code how I am reading class from plc:

The class:

public class Heizung
    {
        public Heizung();

        public float Aussentemperatur { get; set; }
        public float TempPufferOben { get; set; }
        public float TempPufferMitte { get; set; }
        public float TempPufferUnten { get; set; }
        public float VLTempLufterhitzer { get; set; }
        public float VLTempWT23 { get; set; }
        public float BetriebsstundenGlattAufheizbetrieb { get; set; }
        public float BetriebsstundenGlattNachheizbetrieb { get; set; }
        public float BetiebsstundenFrostschutz { get; set; }
        public bool Sammelstörung { get; set; }
        public bool HeizungAUS { get; set; }
        public bool HeizungEIN { get; set; }
    }

the declaration:

        readonly Heizung DatenHeizung = new Heizung();

read class from plc:

 if (plcHeizungRNV1.IsConnected)
  {
      plcHeizungRNV1.ReadClass(DatenHeizung, PLC_DB_HEIZUNG, 0);
  }  

I then write the values of the class into a database and show them on a ASP.Net website:

DatenHeizungRNV1.Aussentemperatur = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "Aussentemperatur")]);
DatenHeizungRNV1.TempPufferOben = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "TempPufferOben")]);
DatenHeizungRNV1.TempPufferMitte = Convert.ToSingle(dsHeizungRNV1.Tables[0].Rows[0].ItemArray[spaltenNameHeizungRNV1.FindIndex(x => x == "TempPufferMitte")]);

Hello,

Yes this is very similar to how we read the class now and is working seamlessly.

Thank you and appreciate you for sharing the code.

Regards, Shreyas

scamille commented 3 years ago

Maybe set it up like this:

class Data
{
public float Value {get;set;}
}

class Wrapper : INotifyPropertyChanged
{
public Data Data {get;set;} // Add INotifyProperyChanged stuff to getter and setter here
}

auto wrapper = new Wrapper();
auto data = new Data();
plc.ReadClass(data);
wrapper.Data = data;

And in your UI bind like this: Text="{Binding Data.Value}"