convertersystems / opc-ua-samples

Sample HMIs using OPC Unified Architecture (OPC UA) and Visual Studio.
MIT License
107 stars 43 forks source link

Node monitoring reliability #40

Closed rudoc closed 7 years ago

rudoc commented 7 years ago

Dear Andrew! On a server side I do command validation and immediately reverting variable state back to previous value if not validated. The command is being successfully sent from RobotApp, but client does not picking up reverted variable value back, so HMI would reflect incorrect figures (RobotHmi does the same). I am using CODESYS Control Win as a PLC, which running with 20ms cycle. Increasing PLC scan time improves the reliability but still discrepancy occurs. As a workaround I see implementation of the same validation on client side and block command which will be rejected as better option than increasing the PLC cycle. Cross checked with UAExpert and found that one will pick up reverted value correctly even with 20ms cycle PLC scan time. Hope this remark will fall within your interest scope.

awcullen commented 7 years ago

I just learned of a robotics demo of OPC UA technology where the latency time was < 10ms. All communication from the PLC to the robot were made using the "Call" method. Interesting.

There are three ways to send commands to the server. Set a property that writes to the server, call the "WriteAsync" method, or call the "CallAsync" method.

If you wish to add validation to the property setter, or the WriteCommand, you should probably do it in the client.

If you use a server-based "method", then the server method can perform the validation and return a StatusCode in the response.

/// <summary>
/// Gets or sets a value indicating whether Robot1Laser is active.
/// </summary>
[MonitoredItem(nodeId: "ns=2;s=Robot1_Laser")]
public bool Robot1Laser
{
    get { return this.robot1Laser; }
    set 
    {
        // validate here 
        this.SetProperty(ref this.robot1Laser, value); 
    }
}
private bool robot1Laser;

/// <summary>
/// Gets the command to set the value of Robot1Laser to On.
/// </summary>
public ICommand Robot1LaserCommand
{
    get
    {
        return new DelegateCommand(async () =>
        {
            try
            {
                // validate here
                await this.InnerChannel.WriteAsync(new WriteRequest
                {
                    NodesToWrite = new[]
                    {
                        new WriteValue
                        {
                            NodeId = NodeId.Parse("ns=2;s=Robot1_Laser"),
                            AttributeId = AttributeIds.Value,
                            IndexRange = null,
                            Value = new DataValue(true)
                        }
                }
                });
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error writing to NodeId {0} : {1}", "ns=2;s=Robot1_Laser", ex.Message);
            }
        });
    }
}

/// <summary>
/// Gets the command to call the method named Robot1SetLaser.
/// </summary>
public ICommand Robot1SetLaserCommand
{
    get
    {
        return new DelegateCommand(async () =>
        {
            try
            {
                // Call the method, passing the input arguments in a Variant[].
                var response = await this.InnerChannel.CallAsync(new CallRequest
                {
                    MethodsToCall = new[]
                    {
                        new CallMethodRequest
                        {
                            ObjectId = NodeId.Parse("ns=2;s=Robot1"),
                            MethodId = NodeId.Parse("ns=2;s=Robot1_SetLaser"),
                            InputArguments = new Variant[] { true }
                        }
                    }
                });
                var code = response.Results[0].StatusCode;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error calling Robot1Multiply method: {0}", ex.Message);
            }
        });
    }
}
rudoc commented 7 years ago

Hi Andrew! I've been trying your solution to use “WriteAsync” and found this approach is the only suitable for real application. There was no any discrepancy for HMI and server values observed. Now I am figuring out for how to parametrize the command. Thank you again for pointing the right way!

awcullen commented 7 years ago

Added bool parameter 'p' to command.

public ICommand Robot1LaserCommand
{
    get
    {
        return new DelegateCommand<bool>(async (p) =>
        {
            try
            {
                 // validate here
                await this.InnerChannel.WriteAsync(new WriteRequest
                {
                    NodesToWrite = new[]
                    {
                        new WriteValue
                        {
                            NodeId = NodeId.Parse("ns=2;s=Robot1_Laser"),
                            AttributeId = AttributeIds.Value,
                            IndexRange = null,
                            Value = new DataValue(p)
                        }
                    }
                });
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error writing to NodeId {0} : {1}", "ns=2;s=Robot1_Laser", ex.Message);
            }
        });
    }
}
rudoc commented 7 years ago

Andrew! This is exactly the solution i was looking for. passing the parameter to command i use for parsed node string change. Thanks!