I try to get into the Historic Part of the SDK and like to adapt Lesson07 to display more than one variable in the Historic View.
To achieve this I am going through the DemoServer and checked the variable ByteWithHistory and DoubleWithHistory.
Unfortunately there are also methods to Start/Stop Logging and the UaProvider_Demo_SimulateHistoryVariables, so it is quite complicated.
I understand that such an example could show all the possibilities of the SDK, but in my case I just like to start small.
So...,
I created a server with the UAModeler which contains a MeasurementDevice and 6 variables(Voltages, Currents), the server compiles and executes and I can drag&drop the variable to the DefaultDAView or the HistoryTrendView. So the variables have the right AccessLevel. Maybe one more thing, I like to identify the Nodes by Numeric_Id´s.
Now I like to adapt the method uaprovider_name_historyread and uaprovider_name_write.
Up to now I understand that the <uaprovider_name_write> is responsible to write the data to a log file using the datalogger and <uaprovider_name_historyread> is just to receive the data in the Client.
So my questions are:
What is the "concept" of having a UserDataCommon struct?
To explain at which problem I am right now I like to compare the Lesson07 and the Demo next.
In the Interface IFMETHODIMP(CustomProvider_WriteAsync)(UaServer_ProviderWriteContext* a_pWriteCtx) there´s a switch-case where one checks the UserDataTemperature, can I extend the program to check for several variables in here ?
Code: Select all
TemperatureSensor *pSensor = (TemperatureSensor*)pUserData;
*pSensor->pValue = pValue->Value.Double;
Code: Select all
/* Iterate over nodes and check if the provider is responsible for processing them */
for (i = 0; i < pReq->NoOfNodesToWrite; i++)
...
if (pNode)
{
...
OpcUa_Variant *pValue = &a_pWriteCtx->pRequest->NodesToWrite[i].Value.Value;
switch (pUserData->Type)
{
case UserDataTemperature:
{
if (pValue->ArrayType == OpcUa_VariantArrayType_Scalar &&
pValue->Datatype == OpcUaType_Double)
{
TemperatureSensor *pSensor = (TemperatureSensor*)pUserData;
*pSensor->pValue = pValue->Value.Double;
pRes->Results[i] = OpcUa_Good;
}
...
UaServer_WriteInternal(pNode, a_pWriteCtx, i);
...
/* Send callback */
UaServer_WriteComplete(a_pWriteCtx);
In the IFMETHODIMP(UaProvider_Demo_WriteAsync)(UaServer_ProviderWriteContext* a_pWriteCtx) there´s another option, I just read that this is used when methods are not "available" ?
Code: Select all
for (i = 0; i < pReq->NoOfNodesToWrite; i++)
{
...
if (pNode)
{
/* handle special variables, which trigger actions.
* this could better be done with methods, but if you don't have methods (only DA)
* you can use this technique.
*/
if (OpcUa_BaseNode_GetType(pNode) == eVariable
&& pReq->NodesToWrite[i].AttributeId == OpcUa_Attributes_Value)
{
OpcUa_NodeId nodeId;
if (pNodeId->IdentifierType == OpcUa_IdentifierType_String)
{
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_SimulationActive);
if (OpcUa_NodeId_Compare(&nodeId, pNodeId) == 0)
{
/* validate datatype */
if (pValue->Datatype == OpcUaType_Boolean
&& pValue->ArrayType == OpcUa_VariantArrayType_Scalar)
{
/* trigger action */
if (pValue->Value.Boolean)
UaProvider_Demo_StartSimulation();
else
UaProvider_Demo_StopSimulation();
}
...
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_History_DataLoggerActive);
if (OpcUa_NodeId_Compare(&nodeId, pNodeId) == 0)
{
/* validate datatype */
if (pValue->Datatype == OpcUaType_Boolean
&& pValue->ArrayType == OpcUa_VariantArrayType_Scalar)
{
/* trigger action */
if (pValue->Value.Boolean)
UaProvider_Demo_StartLogging();
else
UaProvider_Demo_StopLogging();
}
...
UaServer_WriteInternal(pNode, a_pWriteCtx, i);
}
...
/* send callback */
UaServer_WriteComplete(a_pWriteCtx);
Code: Select all
#ifndef _UAPROVIDER_DEMO_SIMULATION_H_
#define _UAPROVIDER_DEMO_SIMULATION_H_ 1
#include <uaserver_config.h>
#include <uaserver_providers.h>
/** Enum of custom user data types */
enum _UserDataType
{
UserDataTemperature,
UserDataMachine,
UserDataMachineSwitch,
UserDataAnalogItem,
UserDataHistoryDataLogger
};
typedef enum _UserDataType UserDataType;
/** All user data structs contain the same header with type information.
* This concept is application specific and only an example.
* You can store whatever you like in UserData.
*/
struct _UserDataCommon
{
UserDataType Type; /**< currently only the type info is needed in the common header */
};
typedef struct _UserDataCommon UserDataCommon;
struct _HistoryDataLogger
{
/* user data header */
UserDataType Type;
/* user data HistoryDataLogger data */
OpcUa_Int DataLogger;
OpcUa_Int DataLogItem;
};
typedef struct _HistoryDataLogger HistoryDataLogger;
struct _TemperatureSensor
{
/* user data header */
UserDataType Type;
/* user data temperature data */
OpcUa_NodeId NodeId;
OpcUa_NodeId HighAlarmId;
OpcUa_NodeId LowAlarmId;
/* protocol information */
OpcUa_Double *pValue;
};
typedef struct _TemperatureSensor TemperatureSensor;
Code: Select all
struct _Voltage_L1
{
/* user data header */
UserDataType Type;
/* user data temperature data */
OpcUa_NodeId NodeId;
/* protocol information */
OpcUa_Float *pValue;
};
typedef struct _Voltage_L1 Voltage_L1;
Code: Select all
void UaProvider_Demo_SetupDataLogger()
if (g_hDataLogger < 0) return;
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_History_ByteWithHistory);
UaProvider_Demo_HistorizeItem(&nodeId);
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_History_DoubleWithHistory);
UaProvider_Demo_HistorizeItem(&nodeId);
...
UaServer_DataLogger_Start(g_hDataLogger);
...
Code: Select all
OpcUa_StatusCode UaProvider_Demo_StartLogging()
{
OpcUa_NodeId nodeId;
...
*g_pDataLoggerActive = OpcUa_True;
/* enable items to start sampling */
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_History_ByteWithHistory);
UaProvider_Demo_EnableHistorizedItem(&nodeId, OpcUa_True);
UaServer_CreateStringNodeId(&nodeId, Demo_Objects_Demo_History_DoubleWithHistory);
UaProvider_Demo_EnableHistorizedItem(&nodeId, OpcUa_True);
...
}