Closed roozbeh63 closed 7 years ago
One way to do this is to turn off the secure endpoints at the server. :)
But maybe that's not practical. You can use the code below to get the available endpoints yourself. Then you can select the one you want.
Console.WriteLine($"Discovering endpoints of '{this.endpointUrl}'.");
var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(
new GetEndpointsRequest
{
EndpointUrl = this.endpointUrl,
ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
});
var selectedEndpoint = getEndpointsResponse.Endpoints.First(e => e.SecurityMode == MessageSecurityMode.None);
var session = new UaTcpSessionClient(
this.localDescription,
this.certificateStore,
ed => Task.FromResult<IUserIdentity>(new UserNameIdentity("root", "secret")),
selectedEndpoint,
loggerFactory: this.loggerFactory);
thanks for the help, now i have another issue. when I read the node from the device it always gives me zero. here is the code that i have, maybe you can see the problem.
this is the code in ViewModel
` [Subscription(publishingInterval: 250, keepAliveCount: 20)] // Step 2: Add a [Subscription] attribute.
public class MainPageViewModel : ViewModelBase
{
private readonly ILogger
public MainPageViewModel(UaTcpSessionClient session)
{
this.session = session;
session?.Subscribe(this);
}
[MonitoredItem(nodeId: "ns=12;s=Motion.AxisSet.LocalControl.Axis1.State")] // Step 4: Add a [MonitoredItem] attribute.
public int Robot1Mode
{
get { return this.robot1Mode; }
set { this.SetProperty(ref this.robot1Mode, value); }
}
private int robot1Mode;
internal class MainViewModelDesignInstance : MainPageViewModel
{
public MainViewModelDesignInstance()
: base(null)
{
}
}
}`
this is the code in xaml file:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App6"
x:Class="App6.MainPage">
<Label Text="{Binding Robot1Mode, StringFormat='{0:F2}'}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</ContentPage>
Do you have 12 namespaces? Maybe this should be ns=2;
nodeId: "ns=12;s=Motion.AxisSet.LocalControl.Axis1.State"
no that is correct I double checked it.
I can send you a sample of the project that I have? if yes then how can I send it? through email maybe?
You could put the project in DropBox, but I think you should copy the text from the debug window, and paste it here
it does't give me any errors, those that I sent were my MainPageViewModel which I inherit from your ViewModelBase. the xaml file was MainPage.xaml and below is my app.cs
` public partial class App : Application { private string discoveryUrl = @"opc.tcp://192.168.0.100:4840"; private ILoggerFactory loggerFactory; private ILogger logger; private UaTcpSessionClient session;
public App ()
{
InitializeComponent();
MainPage = new App6.MainPage();
}
protected override void OnStart ()
{
this.loggerFactory = new LoggerFactory();
this.logger = this.loggerFactory.CreateLogger<App>();
var appDescription = new ApplicationDescription()
{
ApplicationName = "MyHomework",
ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:MyHomework",
ApplicationType = ApplicationType.Client,
};
var getEndpointsResponse = UaTcpDiscoveryClient.GetEndpointsAsync(
new GetEndpointsRequest
{
EndpointUrl = this.discoveryUrl,
ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
}).Result;
var selectedEndpoint = getEndpointsResponse.Endpoints.First(e => e.SecurityMode == MessageSecurityMode.None);
this.session = new UaTcpSessionClient(
appDescription,
new DirectoryStore(Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Workstation.ConsoleApp\pki")),
ed => Task.FromResult<IUserIdentity>(new UserNameIdentity("root", "secret")),
selectedEndpoint,
loggerFactory: this.loggerFactory);
var viewModel = new MainPageViewModel(this.session);
var view = new MainPage { BindingContext = viewModel };
this.MainPage = new NavigationPage(view);
}
protected override void OnSleep ()
{
// Handle when your app sleeps
}
protected override void OnResume ()
{
// Handle when your app resumes
}
private Task<IUserIdentity> ProvideUserIdentity(EndpointDescription endpoint)
{
// Due to problem with dns on android emulator, the endpoint url's hostname is rewritten with an ip address.
endpoint.EndpointUrl = this.discoveryUrl;
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.Anonymous))
{
return Task.FromResult<IUserIdentity>(new AnonymousIdentity());
}
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.UserName))
{
return Task.FromResult<IUserIdentity>(new UserNameIdentity("root", "secret"));
}
return Task.FromResult<IUserIdentity>(new AnonymousIdentity());
}
}`
Do you have UaExpert (free from Unified-Automation)? With UaExpert you could browse to that node and double-check the nodeid and datatype.
I will download it asap, but you don't see any problem in the code? right? I mean any obvious problem
Are you running this Xamarin app on android emulator? Is the server running on the same PC as the emulator or on a different PC?
I am running Xamarin on android emulator, and the server is a control device from Bosch. it is a Linux embedded device
Cool. Would you change the code where you create the session to:
this.session = new UaTcpSessionClient(
appDescription,
new DirectoryStore(Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Workstation.ConsoleApp\pki")),
ProvideUserIdentity,
selectedEndpoint,
loggerFactory: this.loggerFactory);
I just did, but result is the same, still getting 0. and it does not give any errors. it just dons't give me the proper result by some reason.
Also, I see the problem why you are not getting debug messages. Please change the OnStart:
this.loggerFactory = new LoggerFactory();
this.loggerFactory.AddDebug(LogLevel.Trace); // new
this.logger = this.loggerFactory.CreateLogger<App>();
I don't have this.loggerFactory.AddDebug in Xamarin, it does not provide me with that function. I have only
AddProvider
CreateLogger
Dispose
Equals
GetHashCode
GetType
ToString
I forgot to say you will need to add Nuget Package "Microsoft.Extensions.Logging.Debug": "1.1.1",
that is what i am getting
InspectorDebugSession(40): HandleTargetEvent: ThreadStarted [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Received PublishResponse Handle: 20 Result: 0x00000000 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Sending PublishRequest Handle: 23 03-24 01:08:24.880 D/Mono (19244): [0xbaedeac0] hill climbing, change max number of threads 2 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Received PublishResponse Handle: 21 Result: 0x00000000 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Sending PublishRequest Handle: 24 03-24 01:08:31.900 D/Mono (19244): [0xb9f59ef0] hill climbing, change max number of threads 3 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Received PublishResponse Handle: 22 Result: 0x00000000 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Sending PublishRequest Handle: 25 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Received PublishResponse Handle: 23 Result: 0x00000000 [0:] Workstation.ServiceModel.Ua.Channels.UaTcpSessionChannel: Trace: Sending PublishRequest Handle: 26 InspectorDebugSession(40): Disposed
That's wonderful. Getting PublishResponses is a good thing. Maybe its time to download UaExpert from UnifiedAutomation to Double check nodeId and datatype.
oh good, means I am on a right track, I am going to check it asap, thanks for the help, btw you are a great programmer, I learn from reading your codes
is there anyway to handle errors of connection? or when endpoints can't be retrieved? for example if the IP address of the server is not reachable or server does not respond by any reason.
for example in UnifiedAutomation , for Sessoin we have a ConnectionStatuse method which tells us what is the condition of the connection so I can respond to it accordingly. how I can be doing the same here?
Today, if the UaTcpSessionClient encounters an error, it just retries the connection after a delay that starts at 1 second and grows to 20 seconds. When the connection is successful, the subscriptions are recreated. In your case, because you initially are using DiscoveryClient to select an endpoint, the OnStart() is having problems if the server is down.
As a workaround I suggest you comment out the discovery:
/*
var getEndpointsResponse = UaTcpDiscoveryClient.GetEndpointsAsync(
new GetEndpointsRequest
{
EndpointUrl = this.discoveryUrl,
ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
}).Result;
var selectedEndpoint = getEndpointsResponse.Endpoints.First(e => e.SecurityMode == MessageSecurityMode.None);
*/
Change SessionClient to use Url
this.session = new UaTcpSessionClient(
appDescription,
new DirectoryStore(Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Workstation.ConsoleApp\pki")),
ProvideUserIdentity,
this.discoveryUrl,
loggerFactory: this.loggerFactory);
Then change ProvideUserIdentity:
private async Task<IUserIdentity> ProvideUserIdentity(EndpointDescription endpoint)
{
// overide security parameters.
endpoint.SecurityLevel = 0;
endpoint.SecurityMode = MessageSecurityMode.None;
endpoint.SecurityPolicyUri = SecurityPolicyUris.None;
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.Anonymous))
{
return new AnonymousIdentity();
}
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.UserName))
{
return new UserNameIdentity("root", "secret");
}
throw new NotImplementedException("ProvideUserIdentity supports only UserName and Anonymous identity, for now.");
}
Let me know if this workaround fixes the OnStart problem. I'll look into ConnectionStatus method. I'm curious to know what events you wish to handle and what action you would take.
Andrew
I am receiving data from the control now, I traced the node and the problem was with type of variables. also I want to give notifications in my app when:
1- connection is interrupted 2- the app can't find the server IP address 3- endpoint is not available. 4- the Node ID does not exist (which is very essential I think).
with ConnectionStatus I can inform the user that what the app is doing, for example is searching for the connection or connected or disconnected. so I can have a robust solution as an app.
I have this piece of code to connect the color of a boxview to a variable. I am getting the variable correctly updated, but the color does't change.
`
#region rTtMotorTempKty1A
private float _rTtMotorTempKty1A;
[MonitoredItem(nodeId: "ns=2;s=Application.DataToHmiTab_DrvA.rTtMotorTempKty1")]
public float rTtMotorTempKty1A
{
get { return this._rTtMotorTempKty1A; }
set { this.SetProperty(ref this._rTtMotorTempKty1A, value); }
}
#endregion
private Color _colorA;
public Color ColorA
{
get
{
if (this.rTtMotorTempKty1A > 100)
this._colorA = Color.Red;
else
this._colorA = Color.Blue;
return this._colorA;
}
set { this.SetProperty(ref this._colorA, value); }
}`
Try this:
private float _rTtMotorTempKty1A;
[MonitoredItem(nodeId: "ns=2;s=Application.DataToHmiTab_DrvA.rTtMotorTempKty1")]
public float rTtMotorTempKty1A
{
get { return this._rTtMotorTempKty1A; }
set
{
this.SetProperty(ref this._rTtMotorTempKty1A, value);
NotifyPropertyChanged(nameof(ColorA));
}
}
public Color ColorA
{
get
{
if (this.rTtMotorTempKty1A > 100)
return Color.Red;
else
return Color.Blue;
}
}
it got fixed, thanks. do you think it is possible to do something regarding error handing? Connection Status? and when NodeID is not found?
so is it possible to have error handling system in the package? since I have a deadline for my project and the project is still far from being robust. what do you think I can do? what do you propose?
can you please tell me even if your answer is no, because I have a deadline for my project and if I am not getting error handling mechanism I have to think about another solution
I am sorry to hear that you are unhappy with the project. I have noted your suggestions for improvements in the diagnostic area. I will continue working on v2.0! Best of luck.
I am happy with the project, thanks for the great job. it is just error handling that is being missed. when do you think then second version would be released? or there is no clear timing for it?
Good things take time. Maybe we could get creative to solve these diagnostic issues. What is the major problem you are facing? Could you share your App.cs and MainPageViewModel.cs?
the main problem with Connection Status and NodeID when it is not found. now if we can throw one exception for NodeID when it is not found and for Connection Status an event which I can subscribe to.
here you can see my App.cs and part of MainPageViewModel.cs. but I am not getting any errors here, I just want to make my application as robust as possible.
App.cs `public partial class App : Application { private string discoveryUrl = @"opc.tcp://192.168.88.100:4840"; private ILoggerFactory loggerFactory; private ILogger logger; private UaTcpSessionClient session; public App () { InitializeComponent();
MainPage = new HmiTablet.MainPage();
}
protected override void OnStart ()
{
this.loggerFactory = new LoggerFactory();
this.loggerFactory.AddDebug(LogLevel.Trace); // new
this.logger = this.loggerFactory.CreateLogger<App>();
var appDescription = new ApplicationDescription()
{
ApplicationName = "HmiTablet",
ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:HmiTablet",
ApplicationType = ApplicationType.Client,
};
var getEndpointsResponse = UaTcpDiscoveryClient.GetEndpointsAsync(
new GetEndpointsRequest
{
EndpointUrl = this.discoveryUrl,
ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
}).Result;
var selectedEndpoint = getEndpointsResponse.Endpoints.First(e => e.SecurityMode == MessageSecurityMode.None);
this.session = new UaTcpSessionClient(
appDescription,
new DirectoryStore(Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Workstation.ConsoleApp\pki")),
ProvideUserIdentity,
selectedEndpoint,
loggerFactory: this.loggerFactory);
var viewModel = new MainPageViewModel(this.session);
var view = new MainPage { BindingContext = viewModel };
this.MainPage = new NavigationPage(view);
}
protected override void OnSleep ()
{
// Handle when your app sleeps
}
protected override void OnResume ()
{
// Handle when your app resumes
}
private Task<IUserIdentity> ProvideUserIdentity(EndpointDescription endpoint)
{
// Due to problem with dns on android emulator, the endpoint url's hostname is rewritten with an ip address.
endpoint.EndpointUrl = this.discoveryUrl;
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.Anonymous))
{
return Task.FromResult<IUserIdentity>(new AnonymousIdentity());
}
if (endpoint.UserIdentityTokens.Any(p => p.TokenType == UserTokenType.UserName))
{
return Task.FromResult<IUserIdentity>(new UserNameIdentity("root", "secret"));
}
return Task.FromResult<IUserIdentity>(new AnonymousIdentity());
}
}`
MainPageViewModel.cs ` [Subscription(publishingInterval: 250, keepAliveCount: 20)] // Step 2: Add a [Subscription] attribute. public class MainPageViewModel : ViewModelBase {
#region initialization
private readonly ILogger<MainPageViewModel> logger;
private readonly UaTcpSessionClient session;
public MainPageViewModel(UaTcpSessionClient session)
{
this.session = session;
session?.Subscribe(this);
}
#endregion
#region definition of motor A variables
#region rTtMotorTempKty1A
private float _rTtMotorTempKty1A;
[MonitoredItem(nodeId: "ns=2;s=Application.DataToHmiTab_DrvA.rTtMotorTempKty1")]
public float rTtMotorTempKty1A
{
get { return this._rTtMotorTempKty1A; }
set { this.SetProperty(ref this._rTtMotorTempKty1A, value);
//NotifyPropertyChanged(nameof(ColorA));
}
}
#endregion
private Color _colorA;
public Color ColorA
{
get
{
if (this._bSynchronA == false)
this._colorA = Color.Red;
else
this._colorA = Color.Blue;
return this._colorA;
}
set { this.SetProperty(ref this._colorA, value); }
}
#region rActualpositionA
private float _rActualpositionA;
[MonitoredItem(nodeId: "ns=2;s=Application.DataToHmiTab_DrvA.rActualposition")]
public float rActualpositionA
{
get { return this._rActualpositionA; }
set { this.SetProperty(ref this._rActualpositionA, value); }
}
#endregion
#region rActualtorqueA
private float _rActualtorqueA;
[MonitoredItem(nodeId: "ns=2;s=Application.DataToHmiTab_DrvA.rActualtorque")]
public float rActualtorqueA
{
get { return this._rActualtorqueA; }
set { this.SetProperty(ref this._rActualtorqueA, value); }
}
#endregion`
I updated UaClient package to 1.5.11.
Please check out MobileDroid sample and notice:
In App.cs, I inserted some code in the ProvideUserIdentity that disables the security settings. (You should fix the security on your server). This approach would allow you to remove your call to GetEndpointsAsync, and has the advantage of automatically retrying every 2-20 seconds until the server is online.
In MainPageViewMode.cs, I added a property IsDisconnected. This is bound to the visibility of a label in MainPage.xaml.
For NodeId errors (Create, Write, etc), I use the system that is provided by INotifyDataErrorInfo in ViewModelBase. In the WPF framework, there is already a UI provided for these Validation errors. I was hoping that Xamarin would include the UI as well, but I have not yet found it. For purposes of the demo I added a property to allow access to the DataErrors of one property. I bound it to a ListView on the MainPage. I got mixed results.
Can the opc-ua client handle also user validation errors from OPC-UA servers (wrong credentials) ? What about the handling of errors regarding the permitted actions for validated users (for instance the actions permitted for validated user allow the reading of opc-ua items, but not the writing of values for opc-ua items) ?
When the application starts, UaTcpSessionClient will begin opening a channel to the server. First it uses DiscoveryClient to get the list of endpoints from the provided url. Second, it chooses the most secure endpoint and calls the 'ProvideUserIdentiy' callback. This call is asynchronous, so you are free to open a dialog window and have your users enter a username and password. When the callback returns with a IUserIdentity, the UaTcpSessionClient finishes opening the channel. If the userName or password is wrong, then UaTcpSessionClient will retry from the start. Each retry is delayed from 2-20 seconds.
If your user is not permitted to Write certain nodes, then the server will return a bad status code. In the case of the MainPageViewModel (above), the write error is stored in the errors collection in ViewModelBase. The WPF framework has built-in styles that highlight the UI with a red border and display the error message. In other frameworks, you have to listen for ErrorsChanged event and call GetErrors()
V2.0.0-RC2 on NuGet has the following: View-models can inherit from SubscriptionBase, which implements INotifyPropertyChanged. SubscriptionBase automatically connects to specified server endpointUrl and creates the opc-ua subscription and monitored items. It receives the publish responses and places the dataValue or event in the specified view-model properties. It provides a property InnerChannel that allows you to call any opc-ua method such as Read, Write, CallMethod, etc. It provides a property State that indicates the CommunicationState of the connection. If errors occur with creating, publishing or writing Monitored Items, the error message is provided by the INotifyDataErrorInfo interface. If errors occur with the InnerChannel, then the State property is changed to Faulted, and the channel is recreated automatically.
I hope this is helpful. Closing this issue for now.
did you remove IsDisconnected from the repository? I can't find it anymore, and I tried to find it in commit history, but no luck, is it possible to commit the same version again? thanks.
IsDisconnected has been replaced by:
/// <summary>
/// Gets the <see cref="CommunicationState"/>.
/// </summary>
public CommunicationState State { get; }
Where CommunicationState is:
public enum CommunicationState
{
Created,
Opening,
Opened,
Closing,
Closed,
Faulted
}
Is there also some event available, triggered when communication state changes (something like CommunicationStateChangedEvent) ?
Regarding the INotifyDataErrorInfo, could you please provide a sample about how to use it in a non-UI application ? For instance could you add it to the
[TestMethod]
public async Task TestSubscription()
{...}
from UnitTest1.cs ? Would be for instance useful to see what errors are generated in opc-ua client over INotifyDataErrorInfo when the opc-ua-server uri is not reachable, or when the node id is unknown on server.
in OpenAsync function in UaTcpSessionClient class you are setting the RemoteEndpoint with this function:
this.RemoteEndpoint = getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel).Last()
which gives me the last endpoint (obviously). however for my application I need to use the endpoint with none security, which I am using in Xamarin, and I don't have access to change it. can you help me with that? thanks in advanced