.. _client: Client ====== The client implementation allows remote access to all OPC UA services. For convenience, some functionality has been wrapped in :ref:`high-level abstractions `. **However**: At this time, the client does not yet contain its own thread or event-driven main-loop, meaning that the client will not perform any actions automatically in the background. This is especially relevant for connection/session management and subscriptions. The user will have to periodically call `UA_Client_run_iterate` to ensure that asynchronous events are handled, including keeping a secure connection established. See more about :ref:`asynchronicity` and :ref:`subscriptions`. .. _client-config: Client Configuration -------------------- The client configuration is used for setting connection parameters and additional settings used by the client. The configuration should not be modified after it is passed to a client. Currently, only one client can use a configuration at a time. Examples for configurations are provided in the ``/plugins`` folder. The usual usage is as follows: 1. Create a client configuration with default settings as a starting point 2. Modifiy the configuration, e.g. modifying the timeout 3. Instantiate a client with it 4. After shutdown of the client, clean up the configuration (free memory) The :ref:`tutorials` provide a good starting point for this. .. code-block:: c typedef struct { /* Basic client configuration */ void *clientContext; /* User-defined data attached to the client */ UA_Logger logger; /* Logger used by the client */ UA_UInt32 timeout; /* Response timeout in ms */ /* The description must be internally consistent. * - The ApplicationUri set in the ApplicationDescription must match the * URI set in the server certificate */ UA_ApplicationDescription clientDescription; /* Basic connection configuration */ UA_ExtensionObject userIdentityToken; /* Configured User-Identity Token */ UA_MessageSecurityMode securityMode; /* None, Sign, SignAndEncrypt. The * default is invalid. This indicates * the client to select any matching * endpoint. */ UA_String securityPolicyUri; /* SecurityPolicy for the SecureChannel. An * empty string indicates the client to select * any matching SecurityPolicy. */ /* Advanced connection configuration * * If either endpoint or userTokenPolicy has been set (at least one non-zero * byte in either structure), then the selected Endpoint and UserTokenPolicy * overwrite the settings in the basic connection configuration. The * userTokenPolicy array in the EndpointDescription is ignored. The selected * userTokenPolicy is set in the dedicated configuration field. * * If the advanced configuration is not set, the client will write to it the * selected Endpoint and UserTokenPolicy during GetEndpoints. * * The information in the advanced configuration is used during reconnect * when the SecureChannel was broken. */ UA_EndpointDescription endpoint; UA_UserTokenPolicy userTokenPolicy; /* Advanced client configuration */ UA_UInt32 secureChannelLifeTime; /* Lifetime in ms (then the channel needs to be renewed) */ UA_UInt32 requestedSessionTimeout; /* Session timeout in ms */ UA_ConnectionConfig localConnectionConfig; UA_UInt32 connectivityCheckInterval; /* Connectivity check interval in ms. * 0 = background task disabled */ const UA_DataTypeArray *customDataTypes; /* Custom DataTypes. Attention! * Custom datatypes are not cleaned * up together with the * configuration. So it is possible * to allocate them on ROM. */ /* Available SecurityPolicies */ size_t securityPoliciesSize; UA_SecurityPolicy *securityPolicies; /* Certificate Verification Plugin */ UA_CertificateVerification certificateVerification; /* Callbacks for async connection handshakes */ UA_ConnectClientConnection initConnectionFunc; UA_StatusCode (*pollConnectionFunc)(UA_Connection *connection, UA_UInt32 timeout, const UA_Logger *logger); /* Callback for state changes. The client state is differentated into the * SecureChannel state and the Session state. The connectStatus is set if * the client connection (including reconnects) has failed and the client * has to "give up". If the connectStatus is not set, the client still has * hope to connect or recover. */ void (*stateCallback)(UA_Client *client, UA_SecureChannelState channelState, UA_SessionState sessionState, UA_StatusCode connectStatus); /* When connectivityCheckInterval is greater than 0, every * connectivityCheckInterval (in ms), a async read request is performed on * the server. inactivityCallback is called when the client receive no * response for this read request The connection can be closed, this in an * attempt to recreate a healthy connection. */ void (*inactivityCallback)(UA_Client *client); #ifdef UA_ENABLE_SUBSCRIPTIONS /* Number of PublishResponse queued up in the server */ UA_UInt16 outStandingPublishRequests; /* If the client does not receive a PublishResponse after the defined delay * of ``(sub->publishingInterval * sub->maxKeepAliveCount) + * client->config.timeout)``, then subscriptionInactivityCallback is called * for the subscription.. */ void (*subscriptionInactivityCallback)(UA_Client *client, UA_UInt32 subscriptionId, void *subContext); #endif } UA_ClientConfig; Client Lifecycle ---------------- .. code-block:: c /* The method UA_Client_new is defined in client_config_default.h. So default * plugins outside of the core library (for logging, etc) are already available * during the initialization. * * UA_Client * UA_Client_new(void); */ /* Creates a new client. Moves the config into the client with a shallow copy. * The config content is cleared together with the client. */ UA_Client * UA_Client_newWithConfig(const UA_ClientConfig *config); /* Returns the current state. All arguments except ``client`` can be NULL. */ void UA_Client_getState(UA_Client *client, UA_SecureChannelState *channelState, UA_SessionState *sessionState, UA_StatusCode *connectStatus); /* Get the client configuration */ UA_ClientConfig * UA_Client_getConfig(UA_Client *client); /* Get the client context */ static UA_INLINE void * UA_Client_getContext(UA_Client *client) { return UA_Client_getConfig(client)->clientContext; /* Cannot fail */ } /* (Disconnect and) delete the client */ void UA_Client_delete(UA_Client *client); Connect to a Server ------------------- Once a client is connected to an endpointUrl, it is not possible to switch to another server. A new client has to be created for that. Once a connection is established, the client keeps the connection open and reconnects if necessary. If the connection fails unrecoverably (state->connectStatus is set to an error), the client is no longer usable. Create a new client if required. .. code-block:: c /* Connect to the server. First a SecureChannel is opened, then a Session. The * client configuration restricts the SecureChannel selection and contains the * UserIdentityToken for the Session. * * @param client to use * @param endpointURL to connect (for example "opc.tcp://localhost:4840") * @return Indicates whether the operation succeeded or returns an error code */ UA_StatusCode UA_Client_connect(UA_Client *client, const char *endpointUrl); /* Connect async (non-blocking) to the server. After initiating the connection, * call UA_Client_run_iterate repeatedly until the connection is fully * established. You can set a callback to client->config.stateCallback to be * notified when the connection status changes. Or use UA_Client_getState to get * the state manually. */ UA_StatusCode UA_Client_connectAsync(UA_Client *client, const char *endpointUrl); /* Connect to the server without creating a session * * @param client to use * @param endpointURL to connect (for example "opc.tcp://localhost:4840") * @return Indicates whether the operation succeeded or returns an error code */ UA_StatusCode UA_Client_connectSecureChannel(UA_Client *client, const char *endpointUrl); /* Connect async (non-blocking) only the SecureChannel */ UA_StatusCode UA_Client_connectSecureChannelAsync(UA_Client *client, const char *endpointUrl); /* Connect to the server and create+activate a Session with the given username * and password. This first set the UserIdentityToken in the client config and * then calls the regular connect method. */ static UA_INLINE UA_StatusCode UA_Client_connectUsername(UA_Client *client, const char *endpointUrl, const char *username, const char *password) { UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new(); if(!identityToken) return UA_STATUSCODE_BADOUTOFMEMORY; identityToken->userName = UA_STRING_ALLOC(username); identityToken->password = UA_STRING_ALLOC(password); UA_ClientConfig *cc = UA_Client_getConfig(client); UA_ExtensionObject_clear(&cc->userIdentityToken); cc->userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED; cc->userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]; cc->userIdentityToken.content.decoded.data = identityToken; return UA_Client_connect(client, endpointUrl); } /* Disconnect and close a connection to the selected server. Disconnection is * always performed async (without blocking). */ UA_StatusCode UA_Client_disconnect(UA_Client *client); /* Disconnect async. Run UA_Client_run_iterate until the callback notifies that * all connections are closed. */ UA_StatusCode UA_Client_disconnectAsync(UA_Client *client); /* Disconnect the SecureChannel but keep the Session intact (if it exists). * This is always an async (non-blocking) operation. */ UA_StatusCode UA_Client_disconnectSecureChannel(UA_Client *client); Discovery --------- .. code-block:: c /* Gets a list of endpoints of a server * * @param client to use. Must be connected to the same endpoint given in * serverUrl or otherwise in disconnected state. * @param serverUrl url to connect (for example "opc.tcp://localhost:4840") * @param endpointDescriptionsSize size of the array of endpoint descriptions * @param endpointDescriptions array of endpoint descriptions that is allocated * by the function (you need to free manually) * @return Indicates whether the operation succeeded or returns an error code */ UA_StatusCode UA_Client_getEndpoints(UA_Client *client, const char *serverUrl, size_t* endpointDescriptionsSize, UA_EndpointDescription** endpointDescriptions); /* Gets a list of all registered servers at the given server. * * You can pass an optional filter for serverUris. If the given server is not registered, * an empty array will be returned. If the server is registered, only that application * description will be returned. * * Additionally you can optionally indicate which locale you want for the server name * in the returned application description. The array indicates the order of preference. * A server may have localized names. * * @param client to use. Must be connected to the same endpoint given in * serverUrl or otherwise in disconnected state. * @param serverUrl url to connect (for example "opc.tcp://localhost:4840") * @param serverUrisSize Optional filter for specific server uris * @param serverUris Optional filter for specific server uris * @param localeIdsSize Optional indication which locale you prefer * @param localeIds Optional indication which locale you prefer * @param registeredServersSize size of returned array, i.e., number of found/registered servers * @param registeredServers array containing found/registered servers * @return Indicates whether the operation succeeded or returns an error code */ UA_StatusCode UA_Client_findServers(UA_Client *client, const char *serverUrl, size_t serverUrisSize, UA_String *serverUris, size_t localeIdsSize, UA_String *localeIds, size_t *registeredServersSize, UA_ApplicationDescription **registeredServers); #ifdef UA_ENABLE_DISCOVERY /* Get a list of all known server in the network. Only supported by LDS servers. * * @param client to use. Must be connected to the same endpoint given in * serverUrl or otherwise in disconnected state. * @param serverUrl url to connect (for example "opc.tcp://localhost:4840") * @param startingRecordId optional. Only return the records with an ID higher * or equal the given. Can be used for pagination to only get a subset of * the full list * @param maxRecordsToReturn optional. Only return this number of records * @param serverCapabilityFilterSize optional. Filter the returned list to only * get servers with given capabilities, e.g. "LDS" * @param serverCapabilityFilter optional. Filter the returned list to only get * servers with given capabilities, e.g. "LDS" * @param serverOnNetworkSize size of returned array, i.e., number of * known/registered servers * @param serverOnNetwork array containing known/registered servers * @return Indicates whether the operation succeeded or returns an error code */ UA_StatusCode UA_Client_findServersOnNetwork(UA_Client *client, const char *serverUrl, UA_UInt32 startingRecordId, UA_UInt32 maxRecordsToReturn, size_t serverCapabilityFilterSize, UA_String *serverCapabilityFilter, size_t *serverOnNetworkSize, UA_ServerOnNetwork **serverOnNetwork); #endif .. _client-services: Services -------- The raw OPC UA services are exposed to the client. But most of them time, it is better to use the convenience functions from ``ua_client_highlevel.h`` that wrap the raw services. .. code-block:: c /* Don't use this function. Use the type versions below instead. */ void __UA_Client_Service(UA_Client *client, const void *request, const UA_DataType *requestType, void *response, const UA_DataType *responseType); /* * Attribute Service Set * ^^^^^^^^^^^^^^^^^^^^^ */ static UA_INLINE UA_ReadResponse UA_Client_Service_read(UA_Client *client, const UA_ReadRequest request) { UA_ReadResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_READREQUEST], &response, &UA_TYPES[UA_TYPES_READRESPONSE]); return response; } static UA_INLINE UA_WriteResponse UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request) { UA_WriteResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_WRITEREQUEST], &response, &UA_TYPES[UA_TYPES_WRITERESPONSE]); return response; } /* * Historical Access Service Set * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ #ifdef UA_ENABLE_HISTORIZING static UA_INLINE UA_HistoryReadResponse UA_Client_Service_historyRead(UA_Client *client, const UA_HistoryReadRequest request) { UA_HistoryReadResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYREADREQUEST], &response, &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE]); return response; } static UA_INLINE UA_HistoryUpdateResponse UA_Client_Service_historyUpdate(UA_Client *client, const UA_HistoryUpdateRequest request) { UA_HistoryUpdateResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYUPDATEREQUEST], &response, &UA_TYPES[UA_TYPES_HISTORYUPDATERESPONSE]); return response; } #endif /* * Method Service Set * ^^^^^^^^^^^^^^^^^^ */ #ifdef UA_ENABLE_METHODCALLS static UA_INLINE UA_CallResponse UA_Client_Service_call(UA_Client *client, const UA_CallRequest request) { UA_CallResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_CALLREQUEST], &response, &UA_TYPES[UA_TYPES_CALLRESPONSE]); return response; } #endif /* * NodeManagement Service Set * ^^^^^^^^^^^^^^^^^^^^^^^^^^ */ static UA_INLINE UA_AddNodesResponse UA_Client_Service_addNodes(UA_Client *client, const UA_AddNodesRequest request) { UA_AddNodesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ADDNODESREQUEST], &response, &UA_TYPES[UA_TYPES_ADDNODESRESPONSE]); return response; } static UA_INLINE UA_AddReferencesResponse UA_Client_Service_addReferences(UA_Client *client, const UA_AddReferencesRequest request) { UA_AddReferencesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_ADDREFERENCESREQUEST], &response, &UA_TYPES[UA_TYPES_ADDREFERENCESRESPONSE]); return response; } static UA_INLINE UA_DeleteNodesResponse UA_Client_Service_deleteNodes(UA_Client *client, const UA_DeleteNodesRequest request) { UA_DeleteNodesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETENODESREQUEST], &response, &UA_TYPES[UA_TYPES_DELETENODESRESPONSE]); return response; } static UA_INLINE UA_DeleteReferencesResponse UA_Client_Service_deleteReferences(UA_Client *client, const UA_DeleteReferencesRequest request) { UA_DeleteReferencesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETEREFERENCESREQUEST], &response, &UA_TYPES[UA_TYPES_DELETEREFERENCESRESPONSE]); return response; } /* * View Service Set * ^^^^^^^^^^^^^^^^ */ static UA_INLINE UA_BrowseResponse UA_Client_Service_browse(UA_Client *client, const UA_BrowseRequest request) { UA_BrowseResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_BROWSEREQUEST], &response, &UA_TYPES[UA_TYPES_BROWSERESPONSE]); return response; } static UA_INLINE UA_BrowseNextResponse UA_Client_Service_browseNext(UA_Client *client, const UA_BrowseNextRequest request) { UA_BrowseNextResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], &response, &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE]); return response; } static UA_INLINE UA_TranslateBrowsePathsToNodeIdsResponse UA_Client_Service_translateBrowsePathsToNodeIds(UA_Client *client, const UA_TranslateBrowsePathsToNodeIdsRequest request) { UA_TranslateBrowsePathsToNodeIdsResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], &response, &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE]); return response; } static UA_INLINE UA_RegisterNodesResponse UA_Client_Service_registerNodes(UA_Client *client, const UA_RegisterNodesRequest request) { UA_RegisterNodesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_REGISTERNODESREQUEST], &response, &UA_TYPES[UA_TYPES_REGISTERNODESRESPONSE]); return response; } static UA_INLINE UA_UnregisterNodesResponse UA_Client_Service_unregisterNodes(UA_Client *client, const UA_UnregisterNodesRequest request) { UA_UnregisterNodesResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_UNREGISTERNODESREQUEST], &response, &UA_TYPES[UA_TYPES_UNREGISTERNODESRESPONSE]); return response; } /* * Query Service Set * ^^^^^^^^^^^^^^^^^ */ #ifdef UA_ENABLE_QUERY static UA_INLINE UA_QueryFirstResponse UA_Client_Service_queryFirst(UA_Client *client, const UA_QueryFirstRequest request) { UA_QueryFirstResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_QUERYFIRSTREQUEST], &response, &UA_TYPES[UA_TYPES_QUERYFIRSTRESPONSE]); return response; } static UA_INLINE UA_QueryNextResponse UA_Client_Service_queryNext(UA_Client *client, const UA_QueryNextRequest request) { UA_QueryNextResponse response; __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_QUERYFIRSTREQUEST], &response, &UA_TYPES[UA_TYPES_QUERYFIRSTRESPONSE]); return response; } #endif .. _client-async-services: Asynchronous Services --------------------- All OPC UA services are asynchronous in nature. So several service calls can be made without waiting for the individual responses. Depending on the server's priorities responses may come in a different ordering than sent. As noted in :ref:`the client overview` currently no means of handling asynchronous events automatically is provided. However, some synchronous function calls will trigger handling, but to ensure this happens a client should periodically call `UA_Client_run_iterate` explicitly. Connection and session management are also performed in `UA_Client_run_iterate`, so to keep a connection healthy any client need to consider how and when it is appropriate to do the call. This is especially true for the periodic renewal of a SecureChannel's SecurityToken which is designed to have a limited lifetime and will invalidate the connection if not renewed. .. code-block:: c /* Use the type versions of this method. See below. However, the general * mechanism of async service calls is explained here. * * We say that an async service call has been dispatched once this method * returns UA_STATUSCODE_GOOD. If there is an error after an async service has * been dispatched, the callback is called with an "empty" response where the * statusCode has been set accordingly. This is also done if the client is * shutting down and the list of dispatched async services is emptied. * * The statusCode received when the client is shutting down is * UA_STATUSCODE_BADSHUTDOWN. * * The statusCode received when the client don't receive response * after specified config->timeout (in ms) is * UA_STATUSCODE_BADTIMEOUT. * * Instead, you can use __UA_Client_AsyncServiceEx to specify * a custom timeout * * The userdata and requestId arguments can be NULL. */ typedef void (*UA_ClientAsyncServiceCallback)(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response); UA_StatusCode __UA_Client_AsyncService(UA_Client *client, const void *request, const UA_DataType *requestType, UA_ClientAsyncServiceCallback callback, const UA_DataType *responseType, void *userdata, UA_UInt32 *requestId); UA_StatusCode UA_Client_sendAsyncRequest(UA_Client *client, const void *request, const UA_DataType *requestType, UA_ClientAsyncServiceCallback callback, const UA_DataType *responseType, void *userdata, UA_UInt32 *requestId); /* Listen on the network and process arriving asynchronous responses in the * background. Internal housekeeping, renewal of SecureChannels and subscription * management is done as well. */ UA_StatusCode UA_Client_run_iterate(UA_Client *client, UA_UInt32 timeout); /* Force the manual renewal of the SecureChannel. This is useful to renew the * SecureChannel during a downtime when no time-critical operations are * performed. This method is asynchronous. The renewal is triggered (the OPN * message is sent) but not completed. The OPN response is handled with * ``UA_Client_run_iterate`` or a synchronous servica-call operation. * * @return The return value is UA_STATUSCODE_GOODCALLAGAIN if the SecureChannel * has not elapsed at least 75% of its lifetime. Otherwise the * ``connectStatus`` is returned. */ UA_StatusCode UA_Client_renewSecureChannel(UA_Client *client); /* Use the type versions of this method. See below. However, the general * mechanism of async service calls is explained here. * * We say that an async service call has been dispatched once this method * returns UA_STATUSCODE_GOOD. If there is an error after an async service has * been dispatched, the callback is called with an "empty" response where the * statusCode has been set accordingly. This is also done if the client is * shutting down and the list of dispatched async services is emptied. * * The statusCode received when the client is shutting down is * UA_STATUSCODE_BADSHUTDOWN. * * The statusCode received when the client don't receive response * after specified timeout (in ms) is * UA_STATUSCODE_BADTIMEOUT. * * The timeout can be disabled by setting timeout to 0 * * The userdata and requestId arguments can be NULL. */ UA_StatusCode __UA_Client_AsyncServiceEx(UA_Client *client, const void *request, const UA_DataType *requestType, UA_ClientAsyncServiceCallback callback, const UA_DataType *responseType, void *userdata, UA_UInt32 *requestId, UA_UInt32 timeout); Timed Callbacks --------------- Repeated callbacks can be attached to a client and will be executed in the defined interval. .. code-block:: c typedef void (*UA_ClientCallback)(UA_Client *client, void *data); /* Add a callback for execution at a specified time. If the indicated time lies * in the past, then the callback is executed at the next iteration of the * server's main loop. * * @param client The client object. * @param callback The callback that shall be added. * @param data Data that is forwarded to the callback. * @param date The timestamp for the execution time. * @param callbackId Set to the identifier of the repeated callback . This can * be used to cancel the callback later on. If the pointer is null, the * identifier is not set. * @return Upon success, UA_STATUSCODE_GOOD is returned. An error code * otherwise. */ UA_StatusCode UA_Client_addTimedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_DateTime date, UA_UInt64 *callbackId); /* Add a callback for cyclic repetition to the client. * * @param client The client object. * @param callback The callback that shall be added. * @param data Data that is forwarded to the callback. * @param interval_ms The callback shall be repeatedly executed with the given * interval (in ms). The interval must be positive. The first execution * occurs at now() + interval at the latest. * @param callbackId Set to the identifier of the repeated callback . This can * be used to cancel the callback later on. If the pointer is null, the * identifier is not set. * @return Upon success, UA_STATUSCODE_GOOD is returned. An error code * otherwise. */ UA_StatusCode UA_Client_addRepeatedCallback(UA_Client *client, UA_ClientCallback callback, void *data, UA_Double interval_ms, UA_UInt64 *callbackId); UA_StatusCode UA_Client_changeRepeatedCallbackInterval(UA_Client *client, UA_UInt64 callbackId, UA_Double interval_ms); void UA_Client_removeCallback(UA_Client *client, UA_UInt64 callbackId); Client Utility Functions ------------------------ .. code-block:: c /* Lookup a datatype by its NodeId. Takes the custom types in the client * configuration into account. Return NULL if none found. */ const UA_DataType * UA_Client_findDataType(UA_Client *client, const UA_NodeId *typeId); .. toctree:: client_highlevel client_subscriptions