Here I learnt that the subscription parameters (MaxKeepAliveCount
and LifetimeCount
mainly) should be selected keeping in mind the ratio of change of the monitored variables.
In my scenario I have 4 nodes to monitor. One of them changes quite fast (< 1 s), the others change very rarely (> 1 hour). I have 20+ PLCs all the same.
My code is heavily based on the QtOpcUaViewer example. Here the code I use to enable a subscription:
void MyOpcUa::enableMonitoring()
{
QMapIterator<QString, Node_t> i(_mapOpcNodes);
while (i.hasNext())
{
i.next();
Node_t node = i.value();
if (node.enableRead)
{
QOpcUaMonitoringParameters params(POLLING_INTERVAL); // tested between 100 and 10000 ms
//params.setMaxKeepAliveCount(1000); // tested between 100 and 100000
//params.setLifetimeCount(3000); // MaxKeepAliveCount * 3
//params.setPriority(0);
bool ret = node.node->enableMonitoring(QOpcUa::NodeAttribute::Value, params);
}
}
}
I wrapped the whole logic inside a class (MyOpcUa
):
#include <QObject>
#include <QOpcUaProvider>
#include <QOpcUaClient>
#include <QOpcUaNode>
class MyOpcUa : public QObject
{
Q_OBJECT
public:
typedef struct
{
QOpcUaNode *node;
QVariant value;
bool updated;
bool enableRead;
} Node_t;
explicit MyOpcUa(QObject *parent = nullptr);
~MyOpcUa();
void setConfiguration(QOpcUaPkiConfiguration *pkiConfig);
void discoverHost(QUrl url);
bool discoverComplete() { return _endpointList.size(); }
bool isConnected() { return _clientConnected; }
bool isError() { return _clientError; }
void reconnect() { connectToServer(0); }
void clearNodes();
void insertNode(QString key, QString id, bool enableRead);
bool writeNodeValue(QString key, QVariant value, QOpcUa::Types type);
void enableMonitoring();
void disableMonitoring();
private:
QOpcUaProvider *_opcUaProvider;
QOpcUaClient *_opcUaClient = nullptr;
QList<QOpcUaEndpointDescription> _endpointList;
QOpcUaApplicationIdentity _identity;
QOpcUaPkiConfiguration *_pkiConfig;
QOpcUaEndpointDescription _endpoint;
QMap<QString, Node_t> _mapOpcNodes;
void createClient();
private slots:
void connectToServer(int idxEndpoint);
void findServersComplete(const QList<QOpcUaApplicationDescription> &servers, QOpcUa::UaStatusCode statusCode);
void getEndpoints(QString server);
void getEndpointsComplete(const QList<QOpcUaEndpointDescription> &endpoints, QOpcUa::UaStatusCode statusCode);
void clientConnected();
void clientDisconnected();
void namespacesArrayUpdated(const QStringList &namespaceArray);
void clientError(QOpcUaClient::ClientError);
void clientState(QOpcUaClient::ClientState);
void connectError(QOpcUaErrorState *errorState);
void handleAttributes();
};
so I can create an instance for each connection. The behavior I experience seems really odd to me:
if I have only one connection I can subscribe to my nodes with the default parameters and all works as expected. I get the notifications and every now and then the channel is renewed
if I have more than one connection, no matter the subscription parameters every few minutes I get these errors:
[20231003 10:37:23 I] unknown:0 - "The ServiceResult has the StatusCode BadTimeout"
[20231003 10:37:23 W] unknown:0 - "Received Timeout for Publish Response"
[20231003 10:37:23 I] unknown:0 - "The ServiceResult has the StatusCode BadNoSubscription"
[20231003 10:37:38 I] unknown:0 - "Client Status: ChannelState: Closed, SessionState: Closed, ConnectStatus: Good"
and the connections are closed. This happens to all the connections but not at the same time. I connect to all the PLCs on startup (almost at the same time) but the errors above happens randomly among all the machines.
if I have more than one connection, but with no subscriptions the connections are kept open and the secure channels are renewed.
I also tried to subscribe to only one node: no matter if it changes every second or every hour the behavior is exactly the same, regardless the
QOpcUaMonitoringParameters
I'm having a hard time to understand what's happening here. Do you see any flaw in my approach?