Multicast DNS name conflict in OPC UA Discovery - opc-ua

Working on a requirement to display the status information of the server( Available or Busy) to the client.
2 approach followed:
used the facility of server capabilities array to display information. As it is an array, 1st element was made to be the feature of the server and 2nd element, to be the status(Initially- Available). The server config(of the multicast server is changed to max session of 1. When this happens the 2nd element of the capability array is changed as Busy. However, while running the client application(findServersOnNetwork.c from open62541 discovery examples), this is not reflected, as values of the config parameters are taken before the server runs( eg: Available status throughout)
2)Using the same server capabiltity array extension, this time, once a session is established, I unregister the server, change the capability list to Busy and register the server again.
But while doing so, i get an error :
Multicast DNS name conflict detected: 'Crane Multicast Server-._opcua-tcp._tcp.local.' for type 16
I have attached the log of the LDS of successful deregistration( removal of the record). Yet it shows a conflict.
Please let me know what could be the issue here?
Inspite of the removal of records, the conflict is not acceptable right?
Image of the LDS Log
Reproduction steps:
server_multicast.c( from the example section of open62541 stack)
while(running== true){
UA_Server_run_iterate(server, true);
if(count!=1){
if(time(0)>timestamp){
timestamp= time(0) + 3;
int count= getCountValue(); //returns the count of sessions currently
printf("Num: %d\n",count);
caps[0]= UA_String_fromChars("Available");
if(count==1){
//caps[1]= UA_String_fromChars("Busy");
//printf("The server status is: %.*s\n", caps[1]);
if(check==false){
returnValue =Routine_run(server,clientRegister,config,endpointUrl,caps); // to dereg, change caps and register again
printf("The server status is: %.*s\n", returnValue);
check=true;}
else{
continue;
}
}
else{
continue;
}
}
}
else{
continue;
}
}
static UA_String Routine_run(UA_Server *server, UA_Client *clientRegister,UA_ServerConfig *config,char *endpointUrl,UA_String *caps){
UA_StatusCode retval;
retval = UA_Server_unregister_discovery(server, clientRegister);
if(retval != UA_STATUSCODE_GOOD){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"Could not unregister server from discovery server. "
"StatusCode %s", UA_StatusCode_name(retval));}
else{
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"Unregistration Successfull. "
"StatusCode %s", UA_StatusCode_name(retval));
}
UA_Server_removeCallback(server, callbackId);
caps[1] = UA_String_fromChars("Busy");
UA_StatusCode retval2= UA_Server_register_discovery(server, clientRegister, NULL);
if(retval2 != UA_STATUSCODE_GOOD){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"Could not register server from discovery server. "
"StatusCode %s", UA_StatusCode_name(retval));
caps[1] = UA_String_fromChars("FAIL");}
else{
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"registration Successfull. "
"StatusCode %s", UA_StatusCode_name(retval));
caps[1] = UA_String_fromChars("Busy");
}
return caps[1];
}

Re "By available and busy, we mean to say whether server is free or is having a session with another client. OPC UA defines a discovery mechanism where the server is capable of providing its feratures ..." :
Even if it was possible technically, you can not provide this information through OPC UA discovery without violating the OPC UA discovery specification.

Related

SockJS connections in a clustered Vert.x environment

The vertx application runs in Docker containers, on two EC2 instances and is clustered.
Clustering is achieved with the hazelcast-aws plugin and the application is started like this:
docker run --name ... -p ... \
--network ... \
-v ... \
-d ... \
-c 'exec java \
-Dvertx.eventBus.options.setClustered=true \
-Dvertx.eventBus.options.setClusterPort=15701 \
-jar ... -conf ... \
-cluster'
Nothing cluster-related is set programmatically.
Client opens a socket on the first request and uses it for future similar requests.
Each request will:
initiate an async request with the server by publishing a message to the event bus
register a consumer on the event bus which will handle the result of the above,
and which is passed a reference to the socket connection where it should send the result to
Since vertx does round robin by default when clustered and there are two instances, this means any instance gets every other message (from 1., above) and makes the client, which connects to one instance only, receive exactly half of all expected responses.
I suppose this is because, even though the registered consumer has a reference to the socket object, it can't use it because it was created on a different node/webserver.
Would that be correct and is there a way to get 100% of messages to the client, connected to just one node, without introducing things like RabbitMQ?
Here's the SockJS handler code:
SockJSHandler sockJSHandler = SockJSHandler.create(vertx, new SockJSHandlerOptions());
sockJSHandler.socketHandler(socket -> {
SecurityService securityService = (SecurityService) ServiceFactory.getService(SecurityService.class);
if (securityService.socketHeadersSecurity(socket)) {
socket.handler(socketMessage -> {
try {
LOGGER.trace("socketMessage: " + socketMessage);
Socket socket = Json.decodeValue(socketMessage.toString(), Socket.class);
Report report = socket.getReport();
if (report != null) {
Account accountRequest = socket.getAccount();
Account accountDatabase = accountRequest == null ? null
: ((AccountService) ServiceFactory.getService(AccountService.class)).getById(accountRequest.getId());
Response result = securityService.socketReportSecurity(accountRequest, accountDatabase, report) ?
((ReportService) ServiceFactory.getService(ReportService.class)).createOrUpdateReport(report, accountDatabase)
: new Response(Response.unauthorized);
if (Response.success.equals(result.getResponse())) {
//register a consumer
String consumerName = "report.result." + Timestamp.from(ClockFactory.getClock().instant());
vertx.eventBus().consumer(consumerName, message -> {
Response executionResult;
if ("success".equals(message.body())) {
try {
Path csvFile = Paths.get(config.getString(Config.reportPath.getConfigName(), Config.reportPath.getDefaultValue())
+ "/" + ((Report) result.getPayload()).getId() + ".csv");
executionResult = new Response(new JsonObject().put("csv", new String(Files.readAllBytes(csvFile))));
} catch (IOException ioEx) {
executionResult = new Response(new Validator("Failed to read file.", ioEx.getMessage(), null, null));
LOGGER.error("Failed to read file.", ioEx);
}
} else {
executionResult = new Response(new Validator("Report execution failed", (String)message.body(), null, null));
}
//send second message to client
socket.write(Json.encode(executionResult));
vertx.eventBus().consumer(consumerName).unregister();
});
//order report execution
vertx.eventBus().send("report.request", new JsonObject()
.put("reportId", ((Report) result.getPayload()).getId())
.put("consumerName", consumerName));
}
//send first message to client
socket.write(Json.encode(result));
} else {
LOGGER.info("Insufficient data sent over socket: " + socketMessage.toString());
socket.end();
}
} catch (DecodeException dEx) {
LOGGER.error("Error decoding message.", dEx);
socket.end();
}
});
} else {
LOGGER.info("Illegal socket connection attempt from: " + socket.remoteAddress());
socket.end();
}
});
mainRouter.route("/websocket/*").handler(sockJSHandler);
Interestingly, when running two nodes clustered on localhost the client gets 100% of the results.
EDIT:
This was not a SockJS but a configuration issue.
Since vertx does round robin by default when clustered and there are
two instances, this means any instance gets every other message (from
1., above) and makes the client, which connects to one instance only, receive exactly half of all expected responses.
This assumption is only partially correct. Vert.x does round-robin, yes, but this means each instance will get half of the connections, not half of the messages.
Once connection is established, all its messages will arrive to a single instance.
So this:
Would that be correct and is there a way to get 100% of messages to
the client, connected to just one node, without introducing things
like RabbitMQ?
Already happens.

UaSerializationException: request exceeds remote max message size: 2434140 > 2097152

I am a rookie, I tried to use the following code for bulk subscription, but something went wrong, how can I solve this problem
OpcUaSubscriptionManager subscriptionManager = opcUaClient.getSubscriptionManager();
UaSubscription subscription = subscriptionManager.createSubscription(publishInterval).get();
List<MonitoredItemCreateRequest> itemsToCreate = new ArrayList<>();
for (Tag tag : tagList) {
NodeId nodeId = new NodeId(nameSpace, tag.getPath());
ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
MonitoringParameters parameters = new MonitoringParameters(
subscription.nextClientHandle(), //
publishInterval, //
null, // filter, null means use default
UInteger.valueOf(queueSize), // queue size
true // discard oldest
);
MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId,
MonitoringMode.Reporting, parameters);
itemsToCreate.add(request);
}
BiConsumer<UaMonitoredItem, Integer> consumer =(item, id) ->
item.setValueConsumer(this::onSubscriptionValue);
List<UaMonitoredItem> items = subscription.createMonitoredItems(
TimestampsToReturn.Both,
itemsToCreate,
consumer
).get();
for (UaMonitoredItem item : items) {
if (!item.getStatusCode().isGood()) {
log.error("failed to create item for nodeId={} (status={})",item.getReadValueId().getNodeId(), item.getStatusCode());
}
}
How many items are you trying to create?
It seems that the resulting message exceeds the limits set by the server you are connecting to. You may need to break your list up and create the items in smaller chunks.
I do not know the library that you use, but one of the previous steps for a OPC UA client to connect to a server is to negotiate the maximum size of the buffers, the message total size and the max number or chunks a message can be sent, this process is called by the OPC UA documentation as "Handshake".
If your request is too long it should be split and sent in several chunks according to the limits previously negotiated with the server.
And the server will probably also reply in several chunks, all that has to be considered in the programming of an OPC UA client.

OPC UA Client capture the lost item values from the UA server after a disconnect/connection error?

I am building a OPC UA Client using OPC Foundation SDK. I am able to create a subscription containing some Monitoreditems.
On the OPC UA server these monitored items change value constantly (every second or so).
I want to disconnect the client (simulate a connection broken ), keep the subcription alive and wait for a while. Then I reconnect having my subscriptions back, but I also want all the monitored Item values queued up during the disconnect. Right now I only get the last server value on reconnect.
I am setting a queuesize:
monitoredItem.QueueSize = 100;
To kind of simulate a connection error I have set the "delete subscription" to false on ClosesSession:
m_session.CloseSession(new RequestHeader(), false);
My question is how to capture the content of the queue after a disconnect/connection error???
Should the ‘lost values’ be “new MonitoredItem_Notification” automatically when the client reconnect?
Should the SubscriptionId be the same as before the connection was broken?
Should the sessionId be the same or will a new SessionId let med keep the existing subscriptions? What is the best way to simulate a connection error?
Many questions :-)
A sample from the code where I create the subscription containing some MonitoredItems and the MonitoredItem_Notification event method.
Any OPC UA Guru out there??
if (node.Displayname == "node to monitor")
{
MonitoredItem mon = CreateMonitoredItem((NodeId)node.reference.NodeId, node.Displayname);
m_subscription.AddItem(mon);
m_subscription.ApplyChanges();
}
private MonitoredItem CreateMonitoredItem(NodeId nodeId, string displayName)
{
if (m_subscription == null)
{
m_subscription = new Subscription(m_session.DefaultSubscription);
m_subscription.PublishingEnabled = true;
m_subscription.PublishingInterval = 3000;//1000;
m_subscription.KeepAliveCount = 10;
m_subscription.LifetimeCount = 10;
m_subscription.MaxNotificationsPerPublish = 1000;
m_subscription.Priority = 100;
bool cache = m_subscription.DisableMonitoredItemCache;
m_session.AddSubscription(m_subscription);
m_subscription.Create();
}
// add the new monitored item.
MonitoredItem monitoredItem = new MonitoredItem(m_subscription.DefaultItem);
//Each time a monitored item is sampled, the server evaluates the sample using a filter defined for each monitoreditem.
//The server uses the filter to determine if the sample should be reported. The type of filter is dependent on the type of item.
//DataChangeFilter for Variable, Eventfilter when monitoring Events. etc
//MonitoringFilter f = new MonitoringFilter();
//DataChangeFilter f = new DataChangeFilter();
//f.DeadbandValue
monitoredItem.StartNodeId = nodeId;
monitoredItem.AttributeId = Attributes.Value;
monitoredItem.DisplayName = displayName;
//Disabled, Sampling, (Report (includes sampling))
monitoredItem.MonitoringMode = MonitoringMode.Reporting;
//How often the Client wish to check for new values on the server. Must be 0 if item is an event.
//If a negative number the SamplingInterval is set equal to the PublishingInterval (inherited)
//The Subscriptions KeepAliveCount should always be longer than the SamplingInterval/PublishingInterval
monitoredItem.SamplingInterval = 500;
//Number of samples stored on the server between each reporting
monitoredItem.QueueSize = 100;
monitoredItem.DiscardOldest = true;//Discard oldest values when full
monitoredItem.CacheQueueSize = 100;
monitoredItem.Notification += m_MonitoredItem_Notification;
if (ServiceResult.IsBad(monitoredItem.Status.Error))
{
return null;
}
return monitoredItem;
}
private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new MonitoredItemNotificationEventHandler(MonitoredItem_Notification), monitoredItem, e);
return;
}
try
{
if (m_session == null)
{
return;
}
MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
if (notification == null)
{
return;
}
string sess = m_session.SessionId.Identifier.ToString();
string s = string.Format(" MonitoredItem: {0}\t Value: {1}\t Status: {2}\t SourceTimeStamp: {3}", monitoredItem.DisplayName, (notification.Value.WrappedValue.ToString().Length == 1) ? notification.Value.WrappedValue.ToString() : notification.Value.WrappedValue.ToString(), notification.Value.StatusCode.ToString(), notification.Value.SourceTimestamp.ToLocalTime().ToString("HH:mm:ss.fff"));
richTextBox1.AppendText(s + "SessionId: " + sess);
}
catch (Exception exception)
{
ClientUtils.HandleException(this.Text, exception);
}
}e here
I don't know how much of this, if any, the SDK you're using does for you, but the approach when reconnecting is generally:
try to resume (re-activate) your old session. If this is successful your subscriptions will already exist and all you need to do is send more PublishRequests. Since you're trying to test by closing the session this probably won't work.
create a new session and then call the TransferSubscription service to transfer the previous subscriptions to your new session. You can then start sending PublishRequests and you'll get the queued notifications.
Again, depending on the stack/SDK/toolkit you're using some or none of this may be handled for you.

Esp8266 webserver restart after IP changed

I'm trying to access a webserver after I've changed IP addresses on my ESP8266 arduino sketch, but it's not responding (but a ping is).
The sketch starts in WIFI_AP mode (for device testing purposes), but if a network is discovered after an asynchronous WiFi scan, the call back code disconnects the WiFi and then creates a connection in WIFI_AP_STA mode (acting as a secondary network access point).
ESP8266WebServer webServer(80);
void myWifiScanCompleted( int networksFound ) {
IPAddress ip(192,168,1,2); // fixed IP address of this access point
IPAddress gateway(192,168,1,254);
IPAddress subnet(255,255,255,0);
int count;
int quality;
networkConnection = NET_CONNECT_NONE; // initialise as not found
for(count = 0; count < networksFound; count++) {
quality = getRSSIasQuality( WiFi.RSSI(count) );
if(strcmp(WiFi.SSID(count).c_str(), ap_ssid ) == 0) {
debug_print("found network %s (%d\%)", ap_ssid, quality);
networkConnection = NET_CONNECT_FOUND;
break;
} else {
debug_print("ignoring network %s (%d\%)", WiFi.SSID(count).c_str(), quality);
}
}
if(networkConnection == NET_CONNECT_FOUND) { // found network = try to connect
// reset any existing connection
webServer.stop();
WiFi.disconnect();
delay(100);
WiFi.mode(WIFI_AP_STA); // need both modes to join the network to get IP address, and then create access point
WiFi.begin(ap_ssid, ap_password);
count = 0;
while (WiFi.status() != WL_CONNECTED) {
toggle_led(); // Blink the LED
if(++count > (15 * 10)) {
debug_print("error connecting to network %s", ap_ssid);
networkConnection = NET_CONNECT_NONE;
break; // failed to connect
}
delay(100);
}
if(networkConnection != NET_CONNECT_NONE) {
networkConnection = NET_CONNECT_CONNECTED; // we've successfully connected to the network, now set up the access point
ip = WiFi.localIP();
gateway = (uint32_t)0;
kev_softAPConfig(ip, gateway, subnet, false); // no DHCP server
if(! kev_softAP(ap_ssid, ap_password, MY_WIFI_CHANNEL, MY_WIFI_HIDDEN, MY_WIFI_MAX_CONNECTIONS)) {
debug_print("create %s softAP failed - performing network reset", ap_ssid);
WiFi.disconnect();
networkConnection = NET_CONNECT_NONE;
} else {
// access point started = restart server
webServer.begin(); // start web server on the new IP address
debug_print("create %s softAP OK", ap_ssid);
}
}
}
}
The functions kev_softAPConfig() and kev_softAP() are modified versions of the API that do NOT start the dhcp service (which already exists on the network).
I've proved these functions work, I've just changed the order of things so that I can provide device testing features.
Has anyone achieved this, or am I a pioneer? :-)
I found that I had to use a different IP range for the access point from the WiFi.localIP() allocated to the station by the network DHCP server, which meant having to write some proxy code to communicate across networks.

Arduino: turn on and off ethernet server availability

I need to setup an ethernet (web) server that have to be turned on and off depending on some conditions on the Arduino UNO.
I read the docs of the Server class in the Ethernet library and it seems there is no chance to stop the server once you started, i.e. there is no EthernetServer.begin() counterpart.
I thought then to setup the server in the setup section and serve incoming connections depending on when the given condition:
EthernetServer server = EthernetServer(80);
void setup() {
Ethernet.begin(mac, ip);
server.begin();
}
void loop() {
if (condition) {
EthernetClient client = server.available();
if (client == true) {
// serve the client...
}
} else {
// do something else
}
}
This indeed works, but the client is not properly rejected: it is just leaved pending. In the browser one can see the web page loading idefinitely, and if the condition turns to true the client will eventually be served for the request issued when the condition was false.
I see no methods for rejecting the request (there is no counterpart of EthernetServer.available()). The only thing that comes to my mind is to perform a
server.available().stop();
in the beginning of the else block. This prevent to serve requests issued while the condition was false, but doesn't prevent the connection between the client and the server to take place (it's like opening a connection and shut it down immediately).
How could I avoid to establish connections at all while the condition is false?
I'm guessing here since I don't have my Arduino collection handy, but from memory and reading the reference you could try something like
void loop()
{
EthernetClient client = server.available();
if ( !condition )
{
client.stop(); // break connection and do something else
}
else
{
// serve the client...
}
}
Hope that may at least help you in the right direction.
Cheers,
Could you just return a 404 header when you want the server disabled?
if(!condition)
{
client.println("HTTP/1.1 404 OK");
client.println("Content-Type: text/html");
client.println("Connnection: close");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html><body>404</body></html>");
}
else
{
// serve client
}
I am writing this answer here as it is the only post still active or that hasn't been closed regarding this topic. Despite countless researches regarding being able to switch the EthernetServer on or off at will, this is not possible. The only thing you can do is use some functions defined "public" in the classes of the Ethernet/W5100/W5200/W5500 libraries.
The features I've noticed that actually impact the reliability of the network card are:
#include <Ethernet.h>
#include <utility/w5100.h>
W5100.setRetransmissionTime(milliseconds);
W5100.setRetransmissionCount(number);
(helps to shorten waiting times in case of Wiznet W5100/W5200/W5500 network card timeout)
EthernetClient::setConnectionTimeout(CONNECTION_TIMEOUT);
EthernetClient::setTimeout(CONNECTION_INPUT_STREAMING_TIMEOUT);
(they help to shorten waiting times in case of timeout of the client connected to the EthernetServer)
More tips:
when EthernetServer::available() returns false consider using EthernetServer::flush() to flush server buffers;
when using EthernetClient::write() also use EthernetClient::flush() to ensure that all data has been sent;
use EthernetClient::close() on dead/useless clients to free sockets easely.
Consider implementing a function to force-close network sockets, using the following code:
#include <SPI.h>
#include <utility/w5100.h>
void closeAllSockets()
{
for (int i = 0; i < MAX_SOCK_NUM; i++)
{
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
W5100.execCmdSn(i, Sock_CLOSE);
SPI.endTransaction();
}
}
void printAllSockets()
{
for (int i = 0; i < MAX_SOCK_NUM; i++)
{
Serial.print(F("Socket #"));
Serial.print(i);
uint8_ts = W5100.readSnSR(i);
Serial.print(F(": 0x"));
Serial.print(s, 16);
Serial.print(F(" "));
Serial.print(W5100.readSnPORT(i));
Serial.print(F(" D:"));
uint8_t dip[4];
W5100.readSnDIPR(i, dip);
for (int j = 0; j < 4; j++)
{
Serial.print(dip[j], 10);
if (j < 3)
Serial.print(".");
}
Serial.print(F("("));
Serial.print(W5100.readSnDPORT(i));
Serial.println(F(")"));
}
}
MAX_SOCK_NUM changes according to the network card, the Wiznet W5100 has a maximum of 4 sockets, the W5200 and W5500 has a maximum of 8 sockets.
Hope this helps someone.