• open62541开发:添加sqlite3 历史数据库


                

        历史数据库在OPCUA  应用中十分重要,例如OPCUA 网关和OPCUA 汇聚服务器中都需要历史数据库功能。但是open62541 协议栈中仅包含了基于内存的历史数据库,在实际应用中是不够的。本博文讨论open62541 中添加sqlite3 为基础的历史数据库若干问题。

    借鉴

            Github上有一些open62541 添加sqlite 的项目的项目,在CSDN 上

    学习open62541 --- [58] 使用数据库存储历史数据

    的博文中,介绍了如何将sqlite 改成Linux 下的实现。它的项目在:

    GitHub - nicolasr75/open62541_sqlite:  

          他的项目对我帮助很大,但是,它的sqlite 不是加载到open62541 的Plugin 中,而是单独地添加在应用程序的项目中的。 他将c程序和h 文件合在了一起,只有使用sqlite 历史数据时才会被include 并且编译。另一方面,该项目只支持单变量历史数据存储。

          我的项目是将open62541 安装到系统目录中的,所以希望将sqlite 结合到open61541 程序中。另外扩展为多变量历史数据存储。下面记录我的实现过程。

    在open62541 包中加入sqlite3 

    安装sqlite3 库

    sudo apt -y install libsqlite3-dev
     

            将SQLiteBackend.h 放置到open62541 的Plugins/include /plugin/history_data 中。

    重新编译open62541 就可以了。也可以单独地将SQLiteBackend.h放置在应用程序中。

    修改成多节点存储

            在参考文章中只是单变量存储的方法。不支持多个变量的存储。我将它进行了修改:

    方法1:使用NodeId区分不同变量

    将MearsuringPointID 改为NodeId.节点的字符串(ns=1;i=5055)。注意要带单括号(‘)。

    1. strncat(query, "') AND (MeasuringPointID='", QUERY_BUFFER_SIZE);
    2. char measuringPointID[60];
    3. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    4. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);

    方法2 将不同变量的数据构建不同的表(table)

    这种方法的好处是访问数据库效率高。

    在这里,我们仅实现第一种方式。

    源代码(SQLiteBackend.h)

    1. #ifndef BACKEND_H
    2. #define BACKEND_H
    3. #include
    4. #include
    5. #include
    6. #include "sqlite3.h"
    7. static const size_t END_OF_DATA = SIZE_MAX;
    8. static const size_t QUERY_BUFFER_SIZE = 500;
    9. char *UA_String2string(UA_String uaString)
    10. {
    11. char *convert = (char *)UA_malloc(sizeof(char) * uaString.length + 1);
    12. memcpy(convert, uaString.data, uaString.length);
    13. convert[uaString.length] = '\0';
    14. return convert;
    15. }
    16. UA_Int64 convertTimestampStringToUnixSeconds(const char* timestampString)
    17. {
    18. UA_DateTimeStruct dts;
    19. memset(&dts, 0, sizeof(dts));
    20. // printf("convertTimestampStringToUnixSeconds:%s\n",timestampString);
    21. sscanf(timestampString, "%hu-%hu-%hu %hu:%hu:%hu",
    22. &dts.year, &dts.month, &dts.day, &dts.hour, &dts.min, &dts.sec);
    23. // printf("%d %d\n",dts.year, dts.month);
    24. UA_DateTime dt = UA_DateTime_fromStruct(dts);
    25. UA_Int64 t = UA_DateTime_toUnixTime(dt);
    26. // printf("convertTimestampStringToUnixSeconds=%lu\n",t);
    27. return t;
    28. }
    29. const char* convertUnixSecondsToTimestampString(UA_Int64 unixSeconds)
    30. {
    31. static char buffer[20];
    32. // printf("convertUnixSecondsToTimestampString=%lu\n",unixSeconds);
    33. UA_DateTime dt = UA_DateTime_fromUnixTime(unixSeconds);
    34. UA_DateTimeStruct dts = UA_DateTime_toStruct(dt);
    35. struct tm tm;
    36. memset(&tm, 0, sizeof(tm));
    37. tm.tm_year = dts.year- 1900;
    38. tm.tm_mon = dts.month - 1;
    39. tm.tm_mday = dts.day;
    40. tm.tm_hour = dts.hour;
    41. tm.tm_min = dts.min;
    42. tm.tm_sec = dts.sec;
    43. // printf("=%d %d\n",tm.tm_year,tm.tm_mon);
    44. memset(buffer, 0, 20);
    45. strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", &tm);
    46. // printf("convertUnixSecondsToTimestampString:%s\n",buffer);
    47. return buffer;
    48. }
    49. //Context that is needed for the SQLite callback for copying data.
    50. struct context_copyDataValues {
    51. size_t maxValues;
    52. size_t counter;
    53. UA_DataValue *values;
    54. };
    55. typedef struct context_copyDataValues context_copyDataValues;
    56. struct context_sqlite {
    57. sqlite3* sqlite;
    58. const char* measuringPointID;
    59. };
    60. static struct context_sqlite*
    61. generateContext_sqlite(const char* filename)
    62. {
    63. sqlite3* handle;
    64. char *errorMessage;
    65. int res = sqlite3_open(filename, &handle);
    66. if (res != SQLITE_OK)
    67. return NULL;
    68. struct context_sqlite* ret = (struct context_sqlite*)UA_calloc(1, sizeof(struct context_sqlite));
    69. if (ret == NULL)
    70. {
    71. return NULL;
    72. }
    73. const char *sql = "DROP TABLE IF EXISTS PeriodicValues;"
    74. "CREATE TABLE PeriodicValues(MeasuringPointID STRING, Value DOUBLE, Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP);";
    75. res = sqlite3_exec(handle, sql, NULL, NULL, &errorMessage);
    76. if (res != SQLITE_OK)
    77. {
    78. printf("%s | Error | %s\n", __func__, errorMessage);
    79. sqlite3_free(errorMessage);
    80. sqlite3_close(handle);
    81. return NULL;
    82. }
    83. ret->sqlite = handle;
    84. //For this demo we have only one source measuring point which we hardcode in the context.
    85. //A more advanced demo should determine the available measuring points from the source
    86. //itself or maybe an external configuration file.
    87. ret->measuringPointID = "1";
    88. return ret;
    89. }
    90. static UA_StatusCode
    91. serverSetHistoryData_sqliteHDB(UA_Server *server,
    92. void *hdbContext,
    93. const UA_NodeId *sessionId,
    94. void *sessionContext,
    95. const UA_NodeId *nodeId,
    96. UA_Boolean historizing,
    97. const UA_DataValue *value)
    98. {
    99. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    100. size_t result;
    101. char* errorMessage;
    102. char query[QUERY_BUFFER_SIZE];
    103. // UA_String Id=nodeId->identifier.string;
    104. char measuringPointID[60];
    105. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    106. strncpy(query, "INSERT INTO PeriodicValues VALUES('", QUERY_BUFFER_SIZE);
    107. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    108. if (value->hasValue &&
    109. value->status == UA_STATUSCODE_GOOD &&
    110. value->value.type == &UA_TYPES[UA_TYPES_DOUBLE])
    111. {
    112. char remaining[60];
    113. snprintf(remaining, 60, "',%f, CURRENT_TIMESTAMP);", *(double*)(value->value.data));//datetime(CURRENT_TIMESTAMP,'localtime')
    114. strncat(query, remaining, QUERY_BUFFER_SIZE);
    115. }
    116. else
    117. {
    118. printf("%s | Error | historical value is invalid\n", __func__);
    119. return UA_STATUSCODE_BADINTERNALERROR;
    120. }
    121. printf("serverSetHistoryData_sqliteHDB:%s\n",query);
    122. int res = sqlite3_exec(context->sqlite, query, NULL, NULL, &errorMessage);
    123. if (res != SQLITE_OK)
    124. {
    125. printf("%s | Error | %s\n", __func__, errorMessage);
    126. sqlite3_free(errorMessage);
    127. return UA_STATUSCODE_BADINTERNALERROR;
    128. }
    129. return UA_STATUSCODE_GOOD;
    130. }
    131. static size_t
    132. getEnd_sqliteHDB(UA_Server *server,
    133. void *hdbContext,
    134. const UA_NodeId *sessionId,
    135. void *sessionContext,
    136. const UA_NodeId *nodeId)
    137. {
    138. return END_OF_DATA;
    139. }
    140. //This is a callback for all queries that return a single timestamp as the number of Unix seconds
    141. static int timestamp_callback(void* result, int count, char **data, char **columns)
    142. {
    143. *(UA_Int64*)result = convertTimestampStringToUnixSeconds(data[0]);
    144. printf("timestamp_callback:%s\n",data[0]);
    145. // printf("timestamp_callback%lu\n",*(UA_Int64*)result);
    146. return 0;
    147. }
    148. static int resultSize_callback(void* result, int count, char **data, char **columns)
    149. {
    150. *(size_t*)result = strtol(data[0], NULL, 10);
    151. return 0;
    152. }
    153. static size_t
    154. lastIndex_sqliteHDB(UA_Server *server,
    155. void *hdbContext,
    156. const UA_NodeId *sessionId,
    157. void *sessionContext,
    158. const UA_NodeId *nodeId)
    159. {
    160. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    161. size_t result;
    162. char* errorMessage;
    163. char query[QUERY_BUFFER_SIZE];
    164. strncpy(query, "SELECT Timestamp FROM PeriodicValues WHERE MeasuringPointID='", QUERY_BUFFER_SIZE);
    165. char measuringPointID[60];
    166. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    167. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    168. strncat(query, "' ORDER BY Timestamp DESC LIMIT 1", QUERY_BUFFER_SIZE);//DESC
    169. printf("lastIndex:%s\n",query);
    170. int res = sqlite3_exec(context->sqlite, query, timestamp_callback, &result, &errorMessage);
    171. if (res != SQLITE_OK)
    172. {
    173. printf("%s | Error | %s\n", __func__, errorMessage);
    174. sqlite3_free(errorMessage);
    175. return END_OF_DATA;
    176. }
    177. return result;
    178. }
    179. static size_t
    180. firstIndex_sqliteHDB(UA_Server *server,
    181. void *hdbContext,
    182. const UA_NodeId *sessionId,
    183. void *sessionContext,
    184. const UA_NodeId *nodeId)
    185. {
    186. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    187. size_t result;
    188. char* errorMessage;
    189. char query[QUERY_BUFFER_SIZE];
    190. strncpy(query, "SELECT Timestamp FROM PeriodicValues WHERE MeasuringPointID='", QUERY_BUFFER_SIZE);
    191. char measuringPointID[60];
    192. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    193. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    194. strncat(query, "' ORDER BY Timestamp LIMIT 1", QUERY_BUFFER_SIZE);
    195. int res = sqlite3_exec(context->sqlite, query, timestamp_callback, &result, &errorMessage);
    196. printf("firsrIndex:%s\n",query);
    197. if (res != SQLITE_OK)
    198. {
    199. printf("%s | Error | %s\n", __func__, errorMessage);
    200. sqlite3_free(errorMessage);
    201. return END_OF_DATA;
    202. }
    203. return result;
    204. }
    205. static UA_Boolean
    206. search_sqlite(struct context_sqlite* context,const UA_NodeId *nodeId,
    207. UA_Int64 unixSeconds, MatchStrategy strategy,
    208. size_t *index)
    209. {
    210. *index = END_OF_DATA; // TODO
    211. char* errorMessage;
    212. char query[QUERY_BUFFER_SIZE];
    213. strncpy(query, "SELECT Timestamp FROM PeriodicValues WHERE MeasuringPointID='", QUERY_BUFFER_SIZE);
    214. char measuringPointID[60];
    215. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    216. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    217. strncat(query, "' AND ", QUERY_BUFFER_SIZE);
    218. switch (strategy)
    219. {
    220. case MATCH_EQUAL_OR_AFTER:
    221. strncat(query, "Timestamp>='", QUERY_BUFFER_SIZE);
    222. strncat(query, convertUnixSecondsToTimestampString(unixSeconds), QUERY_BUFFER_SIZE);
    223. strncat(query, "' ORDER BY Timestamp LIMIT 1", QUERY_BUFFER_SIZE);
    224. break;
    225. case MATCH_AFTER:
    226. strncat(query, "Timestamp>'", QUERY_BUFFER_SIZE);
    227. strncat(query, convertUnixSecondsToTimestampString(unixSeconds), QUERY_BUFFER_SIZE);
    228. strncat(query, "' ORDER BY Timestamp LIMIT 1", QUERY_BUFFER_SIZE);
    229. break;
    230. case MATCH_EQUAL_OR_BEFORE:
    231. strncat(query, "Timestamp<='", QUERY_BUFFER_SIZE);
    232. strncat(query, convertUnixSecondsToTimestampString(unixSeconds), QUERY_BUFFER_SIZE);
    233. strncat(query, "' ORDER BY Timestamp DESC LIMIT 1", QUERY_BUFFER_SIZE);
    234. break;
    235. case MATCH_BEFORE:
    236. strncat(query, "Timestamp<'", QUERY_BUFFER_SIZE);
    237. strncat(query, convertUnixSecondsToTimestampString(unixSeconds), QUERY_BUFFER_SIZE);
    238. strncat(query, "' ORDER BY Timestamp DESC LIMIT 1", QUERY_BUFFER_SIZE);
    239. break;
    240. default:
    241. return false;
    242. }
    243. printf("search_sqlite:%s\n",query);
    244. int res = sqlite3_exec(context->sqlite, query, timestamp_callback, index, &errorMessage);
    245. if (res != SQLITE_OK)
    246. {
    247. printf("%s | Error | %s\n", __func__, errorMessage);
    248. sqlite3_free(errorMessage);
    249. return false;
    250. }
    251. else
    252. {
    253. return true;
    254. }
    255. }
    256. static size_t
    257. getDateTimeMatch_sqliteHDB(UA_Server *server,
    258. void *hdbContext,
    259. const UA_NodeId *sessionId,
    260. void *sessionContext,
    261. const UA_NodeId *nodeId,
    262. const UA_DateTime timestamp,
    263. const MatchStrategy strategy)
    264. {
    265. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    266. UA_Int64 ts =UA_DateTime_toUnixTime(timestamp);
    267. printf("getDateTimeMatch_sqliteHDB:%s\n",convertUnixSecondsToTimestampString(ts));
    268. printf("strategy:%u\n",strategy);
    269. size_t result = END_OF_DATA;
    270. UA_Boolean res = search_sqlite(context,nodeId, ts, strategy, &result);
    271. return result;
    272. }
    273. static size_t
    274. resultSize_sqliteHDB(UA_Server *server,
    275. void *hdbContext,
    276. const UA_NodeId *sessionId,
    277. void *sessionContext,
    278. const UA_NodeId *nodeId,
    279. size_t startIndex,
    280. size_t endIndex)
    281. {
    282. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    283. char* errorMessage;
    284. size_t result = 0;
    285. printf("resultSize_sqliteHDB:startIndex:%lu\n",startIndex);
    286. printf("resultSize_sqliteHDB:endIndex:%lu\n",endIndex);
    287. char query[QUERY_BUFFER_SIZE];
    288. strncpy(query, "SELECT COUNT(*) FROM PeriodicValues WHERE ", QUERY_BUFFER_SIZE);
    289. strncat(query, "(Timestamp>='", QUERY_BUFFER_SIZE);
    290. strncat(query, convertUnixSecondsToTimestampString(startIndex), QUERY_BUFFER_SIZE);
    291. strncat(query, "') AND (Timestamp<='", QUERY_BUFFER_SIZE);
    292. strncat(query, convertUnixSecondsToTimestampString(endIndex), QUERY_BUFFER_SIZE);
    293. strncat(query, "') AND (MeasuringPointID='", QUERY_BUFFER_SIZE);
    294. char measuringPointID[60];
    295. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    296. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    297. strncat(query, "')", QUERY_BUFFER_SIZE);
    298. printf("resultSize_sqliteHDB:%s\n",query);
    299. int res = sqlite3_exec(context->sqlite, query, resultSize_callback, &result, &errorMessage);
    300. if (res != SQLITE_OK)
    301. {
    302. printf("%s | Error | %s\n", __func__, errorMessage);
    303. sqlite3_free(errorMessage);
    304. return 0; // no data
    305. }
    306. return result;
    307. }
    308. static int copyDataValues_callback(void* result, int count, char **data, char **columns)
    309. {
    310. UA_DataValue dv;
    311. UA_DataValue_init(&dv);
    312. dv.status = UA_STATUSCODE_GOOD;
    313. dv.hasStatus = true;
    314. dv.sourceTimestamp = UA_DateTime_fromUnixTime(convertTimestampStringToUnixSeconds(data[0]));
    315. dv.hasSourceTimestamp = true;
    316. dv.serverTimestamp = dv.sourceTimestamp;
    317. dv.hasServerTimestamp = true;
    318. double value = strtod(data[1], NULL);
    319. UA_Variant_setScalarCopy(&dv.value, &value, &UA_TYPES[UA_TYPES_DOUBLE]);
    320. dv.hasValue = true;
    321. context_copyDataValues* ctx = (context_copyDataValues*)result;
    322. UA_DataValue_copy(&dv, &ctx->values[ctx->counter]);
    323. ctx->counter++;
    324. if (ctx->counter == ctx->maxValues)
    325. {
    326. return 1;
    327. }
    328. else
    329. {
    330. return 0;
    331. }
    332. }
    333. static UA_StatusCode
    334. copyDataValues_sqliteHDB(UA_Server *server,
    335. void *hdbContext,
    336. const UA_NodeId *sessionId,
    337. void *sessionContext,
    338. const UA_NodeId *nodeId,
    339. size_t startIndex,
    340. size_t endIndex,
    341. UA_Boolean reverse,
    342. size_t maxValues,
    343. UA_NumericRange range,
    344. UA_Boolean releaseContinuationPoints,
    345. const UA_ByteString *continuationPoint,
    346. UA_ByteString *outContinuationPoint,
    347. size_t *providedValues,
    348. UA_DataValue *values)
    349. {
    350. //NOTE: this demo does not support continuation points!!!
    351. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    352. char* errorMessage;
    353. // const char* measuringPointID = "1";
    354. char query[QUERY_BUFFER_SIZE];
    355. strncpy(query, "SELECT Timestamp, Value FROM PeriodicValues WHERE ", QUERY_BUFFER_SIZE);
    356. strncat(query, "(Timestamp>='", QUERY_BUFFER_SIZE);
    357. strncat(query, convertUnixSecondsToTimestampString(startIndex), QUERY_BUFFER_SIZE);
    358. strncat(query, "') AND (Timestamp<='", QUERY_BUFFER_SIZE);
    359. strncat(query, convertUnixSecondsToTimestampString(endIndex), QUERY_BUFFER_SIZE);
    360. strncat(query, "') AND (MeasuringPointID='", QUERY_BUFFER_SIZE);
    361. char measuringPointID[60];
    362. snprintf(measuringPointID, 60, "ns=%d;i=%d",nodeId->namespaceIndex,nodeId->identifier.numeric);
    363. strncat(query, measuringPointID, QUERY_BUFFER_SIZE);
    364. strncat(query, "')", QUERY_BUFFER_SIZE);
    365. context_copyDataValues ctx;
    366. ctx.maxValues = maxValues;
    367. ctx.counter = 0;
    368. ctx.values = values;
    369. int res = sqlite3_exec(context->sqlite, query, copyDataValues_callback, &ctx, &errorMessage);
    370. if (res != SQLITE_OK)
    371. {
    372. if (res == SQLITE_ABORT) // if reach maxValues, then request abort, so this is not error
    373. {
    374. sqlite3_free(errorMessage);
    375. return UA_STATUSCODE_GOOD;
    376. }
    377. else
    378. {
    379. printf("%s | Error | %s\n", __func__, errorMessage);
    380. sqlite3_free(errorMessage);
    381. return UA_STATUSCODE_BADINTERNALERROR;
    382. }
    383. }
    384. else
    385. {
    386. return UA_STATUSCODE_GOOD;
    387. }
    388. }
    389. static const UA_DataValue*
    390. getDataValue_sqliteHDB(UA_Server *server,
    391. void *hdbContext,
    392. const UA_NodeId *sessionId,
    393. void *sessionContext,
    394. const UA_NodeId *nodeId,
    395. size_t index)
    396. {
    397. struct context_sqlite* context = (struct context_sqlite*)hdbContext;
    398. return NULL;
    399. }
    400. static UA_Boolean
    401. boundSupported_sqliteHDB(UA_Server *server,
    402. void *hdbContext,
    403. const UA_NodeId *sessionId,
    404. void *sessionContext,
    405. const UA_NodeId *nodeId)
    406. {
    407. return false; // We don't support returning bounds in this demo
    408. }
    409. static UA_Boolean
    410. timestampsToReturnSupported_sqliteHDB(UA_Server *server,
    411. void *hdbContext,
    412. const UA_NodeId *sessionId,
    413. void *sessionContext,
    414. const UA_NodeId *nodeId,
    415. const UA_TimestampsToReturn timestampsToReturn)
    416. {
    417. return true;
    418. }
    419. UA_HistoryDataBackend
    420. UA_HistoryDataBackend_sqlite(const char* filename)
    421. {
    422. UA_HistoryDataBackend result;
    423. memset(&result, 0, sizeof(UA_HistoryDataBackend));
    424. result.serverSetHistoryData = &serverSetHistoryData_sqliteHDB;
    425. result.resultSize = &resultSize_sqliteHDB;
    426. result.getEnd = &getEnd_sqliteHDB;
    427. result.lastIndex = &lastIndex_sqliteHDB;
    428. result.firstIndex = &firstIndex_sqliteHDB;
    429. result.getDateTimeMatch = &getDateTimeMatch_sqliteHDB;
    430. result.copyDataValues = ©DataValues_sqliteHDB;
    431. result.getDataValue = &getDataValue_sqliteHDB;
    432. result.boundSupported = &boundSupported_sqliteHDB;
    433. result.timestampsToReturnSupported = ×tampsToReturnSupported_sqliteHDB;
    434. result.deleteMembers = NULL; // We don't support deleting in this demo
    435. result.getHistoryData = NULL; // We don't support the high level API in this demo
    436. result.context = generateContext_sqlite(filename);
    437. return result;
    438. }
    439. #endif

    应用程序测试

    server 修改为添加两个变量。NodeId 使用Numeric 方式。

    源代码(server.h)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include "SQLiteBackend.h"
    9. static UA_Boolean running = true;
    10. static void stopHandler(int sign)
    11. {
    12. (void)sign;
    13. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    14. running = false;
    15. }
    16. UA_NodeId addHistoryVariable(UA_Server *server,char *VairableName){
    17. /* Define the attribute of the uint32 variable node */
    18. UA_VariableAttributes attr = UA_VariableAttributes_default;
    19. UA_Double myDouble = 17.2;
    20. UA_Variant_setScalar(&attr.value, &myDouble, &UA_TYPES[UA_TYPES_DOUBLE]);
    21. attr.description = UA_LOCALIZEDTEXT("en-US", VairableName);
    22. attr.displayName = UA_LOCALIZEDTEXT("en-US", VairableName);
    23. attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
    24. /*
    25. * We set the access level to also support history read
    26. * This is what will be reported to clients
    27. */
    28. attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD;
    29. /*
    30. * We also set this node to historizing, so the server internals also know from it.
    31. */
    32. attr.historizing = true;
    33. /* Add the variable node to the information model */
    34. UA_NodeId doubleNodeId = UA_NODEID_STRING(1, VairableName);
    35. UA_QualifiedName doubleName = UA_QUALIFIEDNAME(1, VairableName);
    36. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    37. UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    38. UA_NodeId outNodeId;
    39. UA_NodeId_init(&outNodeId);
    40. UA_StatusCode retval = UA_Server_addVariableNode(server,
    41. UA_NODEID_NULL,
    42. parentNodeId,
    43. parentReferenceNodeId,
    44. doubleName,
    45. UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
    46. attr,
    47. NULL,
    48. &outNodeId);
    49. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "UA_Server_addVariableNode %s", UA_StatusCode_name(retval));
    50. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"addHistoryVariable:%d",outNodeId.identifier.numeric);
    51. return outNodeId;
    52. }
    53. int main(void)
    54. {
    55. signal(SIGINT, stopHandler);
    56. signal(SIGTERM, stopHandler);
    57. UA_Server *server = UA_Server_new();
    58. UA_ServerConfig *config = UA_Server_getConfig(server);
    59. UA_ServerConfig_setDefault(config);
    60. UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Default(1);
    61. config->historyDatabase = UA_HistoryDatabase_default(gathering);
    62. UA_NodeId VariableANodeId=addHistoryVariable(server,"myDoubleValueA");
    63. UA_NodeId VariableBNodeId=addHistoryVariable(server,"myDoubleValueB");
    64. UA_HistorizingNodeIdSettings setting;
    65. setting.historizingBackend = UA_HistoryDataBackend_sqlite("database.sqlite");
    66. setting.maxHistoryDataResponseSize = 100;
    67. setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET;
    68. UA_StatusCode retval = gathering.registerNodeId(server, gathering.context, &VariableANodeId, setting);
    69. retval = gathering.registerNodeId(server, gathering.context, &VariableBNodeId, setting);
    70. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "registerNodeId %s", UA_StatusCode_name(retval));
    71. retval = UA_Server_run(server, &running);
    72. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "UA_Server_run %s", UA_StatusCode_name(retval));
    73. UA_Server_delete(server);
    74. return (int)retval;
    75. }

    使用DB Browser for SQLite 工具查看数据库数据

    下载一个DB Browser for SQLite 工具可以查看数据库中的数据:

    使用uaExpert

             使用uaExpert 修改变量的数据 ,并且查看历史数据。具体方法可以参照引用的博文。特别注意的是;在数据库中存储的时间标签是UTC标准时间,而uaExpert 使用的是北京时间,它们相差了8小时。在查看历史数据时,要注意起始时间和结束时间,设置错了,不会读出。可以通过DB Browser 查看记录数据的时间标签,并将小时加8。

    结束语

            开源软件并不完全是拿来就够了。真正实际应用,需要大量的开发工作。要充分估计开源软件的学习和扩展功能的工作量。

  • 相关阅读:
    招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本
    金典成为饿了么小蓝盒首个低碳“盒”伙人:战略合作迎绿色亚运
    JavaScript常用工具函数汇总(一)
    docker+mongo主从仲裁+用户密码认证
    IMX6-->8 RTC同步程序涉及到的知识点
    布尔矩阵的奇偶性
    一个电子小说阅读系统源码,thinkphp开发的小说系统系统
    1.8 打好shell基础
    python与java的一些不同简录
    uni-app学习(1)
  • 原文地址:https://blog.csdn.net/yaojiawan/article/details/133159557