diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cd2704..1fab5d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_AUTORCC ON) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Sql Xml REQUIRED Charts) find_package(Qt6 REQUIRED COMPONENTS SvgWidgets) -find_package(Qt6 COMPONENTS Network REQUIRED) +find_package(Qt6 COMPONENTS Network WebSockets REQUIRED) find_package(PostgreSQL REQUIRED) @@ -158,8 +158,9 @@ set_target_properties(DiagramDesigner PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin" ) -target_link_libraries(DiagramDesigner PRIVATE diagramCavas diagramUtils) +target_link_libraries(DiagramDesigner PRIVATE diagramCavas diagramUtils diagramCommunication) add_subdirectory(diagramCavas) add_subdirectory(diagramUtils) +add_subdirectory(diagramCommunication) file(COPY setting.xml DESTINATION "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin") diff --git a/diagramCavas/CMakeLists.txt b/diagramCavas/CMakeLists.txt index 81d37bb..b0a57d3 100644 --- a/diagramCavas/CMakeLists.txt +++ b/diagramCavas/CMakeLists.txt @@ -40,6 +40,7 @@ set(DIAGRAMCAVAS_HEADER_FILES include/projectIconSetting.h include/projectIconSelectionDlg.h include/projectDiagramNameInput.h + include/diagramConnectSetting.h include/diagramEditor/editPanel.h include/diagramEditor/editView.h include/diagramEditor/editScene.h @@ -112,6 +113,7 @@ set(DIAGRAMCAVAS_HEADER_FILES include/util/scalingSelector.h include/util/selectorManager.h include/util/subMovingSelector.h + include/instance/dataAccessor.h ../common/include/httpInterface.h ../common/include/tools.h ../common/include/global.h @@ -160,6 +162,7 @@ set(DIAGRAMCAVAS_SOURCE_FILES source/projectIconSetting.cpp source/projectIconSelectionDlg.cpp source/projectDiagramNameInput.cpp + source/diagramConnectSetting.cpp source/diagramEditor/editPanel.cpp source/diagramEditor/editView.cpp source/diagramEditor/editScene.cpp @@ -232,6 +235,7 @@ set(DIAGRAMCAVAS_SOURCE_FILES source/util/scalingSelector.cpp source/util/selectorManager.cpp source/util/subMovingSelector.cpp + source/instance/dataAccessor.cpp ../common/source/httpInterface.cpp ../common/source/baseProperty.cpp ../common/source/tools.cpp @@ -263,6 +267,7 @@ set(UI_FILES ui/monitorDetailAttributeDlg.ui ui/monitorDisplaySettingDlg.ui ui/loadMonitorPageDlg.ui + ui/diagramConnectSetting.ui ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) @@ -288,6 +293,7 @@ target_link_libraries(diagramCavas PUBLIC Qt${QT_VERSION_MAJOR}::Core target_link_libraries(diagramCavas PRIVATE Qt6::SvgWidgets) target_link_libraries(diagramCavas PRIVATE Qt6::Xml) target_link_libraries(diagramCavas PRIVATE Qt6::Network) +target_link_libraries(diagramCavas PRIVATE Qt6::WebSockets) target_link_libraries(diagramCavas PRIVATE Qt6::Charts) target_link_libraries(diagramCavas PRIVATE Qt6::Sql ${PostgreSQL_LIBRARIES}) @@ -298,6 +304,7 @@ option(BUILD_SHARED_LIBS "Build as shared library" ON) target_include_directories(diagramCavas PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(diagramCavas PRIVATE diagramUtils) +target_link_libraries(diagramCavas PRIVATE diagramCommunication) target_compile_definitions(diagramCavas PUBLIC diff --git a/diagramCavas/include/diagramCavas.h b/diagramCavas/include/diagramCavas.h index 33bd4fc..b342276 100644 --- a/diagramCavas/include/diagramCavas.h +++ b/diagramCavas/include/diagramCavas.h @@ -18,6 +18,7 @@ class EditBaseItem; class MonitorPanel; class CornerMonitorLauncher; class LoadMonitorPageDlg; +class DiagramConnectSetting; class DIAGRAM_DESIGNER_PUBLIC DiagramCavas : public QMdiArea { @@ -64,6 +65,8 @@ public slots: void onSignal_deleteDiagram(DiagramInfo); void onSignal_selectDiagram(DiagramInfo); + void onSignal_openNetSetting(); //打开网络设置 + void onCreateTestBaseModelDiagram(); //生成测试基模图 /******************************生成组态***********************************/ void onSignal_createEditPanel(QString,QUuid); @@ -104,6 +107,7 @@ private: QString _curPage; CornerMonitorLauncher* _cornerButton; //简略菜单呼出按钮 LoadMonitorPageDlg* _loadMonitorPageDlg; + DiagramConnectSetting* _connectSetting; }; #endif diff --git a/diagramCavas/include/diagramConnectSetting.h b/diagramCavas/include/diagramConnectSetting.h new file mode 100644 index 0000000..4217e0e --- /dev/null +++ b/diagramCavas/include/diagramConnectSetting.h @@ -0,0 +1,35 @@ +#ifndef DIAGRAMCONNECTSETTING_H +#define DIAGRAMCONNECTSETTING_H + +#include +#include "global.h" +/******************************************************* + 网络连接设置 +********************************************************/ +QT_BEGIN_NAMESPACE +namespace Ui { class diagramConnectSetting; } +QT_END_NAMESPACE + +struct ChannelConfig; + +class DiagramConnectSetting : public QDialog +{ + Q_OBJECT +public: + DiagramConnectSetting(QWidget *parent = nullptr); + ~DiagramConnectSetting(); + + void showDlg(); +public slots: + void onTestHttpClicked(); + void onTestWebsocketClicked(); + void onOkClicked(); + void onCancelClicked(); +private: + void initial(); + void updateByConfig(ChannelConfig,int nType = 0); //0http 1websocket +private: + Ui::diagramConnectSetting *ui; +}; + +#endif diff --git a/diagramCavas/include/instance/dataAccessor.h b/diagramCavas/include/instance/dataAccessor.h new file mode 100644 index 0000000..adb36b3 --- /dev/null +++ b/diagramCavas/include/instance/dataAccessor.h @@ -0,0 +1,18 @@ +#ifndef DATAACCESSOR_H +#define DATAACCESSOR_H + +/*********中转、处理网络数据*********/ +#include +#include + +class DataAccessor : public QObject +{ + Q_OBJECT +public: + DataAccessor(QObject *parent = nullptr); + ~DataAccessor(); +private: + QMap> _realTimeData; +}; + +#endif diff --git a/diagramCavas/source/diagramCavas.cpp b/diagramCavas/source/diagramCavas.cpp index 1ea8dfa..aa19d56 100644 --- a/diagramCavas/source/diagramCavas.cpp +++ b/diagramCavas/source/diagramCavas.cpp @@ -18,11 +18,15 @@ #include "basePropertyManager.h" #include "cornerMonitorLauncher.h" #include "loadMonitorPageDlg.h" +#include "diagramConnectSetting.h" +#include "diagramCommunication/include/communicationManager.h" +#include "diagramCommunication/include/configManager.h" DiagramCavas::DiagramCavas(QWidget *parent) : QMdiArea(parent) ,_cornerButton(nullptr) ,_loadMonitorPageDlg(nullptr) + ,_connectSetting(nullptr) { _pageIndex = 0; } @@ -77,6 +81,19 @@ void DiagramCavas::initial() } }); connect(_loadMonitorPageDlg,&LoadMonitorPageDlg::monitorSelected,this,&DiagramCavas::onSignal_monitorSelected); + _connectSetting = new DiagramConnectSetting(this); + + // 初始化通信管理器 + CommunicationManager* comm = CommunicationManager::instance(); + comm->initialize(); + + // 加载配置 + ConfigManager* config = ConfigManager::instance(); + config->loadConfig("config.json"); + + // 应用配置 + comm->updateHttpConfig(config->getHttpConfig()); + comm->updateWebSocketConfig(config->getWebSocketConfig()); } void DiagramCavas::onSignal_addDrawingPanel(PowerEntity* pItem,DiagramMode mode,QString parent) @@ -432,6 +449,13 @@ void DiagramCavas::onSignal_selectDiagram(DiagramInfo info) } } +void DiagramCavas::onSignal_openNetSetting() +{ + if(_connectSetting){ + _connectSetting->showDlg(); + } +} + void DiagramCavas::removePanel(PowerEntity* pEntity) { QMap>::Iterator iter; diff --git a/diagramCavas/source/diagramConnectSetting.cpp b/diagramCavas/source/diagramConnectSetting.cpp new file mode 100644 index 0000000..96c2c08 --- /dev/null +++ b/diagramCavas/source/diagramConnectSetting.cpp @@ -0,0 +1,70 @@ +#include "diagramConnectSetting.h" +#include "ui_diagramConnectSetting.h" +#include "diagramCommunication/include/configManager.h" + +DiagramConnectSetting::DiagramConnectSetting(QWidget *parent) + : QDialog(parent) + , ui(new Ui::diagramConnectSetting) +{ + ui->setupUi(this); + this->setWindowFlags(Qt::FramelessWindowHint | windowFlags()); + initial(); +} + +DiagramConnectSetting::~DiagramConnectSetting() +{ + delete ui; +} + +void DiagramConnectSetting::initial() +{ + connect(ui->btn_testHttp,&QPushButton::clicked,this,&DiagramConnectSetting::onTestHttpClicked); + connect(ui->btn_testWebsoc,&QPushButton::clicked,this,&DiagramConnectSetting::onTestWebsocketClicked); + connect(ui->btn_ok,&QPushButton::clicked,this,&DiagramConnectSetting::onOkClicked); + connect(ui->btn_cancel,&QPushButton::clicked,this,&DiagramConnectSetting::onCancelClicked); +} + +void DiagramConnectSetting::updateByConfig(ChannelConfig info,int nType) +{ + if(nType == 0){ + ui->le_httpName->setText(info.name); + ui->le_httpIp->setText(info.endpoint); + ui->le_httpOver->setText(QString::number(info.timeout)); + } + else if(nType == 1){ + ui->le_websocName->setText(info.name); + ui->le_websocIp->setText(info.endpoint); + ui->le_websocOver->setText(QString::number(info.timeout)); + ui->le_websocHeart->setText(QString::number(info.heartbeatInterval)); + } +} + +void DiagramConnectSetting::showDlg() +{ + show(); + ConfigManager* config = ConfigManager::instance(); + auto httpInfo = config->getHttpConfig(); + updateByConfig(httpInfo,0); + auto socketInfo = config->getWebSocketConfig(); + updateByConfig(socketInfo,1); +} + +void DiagramConnectSetting::onTestHttpClicked() +{ + +} + +void DiagramConnectSetting::onTestWebsocketClicked() +{ + +} + +void DiagramConnectSetting::onOkClicked() +{ + hide(); +} + +void DiagramConnectSetting::onCancelClicked() +{ + hide(); +} diff --git a/diagramCavas/source/instance/dataAccessor.cpp b/diagramCavas/source/instance/dataAccessor.cpp new file mode 100644 index 0000000..1f6585a --- /dev/null +++ b/diagramCavas/source/instance/dataAccessor.cpp @@ -0,0 +1,12 @@ +#include "instance/dataAccessor.h" + +DataAccessor::DataAccessor(QObject* parent) + : QObject(parent) +{ + +} + +DataAccessor::~DataAccessor() +{ + +} diff --git a/diagramCavas/source/monitorDisplaySettingDlg.cpp b/diagramCavas/source/monitorDisplaySettingDlg.cpp index f8db9ff..261537c 100644 --- a/diagramCavas/source/monitorDisplaySettingDlg.cpp +++ b/diagramCavas/source/monitorDisplaySettingDlg.cpp @@ -112,29 +112,6 @@ void MonitorDisplaySettingDlg::onCheckboxToggled(bool val) void MonitorDisplaySettingDlg::onIconSelectClicked() { - // if(!_curMeta.isEmpty() && !_curModel.isEmpty()){ - // auto mapAllSvg = ProjectModelManager::instance().getData()[_curMeta][_curModel].modelSetting.mapSvg; - // ProjectIconSelectionDlg dialog(mapAllSvg, this); - // if (dialog.exec() == QDialog::Accepted) { - // QByteArray selectedSVG = dialog.selectedSVG(); - // if (!selectedSVG.isEmpty()) { - // QSvgRenderer renderer(selectedSVG); - // QPixmap pixmap(32, 32); - // pixmap.fill(Qt::transparent); - // QPainter painter(&pixmap); - // renderer.render(&painter); - - // ui->btn_selectIcon->setIcon(QIcon(pixmap)); - // if (validateSvgData(selectedSVG)) { - // updateIconDisplay(selectedSVG); - - // if(_curType.isValid() && _curState.isValid()){ - // _tempSetting[_curType][_curState].bytPicture = selectedSVG; - // } - // } - // } - // } - // } if(_curMeta.isEmpty() || _curModel.isEmpty()){ return; } @@ -217,22 +194,6 @@ void MonitorDisplaySettingDlg::onDeviceComboBoxChanged(const QString& str) void MonitorDisplaySettingDlg::onStateComboBoxChanged(const QString& str) { - /*if(_tempSetting.contains(_curType)){ - monitorItemStateStruct keyState; - keyState.sState = str; - keyState.eState = monitorItemState(ui->cb_state->currentData().toInt()); - if(_tempSetting[_curType].contains(keyState)){ - saveSetting(_curType,_curState); - auto info = _tempSetting[_curType][keyState]; - ui->checkBox_custom->setChecked(info.bEnable); - if(_curModel != info.sModel){ - _curModel = info.sModel; - } - _curState = keyState; - loadSetting(_curType,_curState); - } - }*/ - if (str.isEmpty() || str == _curState.sState) { return; } diff --git a/diagramCavas/ui/diagramConnectSetting.ui b/diagramCavas/ui/diagramConnectSetting.ui new file mode 100644 index 0000000..28d1912 --- /dev/null +++ b/diagramCavas/ui/diagramConnectSetting.ui @@ -0,0 +1,469 @@ + + + diagramConnectSetting + + + + 0 + 0 + 393 + 502 + + + + + 12 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 21 + + + + background-color: rgb(196, 196, 196); + + + + 0 + + + 0 + + + + + color: rgb(0, 0, 0); +font: 12pt "Microsoft YaHei UI"; + + + 网络通信设置 + + + + + + + Qt::Orientation::Horizontal + + + + 302 + 18 + + + + + + + + + + + 0 + + + + HTTP设置 + + + + 8 + + + + + HTTP服务器配置 + + + + 4 + + + 8 + + + + + ms + + + + + + + 超时时间: + + + + + + + + + + 连接名称: + + + + + + + Qt::Orientation::Horizontal + + + + 217 + 20 + + + + + + + + + + + + 50 + 16777215 + + + + + + + + 服务器地址 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 连接测试 + + + + + + 测试连接 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 状态: + + + + + + + 未连接 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + WebSoket设置 + + + + + + WebSocket服务器配置 + + + + 8 + + + + + Qt::Orientation::Horizontal + + + + 214 + 20 + + + + + + + + 服务器地址 + + + + + + + 连接名称: + + + + + + + ms + + + + + + + + 50 + 16777215 + + + + + + + + 超时时间: + + + + + + + 心跳间隔: + + + + + + + + + + + 50 + 16777215 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + ms + + + + + + + Qt::Orientation::Horizontal + + + + 217 + 20 + + + + + + + + + + + 连接测试 + + + + + + 测试连接 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 状态: + + + + + + + 未连接 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 操作日志 + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 保存 + + + + + + + 取消 + + + + + + + + + + diff --git a/diagramCommunication/CMakeLists.txt b/diagramCommunication/CMakeLists.txt new file mode 100644 index 0000000..3c65312 --- /dev/null +++ b/diagramCommunication/CMakeLists.txt @@ -0,0 +1,58 @@ +project(diagramCommunication) + +set(DIAGRACOMMUNICATION_HEADER_FILES + include/channelConfig.h + include/baseChannel.h + include/communicationManager.h + include/httpChannel.h + include/webSocketChannel.h + include/configManager.h + include/uiCommunicationBus.h + include/dataProcessor.h + ../common/include/compiler.hpp + ../common/include/export.hpp + ../common/include/operatingSystem.hpp +) + +set(DIAGRACOMMUNICATION_SOURCE_FILES + source/communicationManager.cpp + source/baseChannel.cpp + source/webSocketChannel.cpp + source/httpChannel.cpp + source/configManager.cpp + source/uiCommunicationBus.cpp + source/dataProcessor.cpp +) + + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_library(diagramCommunication SHARED + MANUAL_FINALIZATION + ${DIAGRACOMMUNICATION_HEADER_FILES} + ${DIAGRACOMMUNICATION_SOURCE_FILES} + ) +else() + add_library(diagramCommunication SHARED + ${DIAGRACOMMUNICATION_HEADER_FILES} + ${DIAGRACOMMUNICATION_SOURCE_FILES} + ) +endif() + +target_link_libraries(diagramCommunication PUBLIC Qt${QT_VERSION_MAJOR}::Core) +target_link_libraries(diagramCommunication PRIVATE Qt6::Xml) +target_link_libraries(diagramCommunication PRIVATE Qt6::Network) +target_link_libraries(diagramCommunication PRIVATE Qt6::WebSockets) +target_link_libraries(diagramCommunication PRIVATE Qt6::Sql ${PostgreSQL_LIBRARIES}) + +option(BUILD_SHARED_LIBS "Build as shared library" ON) + + +target_include_directories(diagramCommunication PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +target_compile_definitions(diagramCommunication + PUBLIC + DIAGRAM_DESIGNER_SHARED + PRIVATE + DIAGRAM_DESIGNER_EXPORTS + #QT_NO_KEYWORDS +) diff --git a/diagramCommunication/include/ConfigManager.h b/diagramCommunication/include/ConfigManager.h new file mode 100644 index 0000000..f673c11 --- /dev/null +++ b/diagramCommunication/include/ConfigManager.h @@ -0,0 +1,49 @@ +// ConfigManager.h +#pragma once + +#include "channelConfig.h" +#include +#include +#include +#include +#include "export.hpp" + +class DIAGRAM_DESIGNER_PUBLIC ConfigManager : public QObject +{ + Q_OBJECT + +public: + static ConfigManager* instance(); + + // 加载配置 + bool loadConfig(const QString& configFile = ""); + + // 保存配置 + bool saveConfig(); + + // 获取配置 + ChannelConfig getHttpConfig() const; + ChannelConfig getWebSocketConfig() const; + + // 更新配置 + void setHttpConfig(const ChannelConfig& config); + void setWebSocketConfig(const ChannelConfig& config); + + // 获取配置路径 + QString configFilePath() const; + +signals: + void configLoaded(); + void configSaved(); + void httpConfigChanged(const ChannelConfig& config); + void websocketConfigChanged(const ChannelConfig& config); +private: + ConfigManager(QObject* parent = nullptr); + bool createDefaultConfig(); + ChannelConfig getDefaultHttpConfig() const; + ChannelConfig getDefaultWebSocketConfig() const; + + ChannelConfig m_httpConfig; + ChannelConfig m_websocketConfig; + QString m_configFile; +}; diff --git a/diagramCommunication/include/baseChannel.h b/diagramCommunication/include/baseChannel.h new file mode 100644 index 0000000..eae7411 --- /dev/null +++ b/diagramCommunication/include/baseChannel.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "export.hpp" + +class DIAGRAM_DESIGNER_PUBLIC BaseChannel : public QObject +{ + Q_OBJECT + +public: + struct ChannelConfig { + QString channelId; + QUrl endpoint; + int timeout = 30000; // 超时时间(ms) + int reconnectInterval = 5000; // 重连间隔(ms) + int maxRetries = 3; // 最大重试次数 + QVariantMap params; // 自定义参数 + }; + + explicit BaseChannel(const ChannelConfig& config, QObject* parent = nullptr); + virtual ~BaseChannel(); + + // 连接管理 + virtual bool connect() = 0; + virtual bool disconnect() = 0; + virtual bool isConnected() const = 0; + + // 数据发送 + virtual bool send(const QByteArray& data) = 0; + + // 信息获取 + QString channelId() const { return m_config.channelId; } + ChannelConfig config() const { return m_config; } + QUrl endpoint() const { return m_config.endpoint; } + + // 控制 + void setAutoReconnect(bool enable); + bool isAutoReconnect() const { return m_autoReconnect; } + +signals: + void connected(); + void disconnected(); + void dataReceived(const QByteArray& data); + void errorOccurred(const QString& error); + +protected: + // 公共方法 + void startReconnectTimer(); + void stopReconnectTimer(); + void reconnect(); + + // 工具方法 + QByteArray generateMessageId() const; + qint64 currentTimestamp() const; + + // 成员变量 + ChannelConfig m_config; + bool m_autoReconnect = true; + +private slots: + void onReconnectTimeout(); + +private: + QTimer* m_reconnectTimer = nullptr; + int m_reconnectCount = 0; + QMutex m_mutex; +}; diff --git a/diagramCommunication/include/channelConfig.h b/diagramCommunication/include/channelConfig.h new file mode 100644 index 0000000..2444b5d --- /dev/null +++ b/diagramCommunication/include/channelConfig.h @@ -0,0 +1,74 @@ +// ChannelConfig.h +#pragma once + +#include +#include +#include +#include +#include +#include "export.hpp" + +// 简化配置结构 +struct DIAGRAM_DESIGNER_PUBLIC ChannelConfig { + // 通用配置 + QString id; // 通道ID: "http_channel" 或 "websocket_channel" + QString name; // 通道名称 + QString endpoint; // 连接地址 + int timeout = 30000; // 超时时间(ms) + bool enabled = true; // 是否启用 + bool autoConnect = false; // 是否自动连接 + + // 认证 + QString username; + QString password; + + // 状态 + bool connected = false; + QDateTime lastConnectTime; + int errorCount = 0; + + // HTTP特有 + QVariantMap headers; // HTTP头 + + // WebSocket特有 + int heartbeatInterval = 30000; // 心跳间隔 + + // 转换为Map + QVariantMap toMap() const { + return { + {"id", id}, + {"name", name}, + {"endpoint", endpoint}, + {"timeout", timeout}, + {"enabled", enabled}, + {"autoConnect", autoConnect}, + {"username", username}, + {"password", password}, + {"connected", connected}, + {"lastConnectTime", lastConnectTime.toString(Qt::ISODate)}, + {"errorCount", errorCount}, + {"headers", headers}, + {"heartbeatInterval", heartbeatInterval} + }; + } + + // 从Map创建 + static ChannelConfig fromMap(const QVariantMap& map) { + ChannelConfig config; + config.id = map.value("id").toString(); + config.name = map.value("name").toString(); + config.endpoint = map.value("endpoint").toString(); + config.timeout = map.value("timeout", 30000).toInt(); + config.enabled = map.value("enabled", true).toBool(); + config.autoConnect = map.value("autoConnect", false).toBool(); + config.username = map.value("username").toString(); + config.password = map.value("password").toString(); + config.connected = map.value("connected", false).toBool(); + config.lastConnectTime = QDateTime::fromString( + map.value("lastConnectTime").toString(), Qt::ISODate); + config.errorCount = map.value("errorCount", 0).toInt(); + config.headers = map.value("headers").toMap(); + config.heartbeatInterval = map.value("heartbeatInterval", 30000).toInt(); + return config; + } +}; diff --git a/diagramCommunication/include/communicationManager.h b/diagramCommunication/include/communicationManager.h new file mode 100644 index 0000000..308f301 --- /dev/null +++ b/diagramCommunication/include/communicationManager.h @@ -0,0 +1,73 @@ +// CommunicationManager.h +#pragma once + +#include "channelConfig.h" +#include "httpChannel.h" +#include "webSocketChannel.h" +#include +#include +#include "export.hpp" + +class DIAGRAM_DESIGNER_PUBLIC CommunicationManager : public QObject +{ + Q_OBJECT + +public: + static CommunicationManager* instance(); + + // 初始化 + bool initialize(); + + // HTTP通道操作 + bool connectHttp(); + bool disconnectHttp(); + bool sendHttpRequest(const QString& path, + const QByteArray& data = QByteArray(), + const QString& method = "GET"); + + // WebSocket通道操作 + bool connectWebSocket(); + bool disconnectWebSocket(); + bool sendWebSocketMessage(const QByteArray& data); + bool sendWebSocketText(const QString& text); + + // 状态查询 + bool isHttpConnected() const; + bool isWebSocketConnected() const; + ChannelConfig getHttpConfig() const; + ChannelConfig getWebSocketConfig() const; + + // 配置更新 + void updateHttpConfig(const ChannelConfig& config); + void updateWebSocketConfig(const ChannelConfig& config); + +signals: + // HTTP通道信号 + void httpConnected(); + void httpDisconnected(); + void httpDataReceived(const QByteArray& data); + void httpError(const QString& error); + + // WebSocket通道信号 + void websocketConnected(); + void websocketDisconnected(); + void websocketDataReceived(const QByteArray& data); + void websocketTextReceived(const QString& text); + void websocketError(const QString& error); + +private: + CommunicationManager(QObject* parent = nullptr); + ~CommunicationManager(); + + // 内部初始化 + void initHttpChannel(); + void initWebSocketChannel(); + + // 配置 + ChannelConfig m_httpConfig; + ChannelConfig m_websocketConfig; + + // 通道实例 + QSharedPointer m_httpChannel; + QSharedPointer m_websocketChannel; +}; diff --git a/diagramCommunication/include/dataProcessor.h b/diagramCommunication/include/dataProcessor.h new file mode 100644 index 0000000..1840c55 --- /dev/null +++ b/diagramCommunication/include/dataProcessor.h @@ -0,0 +1,39 @@ +// DataProcessor.h +#pragma once + +#include +#include +#include +#include +#include "export.hpp" + +// 网络数据处理中心 +class DIAGRAM_DESIGNER_PUBLIC DataProcessor : public QObject +{ + Q_OBJECT + +public: + static DataProcessor* instance(); + + // 处理数据 + void processData(const QVariant& data,int conType = 0); + + // 获取处理后的数据 + QVariant getProcessedData(const QString& key) const; + + // 清除所有数据 + void clearAllData(); + +signals: + // 数据处理完成信号 + void httpProcessed(const QString& sType,const QVariant& data); + void websocketProcessed(const QVariant& data); +private: + DataProcessor(QObject* parent = nullptr); + // 通用处理函数 + void processJson(const QVariant& data,int conType = 0); //0http 1websocket + + // 数据缓存 + QMap m_dataCache; + mutable QMutex m_mutex; +}; diff --git a/diagramCommunication/include/httpChannel.h b/diagramCommunication/include/httpChannel.h new file mode 100644 index 0000000..8c7e546 --- /dev/null +++ b/diagramCommunication/include/httpChannel.h @@ -0,0 +1,32 @@ +// HttpChannel.h +#pragma once + +#include "baseChannel.h" + +class DIAGRAM_DESIGNER_PUBLIC HttpChannel : public BaseChannel +{ + Q_OBJECT + +public: + HttpChannel(const ChannelConfig& config, QObject* parent = nullptr); + + bool connect() override; + bool disconnect() override; + bool isConnected() const override; + bool send(const QByteArray& data) override; + + // HTTP方法 + bool get(const QString& path = ""); + bool post(const QByteArray& data, const QString& path = ""); + bool put(const QByteArray& data, const QString& path = ""); + bool deleteResource(const QString& path = ""); + + // 配置 + void setBasicAuth(const QString& username, const QString& password); + void setHeader(const QString& name, const QString& value); + +private: + QString m_username; + QString m_password; + QMap m_headers; +}; diff --git a/diagramCommunication/include/uiCommunicationBus.h b/diagramCommunication/include/uiCommunicationBus.h new file mode 100644 index 0000000..d1afc05 --- /dev/null +++ b/diagramCommunication/include/uiCommunicationBus.h @@ -0,0 +1,48 @@ +// uiCommunicationBus.h +#pragma once + +#include "export.hpp" +#include +#include +#include +#include + +// UI通信总线 +class DIAGRAM_DESIGNER_PUBLIC UiCommunicationBus : public QObject +{ + Q_OBJECT + +public: + static UiCommunicationBus* instance(); + + // 发送HTTP请求 + void sendHttpRequest(const QString& endpoint, const QVariant& data = QVariant()); + + // 发送HTTP请求(无回复) + void sendHttpRequestNoReply(const QString& endpoint, const QVariant& data = QVariant()); + + // 向UI发送数据 + void sendToUi(const QString& uiId, const QString& action, const QVariant& data); + + // 广播到所有UI + void broadcastToUis(const QString& action, const QVariant& data); + + // 注册/注销UI + void registerUi(const QString& uiId, QObject* uiObject); + void unregisterUi(const QString& uiId); +signals: + void httpDataProcessed(const QString& type,const QVariant& data); //发送分拣过的数据给外部 + void websocketDataProcessed(const QVariant& data); +private: + UiCommunicationBus(QObject* parent = nullptr); + + // 处理HTTP响应 + void onHttpDataReceived(const QByteArray& data); + + // 处理WebSocket数据 + void onWebSocketDataReceived(const QByteArray& data); + + // UI注册表 + QMap m_uiObjects; + mutable QMutex m_mutex; +}; diff --git a/diagramCommunication/include/webSocketChannel.h b/diagramCommunication/include/webSocketChannel.h new file mode 100644 index 0000000..4cca44a --- /dev/null +++ b/diagramCommunication/include/webSocketChannel.h @@ -0,0 +1,38 @@ +// WebSocketChannel.h +#pragma once + +#include "baseChannel.h" +#include + +class DIAGRAM_DESIGNER_PUBLIC WebSocketChannel : public BaseChannel +{ + Q_OBJECT + +public: + struct WebSocketConfig { + int heartbeatInterval = 30000; + }; + + WebSocketChannel(const ChannelConfig& config, QObject* parent = nullptr); + + bool connect() override; + bool disconnect() override; + bool isConnected() const override; + bool send(const QByteArray& data) override; + + bool sendText(const QString& text); + void setWebSocketConfig(const WebSocketConfig& config); + +signals: + void textMessageReceived(const QString& message); + +private slots: + void onConnected(); + void onDisconnected(); + void onTextMessageReceived(const QString& message); + void onBinaryMessageReceived(const QByteArray& message); + +private: + QWebSocket* m_webSocket = nullptr; + WebSocketConfig m_wsConfig; +}; diff --git a/diagramCommunication/source/baseChannel.cpp b/diagramCommunication/source/baseChannel.cpp new file mode 100644 index 0000000..bc31faa --- /dev/null +++ b/diagramCommunication/source/baseChannel.cpp @@ -0,0 +1,76 @@ +#include "baseChannel.h" +#include +#include +#include + +BaseChannel::BaseChannel(const ChannelConfig& config, QObject* parent) + : QObject(parent) + , m_config(config) + , m_reconnectTimer(new QTimer(this)) +{ + m_reconnectTimer->setSingleShot(true); + QObject::connect(m_reconnectTimer,&QTimer::timeout, this, &BaseChannel::onReconnectTimeout); + + qDebug() << "BaseChannel created:" << m_config.channelId; +} + +BaseChannel::~BaseChannel() +{ + stopReconnectTimer(); + qDebug() << "BaseChannel destroyed:" << m_config.channelId; +} + +void BaseChannel::setAutoReconnect(bool enable) +{ + m_autoReconnect = enable; + if (!enable) { + stopReconnectTimer(); + } +} + +void BaseChannel::startReconnectTimer() +{ + if (m_autoReconnect && m_reconnectCount < m_config.maxRetries) { + int delay = m_config.reconnectInterval * (1 << m_reconnectCount); // 指数退避 + m_reconnectTimer->start(qMin(delay, 30000)); // 最大30秒 + m_reconnectCount++; + } +} + +void BaseChannel::stopReconnectTimer() +{ + m_reconnectTimer->stop(); + m_reconnectCount = 0; +} + +void BaseChannel::reconnect() +{ + if (m_autoReconnect) { + disconnect(); + QTimer::singleShot(100, this, [this]() { + connect(); + }); + } +} + +void BaseChannel::onReconnectTimeout() +{ + if (m_autoReconnect) { + qDebug() << "Reconnecting channel" << m_config.channelId + << "attempt" << m_reconnectCount << "/" << m_config.maxRetries; + connect(); + } +} + +QByteArray BaseChannel::generateMessageId() const +{ + QString id = QString("%1_%2") + .arg(m_config.channelId) + .arg(QDateTime::currentMSecsSinceEpoch()); + return QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); +} + +qint64 BaseChannel::currentTimestamp() const +{ + return QDateTime::currentMSecsSinceEpoch(); +} diff --git a/diagramCommunication/source/communicationManager.cpp b/diagramCommunication/source/communicationManager.cpp new file mode 100644 index 0000000..f579ca2 --- /dev/null +++ b/diagramCommunication/source/communicationManager.cpp @@ -0,0 +1,266 @@ +// CommunicationManager.cpp +#include "communicationManager.h" +#include + +CommunicationManager* CommunicationManager::instance() +{ + static CommunicationManager* instance = nullptr; + static QMutex mutex; + + if (!instance) { + QMutexLocker locker(&mutex); + if (!instance) { + instance = new CommunicationManager; + } + } + return instance; +} + +CommunicationManager::CommunicationManager(QObject* parent) + : QObject(parent) +{ + // 设置默认配置 + m_httpConfig.id = "http_channel"; + m_httpConfig.name = "HTTP通道"; + m_httpConfig.endpoint = "http://localhost:8080"; + + m_websocketConfig.id = "websocket_channel"; + m_websocketConfig.name = "WebSocket通道"; + m_websocketConfig.endpoint = "ws://localhost:8888/ws"; +} + +CommunicationManager::~CommunicationManager() +{ + disconnectHttp(); + disconnectWebSocket(); +} + +bool CommunicationManager::initialize() +{ + // 初始化HTTP通道 + initHttpChannel(); + + // 初始化WebSocket通道 + initWebSocketChannel(); + + qInfo() << "CommunicationManager initialized"; + return true; +} + +void CommunicationManager::initHttpChannel() +{ + if (m_httpChannel) { + m_httpChannel->disconnect(); + } + + // 创建HTTP通道 + HttpChannel::ChannelConfig httpConfig; + httpConfig.endpoint = QUrl(m_httpConfig.endpoint); + httpConfig.timeout = m_httpConfig.timeout; + + m_httpChannel.reset(new HttpChannel(httpConfig)); + + // 设置认证 + if (!m_httpConfig.username.isEmpty() && !m_httpConfig.password.isEmpty()) { + m_httpChannel->setBasicAuth(m_httpConfig.username, m_httpConfig.password); + } + + // 设置HTTP头 + for (auto it = m_httpConfig.headers.begin(); it != m_httpConfig.headers.end(); ++it) { + m_httpChannel->setHeader(it.key(), it.value().toString()); + } + + // 连接信号 + connect(m_httpChannel.data(), &HttpChannel::connected, + this, &CommunicationManager::httpConnected); + connect(m_httpChannel.data(), &HttpChannel::disconnected, + this, &CommunicationManager::httpDisconnected); + connect(m_httpChannel.data(), &HttpChannel::dataReceived, + this, &CommunicationManager::httpDataReceived); + connect(m_httpChannel.data(), &HttpChannel::errorOccurred, + this, &CommunicationManager::httpError); +} + +void CommunicationManager::initWebSocketChannel() +{ + if (m_websocketChannel) { + m_websocketChannel->disconnect(); + } + + // 创建WebSocket通道 + WebSocketChannel::ChannelConfig wsConfig; + wsConfig.endpoint = QUrl(m_websocketConfig.endpoint); + wsConfig.timeout = m_websocketConfig.timeout; + + WebSocketChannel::WebSocketConfig websocketConfig; + websocketConfig.heartbeatInterval = m_websocketConfig.heartbeatInterval; + + m_websocketChannel.reset(new WebSocketChannel(wsConfig)); + m_websocketChannel->setWebSocketConfig(websocketConfig); + + // 连接信号 + connect(m_websocketChannel.data(), &WebSocketChannel::connected, + this, &CommunicationManager::websocketConnected); + connect(m_websocketChannel.data(), &WebSocketChannel::disconnected, + this, &CommunicationManager::websocketDisconnected); + connect(m_websocketChannel.data(), &WebSocketChannel::dataReceived, + this, &CommunicationManager::websocketDataReceived); + connect(m_websocketChannel.data(), &WebSocketChannel::errorOccurred, + this, &CommunicationManager::websocketError); + connect(m_websocketChannel.data(), &WebSocketChannel::textMessageReceived, + this, &CommunicationManager::websocketTextReceived); +} + +bool CommunicationManager::connectHttp() +{ + if (!m_httpChannel) { + qWarning() << "HTTP channel not initialized"; + return false; + } + + if (m_httpChannel->isConnected()) { + return true; + } + + return m_httpChannel->connect(); +} + +bool CommunicationManager::disconnectHttp() +{ + if (!m_httpChannel) { + return false; + } + + if (!m_httpChannel->isConnected()) { + return true; + } + + return m_httpChannel->disconnect(); +} + +bool CommunicationManager::sendHttpRequest(const QString& path, + const QByteArray& data, + const QString& method) +{ + if (!m_httpChannel || !m_httpChannel->isConnected()) { + qWarning() << "HTTP channel not connected"; + return false; + } + + if (method == "GET") { + return m_httpChannel->get(path); + } else if (method == "POST") { + return m_httpChannel->post(data, path); + } else if (method == "PUT") { + return m_httpChannel->put(data, path); + } else if (method == "DELETE") { + return m_httpChannel->deleteResource(path); + } + + qWarning() << "Unsupported HTTP method:" << method; + return false; +} + +bool CommunicationManager::connectWebSocket() +{ + if (!m_websocketChannel) { + qWarning() << "WebSocket channel not initialized"; + return false; + } + + if (m_websocketChannel->isConnected()) { + return true; + } + + return m_websocketChannel->connect(); +} + +bool CommunicationManager::disconnectWebSocket() +{ + if (!m_websocketChannel) { + return false; + } + + if (!m_websocketChannel->isConnected()) { + return true; + } + + return m_websocketChannel->disconnect(); +} + +bool CommunicationManager::sendWebSocketMessage(const QByteArray& data) +{ + if (!m_websocketChannel || !m_websocketChannel->isConnected()) { + qWarning() << "WebSocket channel not connected"; + return false; + } + + return m_websocketChannel->send(data); +} + +bool CommunicationManager::sendWebSocketText(const QString& text) +{ + if (!m_websocketChannel || !m_websocketChannel->isConnected()) { + qWarning() << "WebSocket channel not connected"; + return false; + } + + return m_websocketChannel->sendText(text); +} + +bool CommunicationManager::isHttpConnected() const +{ + return m_httpChannel && m_httpChannel->isConnected(); +} + +bool CommunicationManager::isWebSocketConnected() const +{ + return m_websocketChannel && m_websocketChannel->isConnected(); +} + +ChannelConfig CommunicationManager::getHttpConfig() const +{ + return m_httpConfig; +} + +ChannelConfig CommunicationManager::getWebSocketConfig() const +{ + return m_websocketConfig; +} + +void CommunicationManager::updateHttpConfig(const ChannelConfig& config) +{ + bool reconnect = false; + + if (m_httpConfig.endpoint != config.endpoint) { + // 端点变化,需要重新初始化 + reconnect = true; + } + + m_httpConfig = config; + initHttpChannel(); + + if (reconnect && config.autoConnect) { + connectHttp(); + } + + qInfo() << "HTTP config updated"; +} + +void CommunicationManager::updateWebSocketConfig(const ChannelConfig& config) +{ + bool reconnect = false; + + if (m_websocketConfig.endpoint != config.endpoint) { + reconnect = true; + } + + m_websocketConfig = config; + initWebSocketChannel(); + + if (reconnect && config.autoConnect) { + connectWebSocket(); + } + + qInfo() << "WebSocket config updated"; +} diff --git a/diagramCommunication/source/configManager.cpp b/diagramCommunication/source/configManager.cpp new file mode 100644 index 0000000..61ec1b0 --- /dev/null +++ b/diagramCommunication/source/configManager.cpp @@ -0,0 +1,217 @@ +// ConfigManager.cpp +#include "configManager.h" +#include +#include +#include +#include +#include +#include +#include + +ConfigManager* ConfigManager::instance() +{ + static ConfigManager* instance = nullptr; + static QMutex mutex; + + if (!instance) { + QMutexLocker locker(&mutex); + if (!instance) { + instance = new ConfigManager; + } + } + return instance; +} + +ConfigManager::ConfigManager(QObject* parent) + : QObject(parent) +{ + // 设置默认配置路径 + QString appDir = QCoreApplication::applicationDirPath(); + m_configFile = appDir + "/config.json"; + + qDebug() << "ConfigManager initialized, config file:" << m_configFile; +} + +bool ConfigManager::loadConfig(const QString& configFile) +{ + if (!configFile.isEmpty()) { + m_configFile = configFile; + } + + QFile file(m_configFile); + + // 如果文件不存在,创建默认配置 + if (!file.exists()) { + qWarning() << "Config file not found, creating default config:" << m_configFile; + return createDefaultConfig(); + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Failed to open config file:" << m_configFile; + return false; + } + + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); + + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parse error:" << error.errorString(); + return false; + } + + if (!doc.isObject()) { + qWarning() << "Config is not a JSON object"; + return false; + } + + QVariantMap root = doc.object().toVariantMap(); + + // 加载HTTP配置 + if (root.contains("http")) { + m_httpConfig = ChannelConfig::fromMap(root["http"].toMap()); + } else { + qWarning() << "Config missing 'http' section, creating default"; + m_httpConfig = getDefaultHttpConfig(); + } + + // 加载WebSocket配置 + if (root.contains("websocket")) { + m_websocketConfig = ChannelConfig::fromMap(root["websocket"].toMap()); + } else { + qWarning() << "Config missing 'websocket' section, creating default"; + m_websocketConfig = getDefaultWebSocketConfig(); + } + + qInfo() << "Config loaded from:" << m_configFile; + qInfo() << "HTTP endpoint:" << m_httpConfig.endpoint; + qInfo() << "WebSocket endpoint:" << m_websocketConfig.endpoint; + + emit configLoaded(); + + return true; +} + +bool ConfigManager::saveConfig() +{ + // 确保目录存在 + QFileInfo fileInfo(m_configFile); + QDir dir = fileInfo.dir(); + + if (!dir.exists()) { + if (!dir.mkpath(".")) { + qCritical() << "Failed to create config directory:" << dir.path(); + return false; + } + } + + QVariantMap root = { + {"http", m_httpConfig.toMap()}, + {"websocket", m_websocketConfig.toMap()}, + {"lastSaved", QDateTime::currentDateTime().toString(Qt::ISODate)} + }; + + QFile file(m_configFile); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + qCritical() << "Failed to open config file for writing:" << m_configFile; + return false; + } + + QJsonDocument doc = QJsonDocument::fromVariant(root); + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + + qInfo() << "Config saved to:" << m_configFile; + emit configSaved(); + + return true; +} + +bool ConfigManager::createDefaultConfig() +{ + // 设置默认配置 + m_httpConfig = getDefaultHttpConfig(); + m_websocketConfig = getDefaultWebSocketConfig(); + + // 保存默认配置 + bool success = saveConfig(); + + if (success) { + qInfo() << "Default config created at:" << m_configFile; + } else { + qCritical() << "Failed to create default config"; + } + + return success; +} + +ChannelConfig ConfigManager::getDefaultHttpConfig() const +{ + ChannelConfig config; + + config.id = "http_channel"; + config.name = "SCADA数据接口"; + config.endpoint = "http://192.168.1.100:8080/api"; + config.timeout = 30000; + config.enabled = true; + config.autoConnect = true; + config.username = ""; + config.password = ""; + config.connected = false; + config.lastConnectTime = QDateTime(); + config.errorCount = 0; + + // 默认HTTP头 + config.headers = QVariantMap{ + {"Content-Type", "application/json"}, + }; + + return config; +} + +ChannelConfig ConfigManager::getDefaultWebSocketConfig() const +{ + ChannelConfig config; + + config.id = "websocket_channel"; + config.name = "实时数据推送"; + config.endpoint = "ws://192.168.1.101:8888/ws"; + config.timeout = 30000; + config.enabled = true; + config.autoConnect = false; + config.connected = false; + config.lastConnectTime = QDateTime(); + config.errorCount = 0; + config.heartbeatInterval = 30000; + + return config; +} + +ChannelConfig ConfigManager::getHttpConfig() const +{ + return m_httpConfig; +} + +ChannelConfig ConfigManager::getWebSocketConfig() const +{ + return m_websocketConfig; +} + +void ConfigManager::setHttpConfig(const ChannelConfig& config) +{ + m_httpConfig = config; + emit httpConfigChanged(config); +} + +void ConfigManager::setWebSocketConfig(const ChannelConfig& config) +{ + m_websocketConfig = config; + emit websocketConfigChanged(config); +} + +QString ConfigManager::configFilePath() const +{ + return m_configFile; +} diff --git a/diagramCommunication/source/dataProcessor.cpp b/diagramCommunication/source/dataProcessor.cpp new file mode 100644 index 0000000..c40be5d --- /dev/null +++ b/diagramCommunication/source/dataProcessor.cpp @@ -0,0 +1,85 @@ +// DataProcessor.cpp +#include "dataProcessor.h" +#include +#include +#include +#include + +DataProcessor* DataProcessor::instance() +{ + static DataProcessor* instance = nullptr; + static QMutex mutex; + + if (!instance) { + QMutexLocker locker(&mutex); + if (!instance) { + instance = new DataProcessor; + } + } + return instance; +} + +DataProcessor::DataProcessor(QObject* parent) + : QObject(parent) +{ + qDebug() << "DataProcessor initialized"; +} + +void DataProcessor::processData(const QVariant& data,int conType) +{ + qDebug() << "处理数据_size:" << data.toString().size(); + + // 根据数据类型处理 + processJson(data.toJsonObject(),conType); +} + +void DataProcessor::processJson( const QVariant& data,int conType) +{ + QJsonObject dataObj = data.toJsonObject(); + if(conType == 0){ + if(dataObj.contains("client_id")){ //实时数据相关 + emit httpProcessed("recommend",data); + } + else if(dataObj.contains("input")){ + emit httpProcessed("subscriptions",data); + } + } + else if(conType == 1){ + if(dataObj.contains("targets")){ //实时数据相关 + emit websocketProcessed(data); + } + } + + if(dataObj.contains("client_id")){ //实时数据相关 + /*QString clientId = dataObj.value("client_id").toString(); + QJsonArray arrTarget = dataObj.value("targets").toArray(); + for (const QJsonValue& value : arrTarget) { + QJsonObject obj = value.toObject(); + QString targetId = obj["id"].toString(); + QString sCode = obj["code"].toString(); + }*/ + } + else if(dataObj.contains("input")){ + /*QString input = dataObj.value("input").toString(); + int offSet = dataObj.value("offset").toInt(); + QJsonArray recommendedList = dataObj.value("recommended_list").toArray(); + for(const QJsonValue& value : recommendedList){ + QString content = value.toString(); + }*/ + } + //QMutexLocker locker(&m_mutex); + //m_dataCache[dataType] = data; +} + +QVariant DataProcessor::getProcessedData(const QString& key) const +{ + QMutexLocker locker(&m_mutex); + return m_dataCache.value(key); +} + +void DataProcessor::clearAllData() +{ + QMutexLocker locker(&m_mutex); + m_dataCache.clear(); + qDebug() << "已清除所有处理数据"; +} diff --git a/diagramCommunication/source/httpAdapter.cpp b/diagramCommunication/source/httpAdapter.cpp new file mode 100644 index 0000000..f60b016 --- /dev/null +++ b/diagramCommunication/source/httpAdapter.cpp @@ -0,0 +1,260 @@ +// HttpAdapter.cpp +#include "httpChannel.h" +#include +#include +#include +#include +#include +#include +#include + +HttpAdapter::HttpAdapter(const ChannelConfig& config, QObject* parent) + : CommunicationChannel(config, parent) + , m_networkManager(new QNetworkAccessManager(this)) +{ + // 配置SSL + if (m_config.sslConfig.isNull()) { + QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); + sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); + m_config.sslConfig = sslConfig; + } + + // 连接信号 + connect(m_networkManager, &QNetworkAccessManager::authenticationRequired, + this, &HttpAdapter::onAuthenticationRequired); + + qDebug() << "HttpAdapter created for" << m_config.channelId; +} + +HttpAdapter::~HttpAdapter() +{ + // 取消所有未完成的请求 + for (auto reply : m_pendingRequests.keys()) { + reply->abort(); + reply->deleteLater(); + } + m_pendingRequests.clear(); +} + +bool HttpAdapter::connectToHost() +{ + // HTTP协议无需建立持久连接,只需验证端点是否可达 + if (m_config.endpoint.isEmpty() || !m_config.endpoint.isValid()) { + emit errorOccurred("Invalid endpoint URL: " + m_config.endpoint.toString()); + return false; + } + + // 发送一个测试请求验证连接 + HttpRequest testRequest; + testRequest.method = GET; + + QNetworkRequest request(m_config.endpoint); + request.setSslConfiguration(m_config.sslConfig); + request.setRawHeader("User-Agent", "PowerSCADA/1.0"); + + QNetworkReply* reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, + this, [this, reply]() { + onReplyFinished(reply); + }); + + m_pendingRequests.insert(reply, testRequest); + + // 设置超时 + QTimer::singleShot(m_config.timeout, this, [this, reply]() { + if (reply && reply->isRunning()) { + reply->abort(); + emit errorOccurred("Connection timeout"); + } + }); + + emit connected(); + return true; +} + +bool HttpAdapter::disconnectFromHost() +{ + // 取消所有未完成的请求 + for (auto reply : m_pendingRequests.keys()) { + reply->abort(); + reply->deleteLater(); + } + m_pendingRequests.clear(); + + emit disconnected(); + return true; +} + +bool HttpAdapter::sendData(const QByteArray& data) +{ + HttpRequest request; + request.method = POST; + request.data = data; + request.contentType = "application/octet-stream"; + + return sendRequest(request); +} + +bool HttpAdapter::isConnected() const +{ + // HTTP是无状态协议,总是返回true表示可以发送请求 + return m_config.endpoint.isValid(); +} + +bool HttpAdapter::sendRequest(const HttpRequest& request) +{ + if (!m_config.endpoint.isValid()) { + emit errorOccurred("Invalid endpoint URL"); + return false; + } + + QNetworkRequest networkRequest(m_config.endpoint); + networkRequest.setSslConfiguration(m_config.sslConfig); + networkRequest.setRawHeader("User-Agent", "PowerSCADA/1.0"); + networkRequest.setRawHeader("Content-Type", request.contentType.toUtf8()); + + // 添加自定义头 + for (auto it = request.headers.begin(); it != request.headers.end(); ++it) { + networkRequest.setRawHeader(it.key().toUtf8(), it.value().toUtf8()); + } + + QNetworkReply* reply = nullptr; + + switch (request.method) { + case GET: + reply = m_networkManager->get(networkRequest); + break; + case POST: + reply = m_networkManager->post(networkRequest, request.data); + break; + case PUT: + reply = m_networkManager->put(networkRequest, request.data); + break; + case DELETE: + reply = m_networkManager->deleteResource(networkRequest); + break; + default: + emit errorOccurred("Unsupported HTTP method"); + return false; + } + + if (!reply) { + emit errorOccurred("Failed to create network request"); + return false; + } + + // 设置请求超时 + QTimer* timeoutTimer = new QTimer(this); + timeoutTimer->setSingleShot(true); + connect(timeoutTimer, &QTimer::timeout, this, [this, reply]() { + if (reply && reply->isRunning()) { + reply->abort(); + emit errorOccurred("Request timeout"); + } + }); + timeoutTimer->start(m_config.timeout); + + connect(reply, &QNetworkReply::finished, this, [this, reply, timeoutTimer]() { + timeoutTimer->stop(); + timeoutTimer->deleteLater(); + onReplyFinished(reply); + }); + + m_pendingRequests.insert(reply, request); + return true; +} + +void HttpAdapter::setAuthentication(const QString& username, const QString& password) +{ + m_username = username; + m_password = password; +} + +void HttpAdapter::setProxy(const QNetworkProxy& proxy) +{ + m_networkManager->setProxy(proxy); +} + +bool HttpAdapter::readDataPoints(const QStringList& pointIds) +{ + QJsonObject requestObj; + requestObj["command"] = "read"; + requestObj["points"] = QJsonArray::fromStringList(pointIds); + requestObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); + + HttpRequest request; + request.method = POST; + request.data = QJsonDocument(requestObj).toJson(); + request.contentType = "application/json"; + + return sendRequest(request); +} + +bool HttpAdapter::writeDataPoint(const QString& pointId, const QVariant& value) +{ + QJsonObject requestObj; + requestObj["command"] = "write"; + requestObj["point"] = pointId; + requestObj["value"] = QJsonValue::fromVariant(value); + requestObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); + + HttpRequest request; + request.method = POST; + request.data = QJsonDocument(requestObj).toJson(); + request.contentType = "application/json"; + + return sendRequest(request); +} + +bool HttpAdapter::sendControlCommand(const QString& deviceId, int command, const QVariant& param) +{ + QJsonObject requestObj; + requestObj["command"] = "control"; + requestObj["device"] = deviceId; + requestObj["cmd"] = command; + requestObj["param"] = QJsonValue::fromVariant(param); + requestObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); + + HttpRequest request; + request.method = POST; + request.data = QJsonDocument(requestObj).toJson(); + request.contentType = "application/json"; + + return sendRequest(request); +} + +void HttpAdapter::onReplyFinished(QNetworkReply* reply) +{ + if (!reply || !m_pendingRequests.contains(reply)) { + return; + } + + auto request = m_pendingRequests.take(reply); + + if (reply->error() != QNetworkReply::NoError) { + QString errorMsg = QString("HTTP error: %1 - %2") + .arg(reply->error()) + .arg(reply->errorString()); + emit errorOccurred(errorMsg); + } else { + QByteArray responseData = reply->readAll(); + emit dataReceived(responseData); + + // 记录日志 + qDebug() << "HTTP response received:" << reply->url().toString() + << "Status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() + << "Size:" << responseData.size() << "bytes"; + } + + reply->deleteLater(); +} + +void HttpAdapter::onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator) +{ + if (!m_username.isEmpty() && !m_password.isEmpty()) { + authenticator->setUser(m_username); + authenticator->setPassword(m_password); + } else { + qWarning() << "Authentication required but no credentials provided"; + } +} diff --git a/diagramCommunication/source/httpChannel.cpp b/diagramCommunication/source/httpChannel.cpp new file mode 100644 index 0000000..75979a4 --- /dev/null +++ b/diagramCommunication/source/httpChannel.cpp @@ -0,0 +1,143 @@ +// HttpChannel.cpp +#include "httpChannel.h" +#include +#include +#include +#include +#include + +HttpChannel::HttpChannel(const ChannelConfig& config, QObject* parent) + : BaseChannel(config, parent) +{ +} + +bool HttpChannel::connect() +{ + // HTTP无需建立持久连接 + emit connected(); + return true; +} + +bool HttpChannel::disconnect() +{ + // HTTP无需断开持久连接 + emit disconnected(); + return true; +} + +bool HttpChannel::isConnected() const +{ + return m_config.endpoint.isValid(); // 只要端点有效就认为"可连接" +} + +bool HttpChannel::send(const QByteArray& data) +{ + return post(data); +} + +bool HttpChannel::get(const QString& path) +{ + if (!m_config.endpoint.isValid()) { + emit errorOccurred("Invalid endpoint"); + return false; + } + + QUrl url = m_config.endpoint; + if (!path.isEmpty()) { + url.setPath(url.path() + "/" + path); + } + + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + + QObject::connect(manager, &QNetworkAccessManager::authenticationRequired, + [this](QNetworkReply* reply, QAuthenticator* authenticator) { + if (!m_username.isEmpty() && !m_password.isEmpty()) { + authenticator->setUser(m_username); + authenticator->setPassword(m_password); + } + }); + + QNetworkRequest request(url); + + // 设置头 + for (auto it = m_headers.begin(); it != m_headers.end(); ++it) { + request.setRawHeader(it.key().toUtf8(), it.value().toUtf8()); + } + + QNetworkReply* reply = manager->get(request); + + QObject::connect(reply, &QNetworkReply::finished, [this, reply, manager]() { + if (reply->error() == QNetworkReply::NoError) { + QByteArray data = reply->readAll(); + emit dataReceived(data); + } else { + emit errorOccurred(reply->errorString()); + } + + reply->deleteLater(); + manager->deleteLater(); + }); + + return true; +} + +bool HttpChannel::post(const QByteArray& data, const QString& path) +{ + if (!m_config.endpoint.isValid()) { + emit errorOccurred("Invalid endpoint"); + return false; + } + + QUrl url = m_config.endpoint; + if (!path.isEmpty()) { + url.setPath(url.path() + "/" + path); + } + + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + for (auto it = m_headers.begin(); it != m_headers.end(); ++it) { + request.setRawHeader(it.key().toUtf8(), it.value().toUtf8()); + } + + QNetworkReply* reply = manager->post(request, data); + + QObject::connect(reply, &QNetworkReply::finished,this,[this, reply, manager]() { + if (reply->error() == QNetworkReply::NoError) { + QByteArray data = reply->readAll(); + emit dataReceived(data); + } else { + emit errorOccurred(reply->errorString()); + } + + reply->deleteLater(); + manager->deleteLater(); + }); + + return true; +} + +bool HttpChannel::put(const QByteArray& data, const QString& path) +{ + // 类似post的实现 + return post(data, path); // 简化处理 +} + +bool HttpChannel::deleteResource(const QString& path) +{ + // 类似get的实现 + return get(path); // 简化处理 +} + +void HttpChannel::setBasicAuth(const QString& username, const QString& password) +{ + m_username = username; + m_password = password; +} + +void HttpChannel::setHeader(const QString& name, const QString& value) +{ + m_headers[name] = value; +} diff --git a/diagramCommunication/source/uiCommunicationBus.cpp b/diagramCommunication/source/uiCommunicationBus.cpp new file mode 100644 index 0000000..101106e --- /dev/null +++ b/diagramCommunication/source/uiCommunicationBus.cpp @@ -0,0 +1,189 @@ +// UiCommunicationBus.cpp +#include "uiCommunicationBus.h" +#include "communicationManager.h" +#include "dataProcessor.h" +#include +#include +#include +#include + +UiCommunicationBus* UiCommunicationBus::instance() +{ + static UiCommunicationBus* instance = nullptr; + static QMutex mutex; + + if (!instance) { + QMutexLocker locker(&mutex); + if (!instance) { + instance = new UiCommunicationBus; + } + } + return instance; +} + +UiCommunicationBus::UiCommunicationBus(QObject* parent) + : QObject(parent) +{ + // 连接到CommunicationManager + CommunicationManager* comm = CommunicationManager::instance(); + + // 连接HTTP信号 + connect(comm, &CommunicationManager::httpDataReceived, + this, &UiCommunicationBus::onHttpDataReceived); + + // 连接WebSocket信号 + connect(comm, &CommunicationManager::websocketDataReceived, + this, &UiCommunicationBus::onWebSocketDataReceived); + + // 连接DataProcessor信号 + DataProcessor* processor = DataProcessor::instance(); + connect(processor, &DataProcessor::httpProcessed, + this, [this](const QString& dataType, const QVariant& data) { + if(dataType == "recommend"){ + // 推荐列表请求将数据分发回订阅ui + broadcastToUis(dataType, data); + } + emit httpDataProcessed(dataType,data); + }); + + connect(processor, &DataProcessor::websocketProcessed, + this, [this](const QVariant& data) { + emit websocketDataProcessed(data); + }); + + qDebug() << "UiCommunicationBus initialized"; +} + +void UiCommunicationBus::sendHttpRequest(const QString& endpoint, const QVariant& data) +{ + CommunicationManager* comm = CommunicationManager::instance(); + + QJsonDocument doc = QJsonDocument::fromVariant(data); + bool success = comm->sendHttpRequest(endpoint, doc.toJson()); + + if (success) { + qDebug() << "HTTP请求已发送:" << endpoint; + } else { + qWarning() << "HTTP请求发送失败:" << endpoint; + } +} + +void UiCommunicationBus::sendHttpRequestNoReply(const QString& endpoint, const QVariant& data) +{ + CommunicationManager* comm = CommunicationManager::instance(); + + QJsonDocument doc = QJsonDocument::fromVariant(data); + comm->sendHttpRequest(endpoint, doc.toJson()); + + qDebug() << "无回复HTTP请求已发送:" << endpoint; +} + +void UiCommunicationBus::onHttpDataReceived(const QByteArray& data) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qWarning() << "HTTP响应解析失败:" << error.errorString(); + return; + } + + QVariant response = doc.toVariant(); + + if (response.typeId() == QMetaType::QVariantMap) { + QVariantMap responseMap = response.toMap(); + DataProcessor* processor = DataProcessor::instance(); + + QString state = responseMap.value("msg").toString(); + if(state == "success"){ + if(responseMap.contains("payload")){ + processor->processData(responseMap.value("payload")); + } + } + } + + qDebug() << "HTTP响应已处理"; +} + +void UiCommunicationBus::onWebSocketDataReceived(const QByteArray& data) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qWarning() << "WebSocket数据解析失败:" << error.errorString(); + return; + } + + QVariant response = doc.toVariant(); + + if (response.typeId() == QMetaType::QVariantMap) { + QVariantMap responseMap = response.toMap(); + DataProcessor* processor = DataProcessor::instance(); + + QString state = responseMap.value("msg").toString(); + if(state == "success"){ + if(responseMap.contains("payload")){ + processor->processData(responseMap.value("payload"),1); + } + } + } + + qDebug() << "WebSocket数据已处理"; +} + +void UiCommunicationBus::sendToUi(const QString& uiId, const QString& action, const QVariant& data) +{ + QMutexLocker locker(&m_mutex); + + if (!m_uiObjects.contains(uiId)) { + qWarning() << "UI未注册:" << uiId; + return; + } + + QObject* uiObject = m_uiObjects[uiId]; + + // 尝试调用通用的消息处理槽 + bool success = QMetaObject::invokeMethod(uiObject, "onMessage", + Qt::QueuedConnection, + Q_ARG(QString, action), + Q_ARG(QVariant, data)); + + if (!success) { + qWarning() << "调用UI消息处理失败:" << uiId << "action:" << action; + } +} + +void UiCommunicationBus::broadcastToUis(const QString& action, const QVariant& data) +{ + QMutexLocker locker(&m_mutex); + + for (auto it = m_uiObjects.begin(); it != m_uiObjects.end(); ++it) { + QObject* uiObject = it.value(); + + QMetaObject::invokeMethod(uiObject, "onMessage", + Q_ARG(QString, action), + Q_ARG(QVariant, data)); + } + + qDebug() << "消息广播:" << action << "接收者:" << m_uiObjects.size(); +} + +void UiCommunicationBus::registerUi(const QString& uiId, QObject* uiObject) +{ + if (uiId.isEmpty() || !uiObject) { + qWarning() << "注册UI失败: 参数无效"; + return; + } + + QMutexLocker locker(&m_mutex); + m_uiObjects[uiId] = uiObject; + qDebug() << "UI已注册:" << uiId; +} + +void UiCommunicationBus::unregisterUi(const QString& uiId) +{ + QMutexLocker locker(&m_mutex); + m_uiObjects.remove(uiId); + qDebug() << "UI已注销:" << uiId; +} diff --git a/diagramCommunication/source/webSocketChannel.cpp b/diagramCommunication/source/webSocketChannel.cpp new file mode 100644 index 0000000..d23036f --- /dev/null +++ b/diagramCommunication/source/webSocketChannel.cpp @@ -0,0 +1,93 @@ +// WebSocketChannel.cpp +#include "webSocketChannel.h" +#include + +WebSocketChannel::WebSocketChannel(const ChannelConfig& config, QObject* parent) + : BaseChannel(config, parent) + , m_webSocket(new QWebSocket) +{ + QObject::connect(m_webSocket, &QWebSocket::connected, + this, &WebSocketChannel::onConnected); + QObject::connect(m_webSocket, &QWebSocket::disconnected, + this, &WebSocketChannel::onDisconnected); + QObject::connect(m_webSocket, &QWebSocket::textMessageReceived, + this, &WebSocketChannel::onTextMessageReceived); + QObject::connect(m_webSocket, &QWebSocket::binaryMessageReceived, + this, &WebSocketChannel::onBinaryMessageReceived); +} + +bool WebSocketChannel::connect() +{ + if (m_webSocket->state() == QAbstractSocket::ConnectedState) { + return true; + } + + if (!m_config.endpoint.isValid()) { + emit errorOccurred("Invalid endpoint"); + return false; + } + + m_webSocket->open(m_config.endpoint); + return true; +} + +bool WebSocketChannel::disconnect() +{ + if (m_webSocket->state() != QAbstractSocket::UnconnectedState) { + m_webSocket->close(); + } + return true; +} + +bool WebSocketChannel::isConnected() const +{ + return m_webSocket->state() == QAbstractSocket::ConnectedState; +} + +bool WebSocketChannel::send(const QByteArray& data) +{ + if (!isConnected()) { + emit errorOccurred("WebSocket not connected"); + return false; + } + + qint64 sent = m_webSocket->sendBinaryMessage(data); + return sent == data.size(); +} + +bool WebSocketChannel::sendText(const QString& text) +{ + if (!isConnected()) { + emit errorOccurred("WebSocket not connected"); + return false; + } + + qint64 sent = m_webSocket->sendTextMessage(text); + return sent == text.size(); +} + +void WebSocketChannel::setWebSocketConfig(const WebSocketConfig& config) +{ + m_wsConfig = config; +} + +void WebSocketChannel::onConnected() +{ + emit connected(); +} + +void WebSocketChannel::onDisconnected() +{ + emit disconnected(); +} + +void WebSocketChannel::onTextMessageReceived(const QString& message) +{ + emit textMessageReceived(message); + emit dataReceived(message.toUtf8()); +} + +void WebSocketChannel::onBinaryMessageReceived(const QByteArray& message) +{ + emit dataReceived(message); +} diff --git a/source/mainwindow.cpp b/source/mainwindow.cpp index 2ece151..b6c5129 100644 --- a/source/mainwindow.cpp +++ b/source/mainwindow.cpp @@ -214,6 +214,9 @@ void CMainWindow::initializeAction() } }); + QAction* settingAct = ui->menuSetting->addAction(QString::fromWCharArray(L"网络设置")); + connect(settingAct,&QAction::triggered,m_pDiagramCavas,&DiagramCavas::onSignal_openNetSetting); + QAction* testAct = ui->menuTest->addAction(QString::fromWCharArray(L"生成测试基模")); connect(testAct,&QAction::triggered,m_pDiagramCavas,&DiagramCavas::onCreateTestBaseModelDiagram); } diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 5068403..2e458b3 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -48,10 +48,16 @@ 测试 + + + 设置 + + +