opc-ua-client icon indicating copy to clipboard operation
opc-ua-client copied to clipboard

Winform not work perfeclty

Open franklupo opened this issue 4 years ago • 11 comments

Hi, excellent work. It happens in winform that the ViewModel sometimes does not write to the PLC

Best regards

franklupo avatar Jun 07 '21 14:06 franklupo

Hi Frank,

Take a look at #190 because I think you have the same problem I had... and sometimes still have...

WPF and Windows Forms should be doing different things with view models. My problem was that the ViewModel was "frozen" and OPCUA lib did not even start. When I subscribe for some events in the view model, then "magically" the ViewModel starts to work and communicate.

I think this is related to async code not executing or maybe the main task is paused. Sometimes, my ViewModels run without doing anything special to them. So for me this is still an open issue (not related to this library, I think, but definitely a show stopper if you face it).

ismdiego avatar Jun 10 '21 08:06 ismdiego

Hi, and how do i solve it?

best regards

franklupo avatar Jun 10 '21 09:06 franklupo

Well, in my case the VM did not read and write. If your problem is only related to the VM cannot write, then maybe is a Thread problem. You can try marshalling to the current Thread to write the values to the VM (how... it depends on your code).

If the problem is related to no reading and no writing, then you must try to create a Task/Thread for that ViewModel. I am not still 100% sure of how this works, but in my case I could accomplish that by subscribing to a PropertyChanged event of the VM from the View.

As I am using ReactiveUI MVVM Framework, this translates in using a WhenAnyValue method with whatever VM property. Maybe this it not your case, how are you using the VM in WinForms?

ismdiego avatar Jun 10 '21 09:06 ismdiego

Could you share some code of the ViewModel that is not working?

awcullen avatar Jun 12 '21 03:06 awcullen

HI,

Model

  [Subscription(endpointUrl: FinalSawHelper.Opc.REQUEST_URL, publishingInterval: 500, keepAliveCount: 20)]
    public class FinalSawOpcViewModel : SubscriptionBase
    {
        public FinalSawOpcViewModel() : base() { }
        public FinalSawOpcViewModel(UaApplication application) : base(application) { }

        private short _pressure;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_PRESSURE)]
        public short Pressure
        {
            get => _pressure;
            set => SetProperty(ref _pressure, value);
        }

        private short _cutSpeed;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_CUT_SPEED)]
        public short CutSpeed
        {
            get => _cutSpeed;
            set => SetProperty(ref _cutSpeed, value);
        }

        private short _rollsSpeed;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_ROOLS_SPEED)]
        public short RollsSpeed
        {
            get => _rollsSpeed;
            set => SetProperty(ref _rollsSpeed, value);
        }

        private short _beltsSpeed;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_BELTS_SPEED)]
        public short BeltsSpeed
        {
            get => _beltsSpeed;
            set => SetProperty(ref _beltsSpeed, value);
        }

        private short _length;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_ACTIVE_ORDER_LENGTH)]
        public short Length
        {
            get => _length;
            set => SetProperty(ref _length, value);
        }


        private int _weight;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_WEIGHT_SCALE)]
        public int Weight
        {
            get => _weight;
            set => SetProperty(ref _weight, value);
        }

        private short _r1ProfileNr;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_R1_PROFILE_NR)]
        public short R1ProfileNr
        {
            get => _r1ProfileNr;
            set => SetProperty(ref _r1ProfileNr, value);
        }

        private short _r3LayerNr;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.ITEM_R3_LAYER_NR)]
        public short R3LayerNr
        {
            get => _r3LayerNr;
            set => SetProperty(ref _r3LayerNr, value);
        }

        private OpcTrackLayer _roll3;
        [MonitoredItem(nodeId: FinalSawHelper.Opc.TRACK_LAYER_ROLL3)]
        public OpcTrackLayer Roll3
        {
            get => _roll3;
            set => SetProperty(ref _roll3, value);
        }
    }

Helper


        private static UaApplication _uaApplication;
        public static UaApplication GetUaApplication()
        {
            if (_uaApplication == null)
            {
                _uaApplication = new UaApplicationBuilder()
                                    .SetApplicationUri($"urn:{Dns.GetHostName()}:EPMS")
                                    .SetIdentity(new AnonymousIdentity())

                                    //.AddMappedEndpoint(DieOvenHelper.Opc.REQUEST_URL,
                                    //                   EPMSSettings.Default.OpcUrlPlcDieOvens,
                                    //                   SecurityPolicyUris.None)

                                    .AddMappedEndpoint(ExtrusionHelper.Press.Opc.REQUEST_URL,
                                                       EPMSSettings.Default.OpcUrlPlcExtrusionPress,
                                                       SecurityPolicyUris.None)

                                    .AddMappedEndpoint(ExtrusionHelper.BilletOven.Opc.REQUEST_URL,
                                                       EPMSSettings.Default.OpcUrlPlcExtrusionSawFurnace,
                                                       SecurityPolicyUris.None)

                                    .AddMappedEndpoint(FinalSawHelper.Opc.REQUEST_URL,
                                                       EPMSSettings.Default.OpcUrlPlcFinalSaw,
                                                       SecurityPolicyUris.None)

                                    .AddMappedEndpoint(OvenHelper.Opc.REQUEST_URL,
                                                       EPMSSettings.Default.OpcUrlPlcOvens,
                                                       SecurityPolicyUris.None)

                                    .AddMappedEndpoint(PackingHelper.Opc.REQUEST_URL,
                                                       EPMSSettings.Default.OpcUrlPlcPacking,
                                                       SecurityPolicyUris.None)

                                    .Build();

                _uaApplication.Run();
            }

            return _uaApplication;
        }

Usage

        public UCFinalSawStation()
        {
            InitializeComponent();

....
            _viewModel = new FinalSawOpcViewModel(OpcHelper.GetUaApplication());
            _viewModel.PropertyChanged += ViewModel_PropertyChanged;
            dlcFinalSaw.DataSource = _viewModel;

....
        }

 private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
 {
            switch (e.PropertyName)
            {
.....
            }
}

Best reagrds

franklupo avatar Jun 12 '21 19:06 franklupo

Thanks for the code. Everything looks okay. Tell us more about when the ViewModel does not write to the PLC. Are you using an editor that is bound to a property of the ViewModel?

The library has trouble with setting properties when:

  • from a non-UI thread
  • in the ViewModel_PropertyChanged handler
  • when the PLC is reconnecting

awcullen avatar Jun 15 '21 11:06 awcullen

Hi, has trouble with:

  • from a non-UI thread
  • in the ViewModel_PropertyChanged handler

In some case using editor.

best regards

franklupo avatar Jun 15 '21 12:06 franklupo

From a non-UI thread, or from the PropertyChanged handler you need to do one of the following:

Option 1

        case "Length":
                    Dispatcher.CurrentDispatcher.InvokeAsync(() => { this.Weight = this.Length *42; });

option 2

        case "Length":
                    Task.Run(async () =>
                    {
                        try
                        {
                            await this.InnerChannel.WriteAsync(new WriteRequest
                            {
                                NodesToWrite = new[]
                                {
                                    new WriteValue
                                    {
                                        NodeId = NodeId.Parse(FinalSawHelper.Opc.ITEM_WEIGHT_SCALE),
                                        AttributeId = AttributeIds.Value,
                                        Value = new DataValue((int)(this.Length *42))
                                    }
                                }
                            });
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine("Error writing value : {0}", ex.Message);
                        }
                    });
                

awcullen avatar Jun 15 '21 23:06 awcullen

I create extension with reflection and write/read value and retrive nodeid from attribute.

Best regards

franklupo avatar Jun 22 '21 16:06 franklupo

hI, i create extension to get nodeId from property, and write property using expression.

Best regards

public static class OpcExtension
{
	#region Subscription
	public static string GetNodeId<T>(this T subscription, string propertyName) where T : SubscriptionBaseEx
	   => ((MonitoredItemAttribute)subscription.GetType()
											   .GetProperty(propertyName)
											   .GetCustomAttributes(typeof(MonitoredItemAttribute), true)
											   .FirstOrDefault()).NodeId;

	public static string GetNodeId<T>(this T subscription, Expression<Func<T, object>> expression) where T : SubscriptionBaseEx 
		=> subscription.GetNodeId(NameReaderExtensions.GetMemberName(subscription, expression));

	public static WriteResponse WriteNode<T>(this T subscription,
											 Expression<Func<T, object>> expression,
											 object value) where T : SubscriptionBaseEx
	{
		var propertyName = NameReaderExtensions.GetMemberName(subscription, expression);
		var newValue = Convert.ChangeType(value, subscription.GetType().GetProperty(propertyName).PropertyType);
		return subscription.WriteNode(subscription.GetNodeId(propertyName), newValue);
	}
}
 public static class NameReaderExtensions
    {
        public static string GetMemberName<T>(this T instance, Expression<Func<T, object>> expression) where T: INameReaderExtensions
            => GetMemberName(expression.Body);

        public static List<string> GetMemberNames<T>(this T instance, params Expression<Func<T, object>>[] expressions) where T : INameReaderExtensions
            => expressions.Select(a => GetMemberName(a.Body)).ToList();

        public static string GetMemberName<T>(this T instance, Expression<Action<T>> expression) where T : INameReaderExtensions
            => GetMemberName(expression.Body);

        private static string GetMemberName(Expression expression)
        {
            if (expression == null) { throw new ArgumentException("The expression cannot be null."); }

            // Reference type property or field
            if (expression is MemberExpression memberExpression) { return memberExpression.Member.Name; }

            // Reference type method
            if (expression is MethodCallExpression methodCallExpression) { return methodCallExpression.Method.Name; }

            // Property, field of method returning value type
            if (expression is UnaryExpression unaryExpression) { return GetMemberName(unaryExpression); }
            throw new ArgumentException("Invalid expression.");
        }

        private static string GetMemberName(UnaryExpression unaryExpression)
        {
            if (unaryExpression.Operand is MethodCallExpression methodExpression) { return methodExpression.Method.Name; }
            return ((MemberExpression)unaryExpression.Operand).Member.Name;
        }
    }

franklupo avatar Oct 18 '21 12:10 franklupo

Very nice! Thanks.

awcullen avatar Oct 22 '21 00:10 awcullen