diff --git a/.gitignore b/.gitignore
index dde6cd6..d6376d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
**/.idea
**/vendor
**/.env
-**/node_modules
\ No newline at end of file
+**/node_modules
+**/cmake-build-*
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index c6f0290..03de0fa 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,18 +4,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
@@ -36,10 +50,17 @@
-
-
+
+
+
+
-
+
@@ -307,26 +328,31 @@
- {
- "keyToString": {
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "git-widget-placeholder": "master",
- "last_opened_file_path": "E:/data-collection-terminal",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "vue.rearranger.settings.migration": "true"
+
+}]]>
+
+
+
+
+
@@ -339,9 +365,6 @@
-
-
-
@@ -365,9 +388,7 @@
-
-
-
+
@@ -433,75 +454,53 @@
1724736926763
-
+
- 1724737302338
+ 1724741136342
- 1724737302338
+ 1724741136342
-
+
- 1724743562419
+ 1724742151638
- 1724743562420
+ 1724742151638
-
+
- 1724743728810
+ 1724742216453
- 1724743728810
+ 1724742216453
-
+
- 1724745600949
+ 1724742239281
- 1724745600949
+ 1724742239281
-
+
+
+ 1724742322416
+
+
+
+ 1724742322416
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/opcua-expoter/.gitignore b/opcua-expoter/.gitignore
new file mode 100644
index 0000000..378ce18
--- /dev/null
+++ b/opcua-expoter/.gitignore
@@ -0,0 +1,37 @@
+### C++ template
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+#Clion
+.idea
+cmake-build-*
diff --git a/opcua-expoter/CMakeLists.txt b/opcua-expoter/CMakeLists.txt
new file mode 100644
index 0000000..71621e6
--- /dev/null
+++ b/opcua-expoter/CMakeLists.txt
@@ -0,0 +1,57 @@
+cmake_minimum_required(VERSION 3.18)
+project(opcua_exporter)
+
+cmake_policy(SET CMP0148 OLD)
+
+set(CMAKE_CXX_STANDARD 20)
+option(ENABLE_METRICS_SIMULATE "开启测点故障仿真功能" ON)
+
+find_package(civetweb CONFIG REQUIRED)
+find_package(spdlog CONFIG REQUIRED)
+find_package(prometheus-cpp CONFIG REQUIRED)
+find_package(open62541 CONFIG REQUIRED)
+find_package(yaml-cpp CONFIG REQUIRED)
+find_package(nlohmann_json CONFIG REQUIRED)
+
+add_subdirectory(fault-simulation-algorithm)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+set(
+ OPC_UA_EXPORTER_SRCS
+ main.cpp
+ client/client.cpp
+ client/client.h
+ collector/collector.cpp
+ collector/collector.h
+)
+
+add_executable(
+ opcua_exporter
+ ${OPC_UA_EXPORTER_SRCS}
+)
+
+target_compile_definitions(
+ opcua_exporter
+ PRIVATE
+ ${CMAKE_BUILD_TYPE}
+)
+
+if (${ENABLE_METRICS_SIMULATE})
+ target_compile_definitions(
+ opcua_exporter
+ PRIVATE
+ METRICS_SIMULATE
+ )
+endif ()
+
+target_link_libraries(
+ opcua_exporter
+ simulation-manager
+ spdlog::spdlog
+ yaml-cpp::yaml-cpp
+ nlohmann_json::nlohmann_json
+ open62541::open62541
+ civetweb::civetweb
+ prometheus-cpp::core prometheus-cpp::pull
+)
\ No newline at end of file
diff --git a/opcua-expoter/client/client.cpp b/opcua-expoter/client/client.cpp
new file mode 100644
index 0000000..f9db8e2
--- /dev/null
+++ b/opcua-expoter/client/client.cpp
@@ -0,0 +1,399 @@
+//
+// Created by 闫鹏宇 on 2022/7/28.
+//
+
+#include "client.h"
+#include
+#include
+#include
+
+#include
+#include
+
+
+namespace std {
+ template<>
+ struct hash >{
+ size_t operator()(const std::vector &rhs) const noexcept {
+ size_t res = 0;
+ for (auto const &str: rhs) {
+ res ^= std::hash()(str);
+ }
+ return res;
+ }
+ };
+}
+
+
+inline static spdlog::level::level_enum get_spd_level(UA_LogLevel level) {
+ switch (level) {
+ case UA_LogLevel::UA_LOGLEVEL_TRACE:
+ return spdlog::level::trace;
+ case UA_LogLevel::UA_LOGLEVEL_DEBUG:
+ return spdlog::level::debug;
+ case UA_LogLevel::UA_LOGLEVEL_INFO:
+ return spdlog::level::info;
+ case UA_LogLevel::UA_LOGLEVEL_WARNING:
+ return spdlog::level::warn;
+ case UA_LogLevel::UA_LOGLEVEL_ERROR:
+ return spdlog::level::err;
+ case UA_LogLevel::UA_LOGLEVEL_FATAL:
+ return spdlog::level::critical;
+ default:
+ return spdlog::level::trace;
+ }
+}
+
+
+static void opc_ua_log(void *logContext, UA_LogLevel level, UA_LogCategory category,
+ const char *msg, va_list args) {
+ (void) args;
+ thread_local static char const *last_log_msg = nullptr;
+
+ auto c = static_cast(logContext);
+ if (!c) {
+ return;
+ }
+ if (msg != last_log_msg) {
+ char buff[256] = {0};
+ vsprintf(buff, msg, args);
+ spdlog::log(get_spd_level(level), buff);
+ }
+ last_log_msg = msg;
+}
+
+static void OPC_UA_Client_DeleteSubscriptionCallback(
+ UA_Client *client,
+ UA_UInt32 subId,
+ void *subContext
+) {
+ spdlog::trace(
+ "OPC_UA_Client_DeleteSubscriptionCallback subId{}",
+ subId
+ );
+}
+
+static void OPC_UA_Client_StatusChangeNotificationCallback(
+ UA_Client *client,
+ UA_UInt32 subId,
+ void *subContext,
+ UA_StatusChangeNotification *notification
+) {
+ spdlog::trace(
+ "OPC_UA_Client_StatusChangeNotificationCallback subId{}",
+ subId
+ );
+}
+
+static void OPC_UA_Client_DataChangeNotificationCallback(
+ UA_Client *client,
+ UA_UInt32 subId,
+ void *subContext,
+ UA_UInt32 monId,
+ void *monContext,
+ UA_DataValue *value
+) {
+ spdlog::trace(
+ "OPC_UA_Client_DataChangeNotificationCallback subId{} monId",
+ subId,
+ monId
+ );
+}
+
+static void OPC_UA_Client_DeleteMonitoredItemCallback(
+ UA_Client *client,
+ UA_UInt32 subId,
+ void *subContext,
+ UA_UInt32 monId,
+ void *monContext) {
+ spdlog::trace(
+ "OPC_UA_Client_DeleteMonitoredItemCallback subId{} monId",
+ subId,
+ monId
+ );
+}
+
+static void OPC_UA_ClientAsyncReadCallback(
+ UA_Client *c, void *userdata,
+ UA_UInt32 requestId,
+ UA_ReadResponse *rr
+) {
+ auto cc = static_cast(userdata);
+ auto hash_key = cc->getAsyncReadRequestHashKey(requestId);
+ cc->updateReadValueCache(hash_key, *rr);
+}
+
+
+client::client(std::string endpoint) :
+ ua_endpoint(std::move(endpoint)) {
+ ua_config.logger.context = this;
+ ua_config.logger.log = &opc_ua_log;
+ UA_ClientConfig_setDefault(&ua_config);
+ ua_client = UA_Client_newWithConfig(&ua_config);
+ for (int i = 0; i < async_read_request_ids.size(); ++i) {
+ async_read_request_ids[i].first = i;
+ async_read_request_ids[i].second = 0;
+ }
+}
+
+client::~client() {
+ stop();
+ UA_Client_delete(ua_client);
+}
+
+void client::createSubscription() {
+ if (create_subscription_response) {
+ return;
+ }
+ auto request = UA_CreateSubscriptionRequest_default();
+ create_subscription_response = UA_Client_Subscriptions_create(
+ ua_client,
+ request,
+ this,
+ &OPC_UA_Client_StatusChangeNotificationCallback,
+ &OPC_UA_Client_DeleteSubscriptionCallback
+ );
+}
+
+void client::stop() {
+ running = false;
+ disconnect();
+}
+
+void client::run() {
+ UA_StatusCode retval = UA_STATUSCODE_GOOD;
+ int error_time = 0;
+ while (running) {
+ if (!connect()) {
+ //disconnect();
+ spdlog::error("OPC-UA Server({}) not connected({}). Retrying to connect in 1 second.", ua_endpoint,
+ retval);
+ UA_sleep_ms(1000);
+ continue;
+ } else if (MONITOR == read_mode) {
+ createSubscription();
+ }
+ while (running && UA_STATUSCODE_GOOD == retval) {
+ std::unique_lock lock(ua_mutex);
+ retval = UA_Client_run_iterate(ua_client, 100);
+ }
+ }
+}
+
+bool client::connect() {
+ std::unique_lock lock(ua_mutex);
+ return UA_STATUSCODE_GOOD == UA_Client_connect(ua_client, ua_endpoint.c_str());
+}
+
+void client::disconnect() {
+ std::unique_lock lock(ua_mutex);
+ UA_Client_disconnect(ua_client);
+}
+
+
+std::vector client::makeReadValueIds(std::vector const &node_ids) {
+ std::vector ids;
+ if (!node_ids.empty()) {
+ for (auto const &node_id: node_ids) {
+ ids.emplace_back(
+ UA_ReadValueId{
+ UA_NODEID(node_id.c_str()),
+ UA_ATTRIBUTEID_VALUE,
+ {},
+ {}
+ }
+ );
+ }
+ }
+ return ids;
+}
+
+UA_ReadRequest &client::makeReadRequest(size_t hash_key) {
+
+ std::vector &read_value_ids = ua_read_value_request_cache_[hash_key].read_value_ids;
+ auto &request = ua_read_value_request_cache_[hash_key].read_request;
+ request.nodesToRead = const_cast(read_value_ids.data());
+ request.nodesToReadSize = read_value_ids.size();
+ return request;
+}
+
+void client::cachePreCheckAndInit(size_t hash_key, std::vector const &node_ids) {
+ auto ua_read_value_ids_iter = ua_read_value_request_cache_.find(hash_key);
+ if (ua_read_value_ids_iter == ua_read_value_request_cache_.end()) {
+ auto ids = makeReadValueIds(node_ids);
+ if (ids.empty()) {
+ return;
+ }
+ ua_read_value_request_cache_[hash_key].read_value_ids = std::move(ids);
+ ua_read_value_request_cache_[hash_key].node_ids = node_ids;
+ UA_ReadRequest_init(&ua_read_value_request_cache_[hash_key].read_request);
+ }
+}
+
+uint32_t *client::getAsyncReadRequestId(size_t hash_key) {
+ auto &ids = async_read_request_ids[(current_async_read_request_pos++) % async_read_request_ids.size()];
+ ids.second = hash_key;
+ return &ids.first;
+}
+
+size_t client::getAsyncReadRequestHashKey(uint32_t req_id) {
+ for (auto const &iter: async_read_request_ids) {
+ if (iter.first == req_id) {
+ return iter.second;
+ }
+ }
+ return 0;
+}
+
+void client::sendReadRequest(size_t hash_key, bool force_sync) {
+ ua_read_value_request_cache_[hash_key].last_read_request_time = std::chrono::system_clock::now();
+ if (SYNC == read_mode || force_sync) {
+ UA_ReadResponse response;
+ {
+ std::shared_lock lock(ua_mutex);
+ response = UA_Client_Service_read(
+ ua_client,
+ makeReadRequest(
+ hash_key
+ )
+ );
+ }
+ if (UA_STATUSCODE_GOOD == response.responseHeader.serviceResult) {
+ updateReadValueCache(hash_key, response);
+ }
+ UA_ReadResponse_clear(&response);
+ }
+ switch (read_mode) {
+ case ASYNC: {
+ UA_Client_sendAsyncReadRequest(
+ ua_client,
+ &makeReadRequest(hash_key),
+ &OPC_UA_ClientAsyncReadCallback,
+ this,
+ getAsyncReadRequestId(hash_key)
+ );
+ break;
+ }
+ case MONITOR: {
+
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+std::chrono::system_clock::duration client::getReadRequestDuration(size_t hash_key) {
+ return std::chrono::system_clock::now() -
+ ua_read_value_request_cache_[hash_key].last_read_request_time;
+}
+
+std::chrono::system_clock::duration client::getCacheUpdateDuration(size_t hash_key) {
+ return std::chrono::system_clock::now() -
+ ua_read_value_request_cache_[hash_key].last_read_response_time;
+}
+
+std::map client::readValue(std::vector const &node_ids) {
+ if (!node_ids.empty()) {
+ auto node_ids_hash_value = std::hash>()(node_ids);
+ cachePreCheckAndInit(node_ids_hash_value, node_ids);
+
+ std::lock_guard lock(ua_read_mutex);
+
+ auto duration = getCacheUpdateDuration(node_ids_hash_value);
+ if (duration > cache_timeout) {
+ sendReadRequest(node_ids_hash_value, true);
+ } else if (duration > cache_update_time) {
+ sendReadRequest(node_ids_hash_value);
+ }
+ if (getCacheUpdateDuration(node_ids_hash_value) < cache_timeout) {
+ return getCacheValue(node_ids_hash_value);
+ }
+ }
+ return {};
+}
+
+std::map client::getCacheValue(size_t hash_key) {
+ std::map values;
+
+ auto iter = ua_read_value_request_cache_.find(hash_key);
+ if (iter != ua_read_value_request_cache_.end()) {
+ for (auto const &node_id: iter->second.node_ids) {
+ values[node_id] = ua_read_value_cache[node_id];
+ }
+ }
+ return values;
+}
+
+void client::updateReadValueCache(size_t hash_key, UA_ReadResponse const &response) {
+ auto iter = ua_read_value_request_cache_.find(hash_key);
+ if (iter == ua_read_value_request_cache_.end()) {
+ return;
+ }
+ if (response.resultsSize != iter->second.read_value_ids.size()) {
+ return;
+ }
+ iter->second.last_read_response_time = std::chrono::system_clock::now();
+
+ for (int i = 0; i < response.resultsSize; ++i) {
+ auto &data_value = response.results[i];
+ setValueCache(iter->second.node_ids[i], getValueAsDouble(data_value));
+ }
+}
+
+void client::setValueCache(std::string const &node_id, double value) {
+ ua_read_value_cache[node_id] = value;
+}
+
+double client::getValueAsDouble(UA_DataValue const &ua_value) {
+ if (ua_value.hasValue) {
+ switch (ua_value.value.type->typeKind) {
+ case UA_DATATYPEKIND_BOOLEAN: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_SBYTE: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_BYTE: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_INT16: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_UINT16: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_INT32: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_UINT32: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_INT64: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_UINT64: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_FLOAT: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ case UA_DATATYPEKIND_DOUBLE: {
+ return static_cast(*static_cast(ua_value.value.data));
+ }
+ default:
+ break;
+ }
+ }
+ return std::nan(nullptr);
+}
+
+void client::readTest() {
+ auto read_request = UA_ReadRequest_new();
+}
+
+
+UA_Client *client::getClient() {
+ return ua_client;
+}
diff --git a/opcua-expoter/client/client.h b/opcua-expoter/client/client.h
new file mode 100644
index 0000000..b694157
--- /dev/null
+++ b/opcua-expoter/client/client.h
@@ -0,0 +1,83 @@
+//
+// Created by 闫鹏宇 on 2022/7/28.
+//
+
+#ifndef OPCUA_EXPORTER_CLIENT_H
+#define OPCUA_EXPORTER_CLIENT_H
+
+#include
+#include