Skip to content

Commit

Permalink
refactor(server): Remove duplicate data source definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
jpfr committed Jan 13, 2024
1 parent 767ba16 commit 867fdf1
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 341 deletions.
214 changes: 113 additions & 101 deletions include/open62541/plugin/nodestore.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ UA_NodePointer_toNodeId(UA_NodePointer np);
/**
* Base Node Attributes
* --------------------
*
* Nodes contain attributes according to their node type. The base node
* attributes are common to all node types. In the OPC UA :ref:`services`,
* attributes are referred to via the :ref:`nodeid` of the containing node and
Expand Down Expand Up @@ -334,7 +333,6 @@ typedef struct UA_ReferenceTargetTreeElem {
} nameTreeEntry;
} UA_ReferenceTargetTreeElem;


/* List of reference targets with the same reference type and direction. Uses
* either an array or a tree structure. The SDK will not change the type of
* reference target structure internally. The nodestore implementations may
Expand Down Expand Up @@ -421,52 +419,107 @@ struct UA_NodeHead {
};

/**
* VariableNode
* ------------ */
* Value Attribute Handling
* ------------------------
* Variable and VariableType store a value attribute. The value is a
* :ref:`datavalue` structure. It can store any Variant (with scalar or array
* data) with an additional StatusCode and timestamps.
*
* The value attribute is stored in one of three ways:
*
* 1. Internal Value
* 2. External Value
* 3. Data Source Callbacks */

/* Indicates whether a variable contains data inline or whether it points to an
* external data source */
typedef enum {
UA_VALUESOURCE_DATA,
UA_VALUESOURCE_DATASOURCE
} UA_ValueSource;
/**
* Internal
* ~~~~~~~~
* Data is stored in a :ref:`datavalue` inside the Variable or VariableType. In
* addition, callbacks can be attached to notify the application when a variable
* is read or written. */

typedef struct {
/* Called before the value attribute is read. It is possible to write into the
* value attribute during onRead (using the write service). The node is
* re-opened afterwards so that changes are considered in the following read
* operation.
*
* @param handle Points to user-provided data for the callback.
* @param nodeid The identifier of the node.
* @param data Points to the current node value.
* @param range Points to the numeric range the client wants to read from
* (or NULL). */
* operation. */
void (*onRead)(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeid, void *nodeContext,
const UA_NumericRange *range, const UA_DataValue *value);

/* Called after writing the value attribute. The node is re-opened after
* writing so that the new value is visible in the callback.
*
* @param server The server executing the callback
* @sessionId The identifier of the session
* @sessionContext Additional data attached to the session
* in the access control layer
* @param nodeid The identifier of the node.
* @param nodeUserContext Additional data attached to the node by
* the user.
* @param nodeConstructorContext Additional data attached to the node
* by the type constructor(s).
* @param range Points to the numeric range the client wants to write to (or
* NULL). */
* writing so that the new value is visible in the callback. */
void (*onWrite)(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, void *nodeContext,
const UA_NumericRange *range, const UA_DataValue *data);
} UA_ValueCallback;

/**
* External
* ~~~~~~~~
* For the external value source the Variable / VariableType contains a
* double-pointer to a ``UA_DataType`` value. The double-pointer approach is
* used for decoupling of the server thread from an external management of value
* updates.
*
* Reading is done by resolving the double-pointer and accessing the data. An
* atomic operation can be used to update the "intermediate pointer" to the
* eventual ``UA_DataType``. That enables lock-free updates. Note the server
* might hold to a pointer for a short while. Cleanup of old values should be
* delayed until the next iteration of the server EventLoop.
*
* Writing an external value source (from within the server) must go through the
* indirection of the ``externalWrite`` callback. The server never writes
* directly to the externalValue double-pointer. */

typedef struct {
/* Called before the value attribute is read. So the externalValue can be
* updated within the onRead callback. The onExternalRead callback is
* omitted if it is NULL. */
void (*onExternalRead)(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeid, void *nodeContext,
const UA_NumericRange *range,
const UA_DataValue **externalValue);

/* The only way to write into an external value from within the server is by
* the write callback. If the write callback is NULL, then writing is not
* possible.
*
* @param server The server executing the callback
* @param sessionId The identifier of the session
* @param sessionContext Additional data attached to the session in the
* access control layer
* @param nodeId The identifier of the node being written to
* @param nodeContext Additional data attached to the node by the user
* @param range If not NULL, then the datasource shall return only a
* selection of the (nonscalar) data. Set
* UA_STATUSCODE_BADINDEXRANGEINVALID in the value if this does not
* apply
* @param value The (non-NULL) DataValue that has been written by the client.
* The data source contains the written data, the result status and
* optionally a sourcetimestamp
* @return Returns a statuscode for logging. Error codes intended for the
* original caller are set in the value. If an error is returned,
* then no releasing of the value is done */
UA_StatusCode
(*externalWrite)(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, void *nodeContext,
const UA_NumericRange *range, const UA_DataValue *data);
} UA_ExternalValueCallback;

/**
* .. _node-datasource:
*
* Data Source
* ~~~~~~~~~~~
* A data source is used to make data accessible to the server via callback
* methods only. For now these are blocking calls, so it is not possible to wait
* for slow sensors within a read callback. */

typedef struct {
/* Copies the data from the source into the provided value.
*
Expand Down Expand Up @@ -497,10 +550,9 @@ typedef struct {
* @param value The (non-null) DataValue that is returned to the client. The
* data source sets the read data, the result status and optionally a
* sourcetimestamp.
* @return Returns a status code for logging. Error codes intended for the
* @return Returns a statuscode for logging. Error codes intended for the
* original caller are set in the value. If an error is returned,
* then no releasing of the value is done
*/
* then no releasing of the value is done */
UA_StatusCode (*read)(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, UA_Boolean includeSourceTimeStamp,
Expand All @@ -522,73 +574,42 @@ typedef struct {
* @param value The (non-NULL) DataValue that has been written by the client.
* The data source contains the written data, the result status and
* optionally a sourcetimestamp
* @return Returns a status code for logging. Error codes intended for the
* @return Returns a statuscode for logging. Error codes intended for the
* original caller are set in the value. If an error is returned,
* then no releasing of the value is done
*/
* then no releasing of the value is done */
UA_StatusCode (*write)(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *value);
} UA_DataSource;

/**
* .. _value-callback:
*
* Value Callback
* ~~~~~~~~~~~~~~
* Value Callbacks can be attached to variable and variable type nodes. If
* not ``NULL``, they are called before reading and after writing respectively. */
typedef struct {
/* Called before the value attribute is read. The external value source can be
* be updated and/or locked during this notification call. After this function returns
* to the core, the external value source is readed immediately.
*/
UA_StatusCode (*notificationRead)(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeid,
void *nodeContext, const UA_NumericRange *range);
typedef enum {
UA_VALUESOURCE_INTERNAL = 0,
UA_VALUESOURCE_EXTERNAL,
UA_VALUESOURCE_DATASOURCE
} UA_ValueSource;

/* Called after writing the value attribute. The node is re-opened after
* writing so that the new value is visible in the callback.
*
* @param server The server executing the callback
* @sessionId The identifier of the session
* @sessionContext Additional data attached to the session
* in the access control layer
* @param nodeid The identifier of the node.
* @param nodeUserContext Additional data attached to the node by
* the user.
* @param nodeConstructorContext Additional data attached to the node
* by the type constructor(s).
* @param range Points to the numeric range the client wants to write to (or
* NULL). */
UA_StatusCode (*userWrite)(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data);
} UA_ExternalValueCallback;
#define UA_VALUESOURCE_DATA 0 /* deprecated */

typedef enum {
UA_VALUEBACKENDTYPE_NONE,
UA_VALUEBACKENDTYPE_INTERNAL,
UA_VALUEBACKENDTYPE_DATA_SOURCE_CALLBACK,
UA_VALUEBACKENDTYPE_EXTERNAL
} UA_ValueBackendType;
#define UA_NODE_VALUE \
UA_ValueSource valueSource; \
union { \
struct { \
UA_DataValue value; \
UA_ValueCallback valueCallback; \
} internal; \
struct { \
UA_DataValue **value; \
UA_ExternalValueCallback valueCallback; \
} external; \
UA_DataSource dataSource; \
} value;

typedef struct {
UA_ValueBackendType backendType;
union {
struct {
UA_DataValue value;
UA_ValueCallback callback;
} internal;
UA_DataSource dataSource;
struct {
UA_DataValue **value;
UA_ExternalValueCallback callback;
} external;
} backend;
} UA_ValueBackend;
/**
* .. _variable-node:
*
* VariableNode
* ------------ */

#define UA_NODE_VARIABLEATTRIBUTES \
/* Constraints on possible values */ \
Expand All @@ -597,17 +618,8 @@ typedef struct {
size_t arrayDimensionsSize; \
UA_UInt32 *arrayDimensions; \
\
UA_ValueBackend valueBackend; \
\
/* The current value */ \
UA_ValueSource valueSource; \
union { \
struct { \
UA_DataValue value; \
UA_ValueCallback callback; \
} data; \
UA_DataSource dataSource; \
} value;
/* Value attribute */ \
UA_NODE_VALUE

typedef struct {
UA_NodeHead head;
Expand Down
44 changes: 28 additions & 16 deletions include/open62541/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -1116,33 +1116,45 @@ UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_setNodeContext(UA_Server *server, UA_NodeId nodeId,
void *nodeContext);

/**
* Value Callback
* ^^^^^^^^^^^^^^
* The value callback is used to notify before-read / after-write of the
* variable value to the userland. This sets the node to have an "internal"
* value attribute */

UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_setVariableNode_valueCallback(UA_Server *server, const UA_NodeId nodeId,
const UA_ValueCallback callback);

/**
* .. _datasource:
*
* Data Source Callback
* ^^^^^^^^^^^^^^^^^^^^
* The server has a unique way of dealing with the content of variables. Instead
* of storing a variant attached to the variable node, the node can point to a
* function with a local data provider. Whenever the value attribute is read,
* the function will be called and asked to provide a UA_DataValue return value
* that contains the value content and additional timestamps.
* Instead of storing a variant attached to the variable node, the node can
* point to a function with a local data provider. Whenever the value attribute
* is read, the function will be called and asked to provide a UA_DataValue
* return value that contains the value content and additional timestamps.
*
* It is expected that the read callback is implemented. The write callback can
* be set to a null-pointer. */
* See the Chapter :ref:`node-datasource` for the definition of an external data
* source. It is expected that the read callback is implemented. The write
* callback can be set to a null-pointer. */

UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
const UA_DataSource dataSource);

UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_setVariableNode_valueCallback(UA_Server *server,
const UA_NodeId nodeId,
const UA_ValueCallback callback);

UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_setVariableNode_valueBackend(UA_Server *server,
const UA_NodeId nodeId,
const UA_ValueBackend valueBackend);
/**
* External Value
* ^^^^^^^^^^^^^^
* The external value allows asynchronous updates to the value without locking
* the server. */

UA_StatusCode
UA_Server_setVariableNode_externalValue(UA_Server *server, const UA_NodeId nodeId,
UA_DataValue **value,
UA_ExternalValueCallback valueCallback);

/**
* .. _local-monitoreditems:
Expand Down
2 changes: 1 addition & 1 deletion src/pubsub/ua_pubsub_dataset.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ UA_PubSubDataSetField_sampleValue(UA_Server *server, UA_DataSetField *field,
if(field->config.field.variable.rtValueSource.rtInformationModelNode) {
const UA_VariableNode *rtNode = (const UA_VariableNode *)
UA_NODESTORE_GET(server, &params->publishedVariable);
*value = **rtNode->valueBackend.backend.external.value;
*value = **rtNode->value.external.value;
value->value.storageType = UA_VARIANT_DATA_NODELETE;
UA_NODESTORE_RELEASE(server, (const UA_Node *) rtNode);
} else if(field->config.field.variable.rtValueSource.rtFieldSourceEnabled == false){
Expand Down
4 changes: 2 additions & 2 deletions src/pubsub/ua_pubsub_readergroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ UA_ReaderGroup_freezeConfiguration(UA_Server *server, UA_ReaderGroup *rg) {
const UA_VariableNode *rtNode = (const UA_VariableNode *)
UA_NODESTORE_GET(server, &tv->targetVariable.targetNodeId);
if(!rtNode ||
rtNode->valueBackend.backendType != UA_VALUEBACKENDTYPE_EXTERNAL) {
rtNode->valueSource != UA_VALUESOURCE_EXTERNAL) {
UA_LOG_WARNING_READER(server->config.logging, dsr,
"PubSub-RT configuration fail: PDS contains field "
"without external data source.");
Expand All @@ -519,7 +519,7 @@ UA_ReaderGroup_freezeConfiguration(UA_Server *server, UA_ReaderGroup *rg) {
}

/* Set the external data source in the tv */
tv->externalDataValue = rtNode->valueBackend.backend.external.value;
tv->externalDataValue = rtNode->value.external.value;

UA_NODESTORE_RELEASE(server, (const UA_Node *) rtNode);

Expand Down
2 changes: 1 addition & 1 deletion src/pubsub/ua_pubsub_writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ UA_DataSetWriter_prepareDataSet(UA_Server *server, UA_DataSetWriter *dsw,
const UA_VariableNode *rtNode = (const UA_VariableNode*)
UA_NODESTORE_GET(server, publishedVariable);
if(rtNode != NULL &&
rtNode->valueBackend.backendType != UA_VALUEBACKENDTYPE_EXTERNAL) {
rtNode->valueSource != UA_VALUESOURCE_EXTERNAL) {
UA_LOG_WARNING_WRITER(server->config.logging, dsw,
"PubSub-RT configuration fail: "
"PDS contains field without external data source");
Expand Down
Loading

0 comments on commit 867fdf1

Please sign in to comment.