Open crenwick opened 5 years ago
Hi, I agree the client API is a little confusing.
The current default behaviour for the client is that you define the endpoints you expect to be on the server, including one which is your default. Then you call connect_and_activate() which will attempt to connect to the endpoint you specify by its id or with the default. But if the endpoint doesn't match any on the server you'll get an error.
If you prefer to blindly connect, you should be able to do something like this pseudo code instead:
let mut client = ClientBuilder::new()
.endpoint(
"lab_plc",
ClientEndpoint {
url: String::from("opc.tcp://192.168.0.1:4840"),
security_policy: String::from(SecurityPolicy::None.to_str()),
security_mode: String::from(MessageSecurityMode::None),
user_token_id: String::from("ANONYMOUS")
},
)
.default_endpoint("lab_plc")
.client().unwrap();
// This will connect to the default endpoint "lab_plc" and get endpoints
let endpoints = client.get_server_endpoints();
// This will try for an exact match and then a fuzzy match on the server's endpoints vs your input criteria
if let Some(endpoint) =client.find_server_endpoint(&endpoints, my_url, my_policy, my_mode) {
let mut session = client.new_session_from_endpoint(&endpoint).unwrap();
session.connect_and_activate_session();
//...
}
This tends to be a problem with OPC UA. Servers may be configured to advertise endpoints that do not match the hostname of the computer, or there may be multiple hostnames. Thus far I've tried to code the client to be as flexible as possible, but I think I will work on simplifying it further for the common cases.
One further improvement is could probably do this (again pseudo code)
let mut client = ClientBuilder::new().client.unwrap();
let endpoints = client.get_server_endpoints_from_url(server_url);
//....
i.e. you don't need to supply an endpoint at all in your builder, but instead just call the ad hoc get_server_endpoints_from_url() and then match up.
Hmmm. After changing the get_server_endpoints_from_url
function to be public, the code paniced when calling read_nodes
on the session after calling write
.
Panics on this scope:
let data_values = {
let mut session = session.write().unwrap();
session.read_nodes(&read_nodes)?.unwrap // panics here
};
While the same code doesn't panic when I use the method I described above (hard-coding the url in the decode impl of the struct, and calling connect_and_activate
).
I didn't have time to investigate where in the library read_nodes
is failing, but I hope to do that after the holidays.
Thanks for getting back to me!
No probs & enjoy your holidays! When you're back, let me have a sample of what you're doing and I will see if I can identify where the issue lies.
Hey,
Got to play around with the code a bit again today. I found a way around the API, but its a little hacky. Please let me know if there is a more straightforward API I should be using.
After creating client endpoint, I created a client:
let mut client = ClientBuilder::new()
...
.endpoint("lab", client_endpoint.clone())
.default_endpoint("lab")
.client()
.unwrap();
I then created a custom endpoint by cloning one of the server-returned endpoints and overriding the url:
let server_eps = client.get_server_endpoints_from_url(SERVER_URL).unwrap();
let mut ep = sample_eps[0].clone();
ep.endpoint_url = UAString::from(SERVER_URL);
let eps = vec![ep];
Then I could create a session using the new endpoint vector:
let session = client
.new_session_from_endpoint(&client_endpoint, &eps)
.unwrap();
Finally I was able to call write()
, connect()
, create_session()
, activate_session()
, and read_nodes(&nodes)
on that session to get the values I wanted.
That said, I still had to make the get_server_endpoints_from_url
function public, as I found it the most straightforward way to create an valid EndpointDescription.
Hello!
Not sure if I'm using this wrong but when I try to create a session from a client I cannot get the program to connect to a custom endpoint I configured. Instead, it only allows me to connect to an endpoint that the server responds with.
For example, if I create a session via the
ClientBuilder
API like so:the
connect_and_activate
method will not use the"lab_plc"
endpoint like I expected it to. Instead that method callsClient.get_server_endpoints()
to build an internal list of endpoints that it gets from asking the OPCUA server what endpoints exist. If the server doesn't respond with the endpoint that I want (for example, instead of listingopc.tcp://192.168.0.1:4840
as an endpoint url, the server responds with a hostname likeopc.tcp://automation:4840
that doesn't resolve correctly on the network) then there is no alternative way for me to override that hostname with my custom url.My "fix" was to clone the types crate and edit the
endpoint_description.rs
file so that thedecode
method returned a hardcoded value in the struct:Am I using this API wrong? Is there a better API to use where I can send it the endpoint url I want?
Thanks!