| @@ -17,6 +17,7 @@ INCLUDEPATH += src | |||||
| SOURCES += \ | SOURCES += \ | ||||
| src/main.cpp \ | src/main.cpp \ | ||||
| src/ui/main_window.cpp \ | src/ui/main_window.cpp \ | ||||
| src/ui/plc_connection_dialog.cpp \ | |||||
| src/domain/register_address.cpp \ | src/domain/register_address.cpp \ | ||||
| src/domain/register_repository.cpp \ | src/domain/register_repository.cpp \ | ||||
| src/domain/active_register_repository.cpp \ | src/domain/active_register_repository.cpp \ | ||||
| @@ -41,9 +42,10 @@ SOURCES += \ | |||||
| HEADERS += \ | HEADERS += \ | ||||
| src/ui/main_window.h \ | src/ui/main_window.h \ | ||||
| src/ui/plc_connection_dialog.h \ | |||||
| src/domain/register_address.h \ | src/domain/register_address.h \ | ||||
| src/domain/active_register_repository.h \ | |||||
| src/domain/register_repository.h \ | src/domain/register_repository.h \ | ||||
| src/domain/active_register_repository.h \ | |||||
| src/domain/data_point_model.h \ | src/domain/data_point_model.h \ | ||||
| src/domain/hmi_model.h \ | src/domain/hmi_model.h \ | ||||
| src/domain/control_logic_model.h \ | src/domain/control_logic_model.h \ | ||||
| @@ -57,13 +59,14 @@ HEADERS += \ | |||||
| src/services/hmi_runtime_service.h \ | src/services/hmi_runtime_service.h \ | ||||
| src/services/software_logic_executor.h \ | src/services/software_logic_executor.h \ | ||||
| src/services/offline_simulation_service.h \ | src/services/offline_simulation_service.h \ | ||||
| src/services/runtime_mode_service.h \ | |||||
| src/services/plc_communication_gateway.h \ | src/services/plc_communication_gateway.h \ | ||||
| src/infrastructure/plc_register_repository.h \ | src/infrastructure/plc_register_repository.h \ | ||||
| src/infrastructure/plc_communication_service.h \ | src/infrastructure/plc_communication_service.h \ | ||||
| src/services/runtime_mode_service.h \ | |||||
| src/infrastructure/json_project_storage.h \ | src/infrastructure/json_project_storage.h \ | ||||
| src/ui/hmi_editor_widget.h \ | src/ui/hmi_editor_widget.h \ | ||||
| src/ui/logic_editor_widget.h | src/ui/logic_editor_widget.h | ||||
| FORMS += \ | FORMS += \ | ||||
| src/ui/main_window.ui | |||||
| src/ui/main_window.ui \ | |||||
| src/ui/plc_connection_dialog.ui | |||||
| @@ -9,7 +9,11 @@ | |||||
| #include <QApplication> | #include <QApplication> | ||||
| #include "infrastructure/json_project_storage.h" | #include "infrastructure/json_project_storage.h" | ||||
| #include "infrastructure/plc_communication_service.h" | |||||
| #include "infrastructure/plc_register_repository.h" | |||||
| #include "domain/active_register_repository.h" | |||||
| #include "services/hmi_editor_service.h" | #include "services/hmi_editor_service.h" | ||||
| #include "services/data_point_service.h" | |||||
| #include "services/hmi_runtime_service.h" | #include "services/hmi_runtime_service.h" | ||||
| #include "services/logic_editor_service.h" | #include "services/logic_editor_service.h" | ||||
| #include "services/offline_simulation_service.h" | #include "services/offline_simulation_service.h" | ||||
| @@ -27,17 +31,27 @@ int main(int argc, char *argv[]) | |||||
| JsonProjectStorage project_storage; | JsonProjectStorage project_storage; | ||||
| ProjectService project_service(project_storage); | ProjectService project_service(project_storage); | ||||
| HmiEditorService hmi_editor_service(project_service); | HmiEditorService hmi_editor_service(project_service); | ||||
| DataPointService data_point_service(project_service); | |||||
| LogicEditorService logic_editor_service(project_service); | LogicEditorService logic_editor_service(project_service); | ||||
| // 当前离线模式使用内存仓库,后续真机模式替换为 PLC 缓存实现 | // 当前离线模式使用内存仓库,后续真机模式替换为 PLC 缓存实现 | ||||
| VirtualRegisterRepository virtual_register_repository; | VirtualRegisterRepository virtual_register_repository; | ||||
| HmiRuntimeService hmi_runtime_service(virtual_register_repository); | |||||
| PlcRegisterRepository plc_register_repository; | |||||
| ActiveRegisterRepository active_register_repository(virtual_register_repository); | |||||
| PlcCommunicationService plc_communication_service(plc_register_repository); | |||||
| HmiRuntimeService hmi_runtime_service(active_register_repository); | |||||
| OfflineSimulationService offline_simulation_service(virtual_register_repository); | OfflineSimulationService offline_simulation_service(virtual_register_repository); | ||||
| RuntimeModeService runtime_mode_service( | RuntimeModeService runtime_mode_service( | ||||
| project_service, offline_simulation_service); | project_service, offline_simulation_service); | ||||
| runtime_mode_service.configurePlc( | |||||
| plc_communication_service, | |||||
| active_register_repository, | |||||
| virtual_register_repository, | |||||
| plc_register_repository); | |||||
| MainWindow main_window( | MainWindow main_window( | ||||
| runtime_mode_service, | runtime_mode_service, | ||||
| project_service, | project_service, | ||||
| hmi_editor_service, | hmi_editor_service, | ||||
| data_point_service, | |||||
| logic_editor_service, | logic_editor_service, | ||||
| hmi_runtime_service); | hmi_runtime_service); | ||||
| main_window.show(); | main_window.show(); | ||||
| @@ -107,6 +107,16 @@ HmiRuntimeWriteResult HmiRuntimeService::writeNumericInput( | |||||
| : writeFailure(repositoryError(result.error)); | : writeFailure(repositoryError(result.error)); | ||||
| } | } | ||||
| BitReadResult HmiRuntimeService::readBit(const RegisterAddress &address) const | |||||
| { | |||||
| return repository_.readBit(address); | |||||
| } | |||||
| WordReadResult HmiRuntimeService::readWord(const RegisterAddress &address) const | |||||
| { | |||||
| return repository_.readWord(address); | |||||
| } | |||||
| HmiRuntimeError HmiRuntimeService::repositoryError(RegisterError error) | HmiRuntimeError HmiRuntimeService::repositoryError(RegisterError error) | ||||
| { | { | ||||
| // 领域仓库错误被收敛为 HMI 可理解的运行错误 | // 领域仓库错误被收敛为 HMI 可理解的运行错误 | ||||
| @@ -78,6 +78,8 @@ public: | |||||
| */ | */ | ||||
| HmiRuntimeWriteResult writeNumericInput( | HmiRuntimeWriteResult writeNumericInput( | ||||
| const HmiControl &control, std::int16_t value); | const HmiControl &control, std::int16_t value); | ||||
| BitReadResult readBit(const RegisterAddress &address) const; | |||||
| WordReadResult readWord(const RegisterAddress &address) const; | |||||
| private: | private: | ||||
| static HmiRuntimeError repositoryError(RegisterError error); | static HmiRuntimeError repositoryError(RegisterError error); | ||||
| @@ -11,6 +11,7 @@ | |||||
| #include "domain/hmi_model.h" | #include "domain/hmi_model.h" | ||||
| #include "domain/control_logic_model.h" | #include "domain/control_logic_model.h" | ||||
| #include "domain/runtime_state.h" | #include "domain/runtime_state.h" | ||||
| #include "services/plc_communication_gateway.h" | |||||
| #include <QMainWindow> | #include <QMainWindow> | ||||
| @@ -18,15 +19,8 @@ | |||||
| QT_BEGIN_NAMESPACE | QT_BEGIN_NAMESPACE | ||||
| class QActionGroup; | class QActionGroup; | ||||
| class QAction; | |||||
| class QComboBox; | |||||
| class QLabel; | class QLabel; | ||||
| class QLineEdit; | |||||
| class QPushButton; | |||||
| class QSpinBox; | |||||
| class QStackedWidget; | |||||
| class QTimer; | class QTimer; | ||||
| class QToolBar; | |||||
| namespace Ui { | namespace Ui { | ||||
| class MainWindow; | class MainWindow; | ||||
| @@ -37,6 +31,7 @@ class RuntimeModeService; | |||||
| class ProjectService; | class ProjectService; | ||||
| class HmiEditorService; | class HmiEditorService; | ||||
| class HmiRuntimeService; | class HmiRuntimeService; | ||||
| class DataPointService; | |||||
| class HmiEditorWidget; | class HmiEditorWidget; | ||||
| class LogicEditorService; | class LogicEditorService; | ||||
| class LogicEditorWidget; | class LogicEditorWidget; | ||||
| @@ -63,6 +58,7 @@ public: | |||||
| RuntimeModeService &runtime_mode_service, | RuntimeModeService &runtime_mode_service, | ||||
| ProjectService &project_service, | ProjectService &project_service, | ||||
| HmiEditorService &hmi_editor_service, | HmiEditorService &hmi_editor_service, | ||||
| DataPointService &data_point_service, | |||||
| LogicEditorService &logic_editor_service, | LogicEditorService &logic_editor_service, | ||||
| HmiRuntimeService &hmi_runtime_service, | HmiRuntimeService &hmi_runtime_service, | ||||
| QWidget *parent = nullptr); | QWidget *parent = nullptr); | ||||
| @@ -80,6 +76,8 @@ private: | |||||
| void configureLogicEditor(); | void configureLogicEditor(); | ||||
| // 创建并连接控件属性编辑表单 | // 创建并连接控件属性编辑表单 | ||||
| void configurePropertyEditor(); | void configurePropertyEditor(); | ||||
| void configureDataPointEditor(); | |||||
| void configurePlcConnection(); | |||||
| // 刷新工程树和当前 HMI 页面信息 | // 刷新工程树和当前 HMI 页面信息 | ||||
| void refreshProjectUi(); | void refreshProjectUi(); | ||||
| // 显示指定 HMI 控件的可编辑属性 | // 显示指定 HMI 控件的可编辑属性 | ||||
| @@ -102,6 +100,12 @@ private: | |||||
| void deleteSelectedLogicObject(); | void deleteSelectedLogicObject(); | ||||
| // 将逻辑属性表单内容应用到当前选中节点 | // 将逻辑属性表单内容应用到当前选中节点 | ||||
| void applySelectedLogicNodeProperties(); | void applySelectedLogicNodeProperties(); | ||||
| void refreshDataPointUi(); | |||||
| void addDataPoint(); | |||||
| void updateSelectedDataPoint(); | |||||
| void deleteSelectedDataPoint(); | |||||
| void connectPlc(); | |||||
| void disconnectPlc(); | |||||
| // 创建新的工程并刷新编辑界面 | // 创建新的工程并刷新编辑界面 | ||||
| void createNewProject(); | void createNewProject(); | ||||
| // 保存当前工程到已关联的路径 | // 保存当前工程到已关联的路径 | ||||
| @@ -144,55 +148,18 @@ private: | |||||
| */ | */ | ||||
| ProjectService &project_service_; | ProjectService &project_service_; | ||||
| HmiEditorService &hmi_editor_service_; | HmiEditorService &hmi_editor_service_; | ||||
| DataPointService &data_point_service_; | |||||
| LogicEditorService &logic_editor_service_; | LogicEditorService &logic_editor_service_; | ||||
| HmiRuntimeService &hmi_runtime_service_; | HmiRuntimeService &hmi_runtime_service_; | ||||
| QActionGroup *mode_action_group_ = nullptr; | QActionGroup *mode_action_group_ = nullptr; | ||||
| QAction *new_project_action_ = nullptr; | |||||
| QAction *save_project_action_ = nullptr; | |||||
| QAction *save_as_project_action_ = nullptr; | |||||
| QAction *load_project_action_ = nullptr; | |||||
| QAction *add_button_action_ = nullptr; | |||||
| QAction *add_indicator_action_ = nullptr; | |||||
| QAction *add_numeric_display_action_ = nullptr; | |||||
| QAction *add_numeric_input_action_ = nullptr; | |||||
| QAction *delete_control_action_ = nullptr; | |||||
| QAction *add_rung_action_ = nullptr; | |||||
| QAction *parallel_insert_action_ = nullptr; | |||||
| QAction *add_normally_open_action_ = nullptr; | |||||
| QAction *add_normally_closed_action_ = nullptr; | |||||
| QAction *add_normal_coil_action_ = nullptr; | |||||
| QAction *add_set_coil_action_ = nullptr; | |||||
| QAction *add_reset_coil_action_ = nullptr; | |||||
| QAction *add_compare_action_ = nullptr; | |||||
| QAction *delete_logic_action_ = nullptr; | |||||
| QToolBar *hmi_tool_bar_ = nullptr; | |||||
| QToolBar *logic_tool_bar_ = nullptr; | |||||
| QActionGroup *logic_insert_action_group_ = nullptr; | |||||
| HmiEditorWidget *hmi_editor_widget_ = nullptr; | HmiEditorWidget *hmi_editor_widget_ = nullptr; | ||||
| LogicEditorWidget *logic_editor_widget_ = nullptr; | LogicEditorWidget *logic_editor_widget_ = nullptr; | ||||
| QTimer *runtime_refresh_timer_ = nullptr; | QTimer *runtime_refresh_timer_ = nullptr; | ||||
| QLabel *mode_status_label_ = nullptr; | QLabel *mode_status_label_ = nullptr; | ||||
| QLabel *register_status_label_ = nullptr; | QLabel *register_status_label_ = nullptr; | ||||
| QLabel *executor_status_label_ = nullptr; | QLabel *executor_status_label_ = nullptr; | ||||
| QLineEdit *control_id_edit_ = nullptr; | |||||
| QLineEdit *control_text_edit_ = nullptr; | |||||
| QSpinBox *control_x_spin_box_ = nullptr; | |||||
| QSpinBox *control_y_spin_box_ = nullptr; | |||||
| QSpinBox *control_width_spin_box_ = nullptr; | |||||
| QSpinBox *control_height_spin_box_ = nullptr; | |||||
| QComboBox *binding_area_combo_box_ = nullptr; | |||||
| QSpinBox *binding_index_spin_box_ = nullptr; | |||||
| QPushButton *apply_properties_button_ = nullptr; | |||||
| QStackedWidget *property_stack_ = nullptr; | |||||
| QWidget *hmi_properties_page_ = nullptr; | |||||
| QWidget *logic_properties_page_ = nullptr; | |||||
| QLineEdit *logic_node_id_edit_ = nullptr; | |||||
| QLabel *logic_node_type_label_ = nullptr; | |||||
| QLabel *logic_address_area_label_ = nullptr; | |||||
| QSpinBox *logic_address_spin_box_ = nullptr; | |||||
| QComboBox *logic_mode_combo_box_ = nullptr; | |||||
| QComboBox *logic_comparison_combo_box_ = nullptr; | |||||
| QSpinBox *logic_value_spin_box_ = nullptr; | |||||
| QPushButton *apply_logic_properties_button_ = nullptr; | |||||
| PlcSerialConfiguration plc_configuration_; | |||||
| std::string selected_control_id_; | std::string selected_control_id_; | ||||
| std::string selected_logic_node_id_; | std::string selected_logic_node_id_; | ||||
| }; | }; | ||||
| @@ -221,7 +221,12 @@ | |||||
| <property name="title"> | <property name="title"> | ||||
| <string>文件(&F)</string> | <string>文件(&F)</string> | ||||
| </property> | </property> | ||||
| <addaction name="exitAction"/> | |||||
| <addaction name="newProjectAction"/> | |||||
| <addaction name="saveProjectAction"/> | |||||
| <addaction name="saveAsProjectAction"/> | |||||
| <addaction name="loadProjectAction"/> | |||||
| <addaction name="separator"/> | |||||
| <addaction name="exitAction"/> | |||||
| </widget> | </widget> | ||||
| <widget class="QMenu" name="runMenu"> | <widget class="QMenu" name="runMenu"> | ||||
| <property name="title"> | <property name="title"> | ||||
| @@ -229,7 +234,10 @@ | |||||
| </property> | </property> | ||||
| <addaction name="editingModeAction"/> | <addaction name="editingModeAction"/> | ||||
| <addaction name="offlineModeAction"/> | <addaction name="offlineModeAction"/> | ||||
| <addaction name="onlineModeAction"/> | |||||
| <addaction name="onlineModeAction"/> | |||||
| <addaction name="separator"/> | |||||
| <addaction name="configurePlcAction"/> | |||||
| <addaction name="disconnectPlcAction"/> | |||||
| </widget> | </widget> | ||||
| <widget class="QMenu" name="viewMenu"> | <widget class="QMenu" name="viewMenu"> | ||||
| <property name="title"> | <property name="title"> | ||||
| @@ -260,7 +268,39 @@ | |||||
| <addaction name="editingModeAction"/> | <addaction name="editingModeAction"/> | ||||
| <addaction name="offlineModeAction"/> | <addaction name="offlineModeAction"/> | ||||
| <addaction name="onlineModeAction"/> | <addaction name="onlineModeAction"/> | ||||
| </widget> | |||||
| <addaction name="configurePlcAction"/> | |||||
| <addaction name="disconnectPlcAction"/> | |||||
| </widget> | |||||
| <widget class="QToolBar" name="hmiToolBar"> | |||||
| <property name="windowTitle"><string>HMI 控件</string></property> | |||||
| <property name="toolButtonStyle"><enum>Qt::ToolButtonTextBesideIcon</enum></property> | |||||
| <attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute> | |||||
| <attribute name="toolBarBreak"><bool>false</bool></attribute> | |||||
| <addaction name="addButtonAction"/> | |||||
| <addaction name="addIndicatorAction"/> | |||||
| <addaction name="addNumericDisplayAction"/> | |||||
| <addaction name="addNumericInputAction"/> | |||||
| <addaction name="deleteControlAction"/> | |||||
| </widget> | |||||
| <widget class="QToolBar" name="logicToolBar"> | |||||
| <property name="windowTitle"><string>控制逻辑节点</string></property> | |||||
| <property name="toolButtonStyle"><enum>Qt::ToolButtonTextBesideIcon</enum></property> | |||||
| <attribute name="toolBarArea"><enum>TopToolBarArea</enum></attribute> | |||||
| <attribute name="toolBarBreak"><bool>false</bool></attribute> | |||||
| <addaction name="addRungAction"/> | |||||
| <addaction name="appendInsertAction"/> | |||||
| <addaction name="beforeInsertAction"/> | |||||
| <addaction name="afterInsertAction"/> | |||||
| <addaction name="parallelInsertAction"/> | |||||
| <addaction name="separator"/> | |||||
| <addaction name="addNormallyOpenAction"/> | |||||
| <addaction name="addNormallyClosedAction"/> | |||||
| <addaction name="addNormalCoilAction"/> | |||||
| <addaction name="addSetCoilAction"/> | |||||
| <addaction name="addResetCoilAction"/> | |||||
| <addaction name="addCompareAction"/> | |||||
| <addaction name="deleteLogicAction"/> | |||||
| </widget> | |||||
| <widget class="QDockWidget" name="projectDock"> | <widget class="QDockWidget" name="projectDock"> | ||||
| <property name="minimumSize"> | <property name="minimumSize"> | ||||
| <size> | <size> | ||||
| @@ -360,32 +400,82 @@ | |||||
| <height>164</height> | <height>164</height> | ||||
| </rect> | </rect> | ||||
| </property> | </property> | ||||
| <layout class="QFormLayout" name="propertiesForm"> | |||||
| <property name="fieldGrowthPolicy"> | |||||
| <enum>QFormLayout::AllNonFixedFieldsGrow</enum> | |||||
| </property> | |||||
| <item row="0" column="0"> | |||||
| <widget class="QLabel" name="selectionCaptionLabel"> | |||||
| <property name="text"> | |||||
| <string>当前对象</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="0" column="1"> | |||||
| <widget class="QLabel" name="selectionValueLabel"> | |||||
| <property name="text"> | |||||
| <string>未选择</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| <layout class="QVBoxLayout" name="propertiesPageLayout"> | |||||
| <item> | |||||
| <layout class="QFormLayout" name="selectionForm"> | |||||
| <item row="0" column="0"><widget class="QLabel" name="selectionCaptionLabel"><property name="text"><string>当前对象</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QLabel" name="selectionValueLabel"><property name="text"><string>未选择</string></property></widget></item> | |||||
| </layout> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QStackedWidget" name="propertyStack"> | |||||
| <widget class="QWidget" name="hmiPropertiesPage"> | |||||
| <layout class="QFormLayout" name="hmiPropertiesForm"> | |||||
| <property name="fieldGrowthPolicy"><enum>QFormLayout::AllNonFixedFieldsGrow</enum></property> | |||||
| <item row="0" column="0"><widget class="QLabel" name="controlIdLabel"><property name="text"><string>控件 ID</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QLineEdit" name="controlIdEdit"/></item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="controlTextLabel"><property name="text"><string>显示文本</string></property></widget></item> | |||||
| <item row="1" column="1"><widget class="QLineEdit" name="controlTextEdit"/></item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="controlXLabel"><property name="text"><string>X</string></property></widget></item> | |||||
| <item row="2" column="1"><widget class="QSpinBox" name="controlXSpinBox"><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="3" column="0"><widget class="QLabel" name="controlYLabel"><property name="text"><string>Y</string></property></widget></item> | |||||
| <item row="3" column="1"><widget class="QSpinBox" name="controlYSpinBox"><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="4" column="0"><widget class="QLabel" name="controlWidthLabel"><property name="text"><string>宽度</string></property></widget></item> | |||||
| <item row="4" column="1"><widget class="QSpinBox" name="controlWidthSpinBox"><property name="minimum"><number>1</number></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="5" column="0"><widget class="QLabel" name="controlHeightLabel"><property name="text"><string>高度</string></property></widget></item> | |||||
| <item row="5" column="1"><widget class="QSpinBox" name="controlHeightSpinBox"><property name="minimum"><number>1</number></property><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="6" column="0"><widget class="QLabel" name="bindingAreaLabel"><property name="text"><string>绑定区域</string></property></widget></item> | |||||
| <item row="6" column="1"><widget class="QComboBox" name="bindingAreaComboBox"> | |||||
| <item><property name="text"><string>未绑定</string></property><property name="userData"><string notr="true">-1</string></property></item> | |||||
| <item><property name="text"><string>M</string></property><property name="userData"><string notr="true">0</string></property></item> | |||||
| <item><property name="text"><string>D</string></property><property name="userData"><string notr="true">1</string></property></item> | |||||
| </widget></item> | |||||
| <item row="7" column="0"><widget class="QLabel" name="bindingIndexLabel"><property name="text"><string>绑定地址</string></property></widget></item> | |||||
| <item row="7" column="1"><widget class="QSpinBox" name="bindingIndexSpinBox"><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="8" column="0"><widget class="QLabel" name="hmiDataPointLabel"><property name="text"><string>选择数据点</string></property></widget></item> | |||||
| <item row="8" column="1"><widget class="QComboBox" name="hmiDataPointComboBox"/></item> | |||||
| <item row="9" column="0" colspan="2"><widget class="QPushButton" name="applyPropertiesButton"><property name="text"><string>应用属性</string></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| <widget class="QWidget" name="logicPropertiesPage"> | |||||
| <layout class="QFormLayout" name="logicPropertiesForm"> | |||||
| <property name="fieldGrowthPolicy"><enum>QFormLayout::AllNonFixedFieldsGrow</enum></property> | |||||
| <item row="0" column="0"><widget class="QLabel" name="logicNodeIdLabel"><property name="text"><string>节点 ID</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QLineEdit" name="logicNodeIdEdit"><property name="readOnly"><bool>true</bool></property></widget></item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="logicNodeTypeCaption"><property name="text"><string>节点类型</string></property></widget></item> | |||||
| <item row="1" column="1"><widget class="QLabel" name="logicNodeTypeLabel"><property name="text"><string/></property></widget></item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="logicAddressAreaCaption"><property name="text"><string>地址区域</string></property></widget></item> | |||||
| <item row="2" column="1"><widget class="QLabel" name="logicAddressAreaLabel"><property name="text"><string/></property></widget></item> | |||||
| <item row="3" column="0"><widget class="QLabel" name="logicAddressLabel"><property name="text"><string>地址</string></property></widget></item> | |||||
| <item row="3" column="1"><widget class="QSpinBox" name="logicAddressSpinBox"><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item row="4" column="0"><widget class="QLabel" name="logicDataPointLabel"><property name="text"><string>选择数据点</string></property></widget></item> | |||||
| <item row="4" column="1"><widget class="QComboBox" name="logicDataPointComboBox"/></item> | |||||
| <item row="5" column="0"><widget class="QLabel" name="logicModeLabel"><property name="text"><string>工作方式</string></property></widget></item> | |||||
| <item row="5" column="1"><widget class="QComboBox" name="logicModeComboBox"/></item> | |||||
| <item row="6" column="0"><widget class="QLabel" name="logicComparisonLabel"><property name="text"><string>比较运算</string></property></widget></item> | |||||
| <item row="6" column="1"><widget class="QComboBox" name="logicComparisonComboBox"> | |||||
| <item><property name="text"><string>等于</string></property><property name="userData"><string notr="true">0</string></property></item> | |||||
| <item><property name="text"><string>不等于</string></property><property name="userData"><string notr="true">1</string></property></item> | |||||
| <item><property name="text"><string>小于</string></property><property name="userData"><string notr="true">2</string></property></item> | |||||
| <item><property name="text"><string>小于等于</string></property><property name="userData"><string notr="true">3</string></property></item> | |||||
| <item><property name="text"><string>大于</string></property><property name="userData"><string notr="true">4</string></property></item> | |||||
| <item><property name="text"><string>大于等于</string></property><property name="userData"><string notr="true">5</string></property></item> | |||||
| </widget></item> | |||||
| <item row="7" column="0"><widget class="QLabel" name="logicValueLabel"><property name="text"><string>比较常量</string></property></widget></item> | |||||
| <item row="7" column="1"><widget class="QSpinBox" name="logicValueSpinBox"><property name="minimum"><number>-32768</number></property><property name="maximum"><number>32767</number></property></widget></item> | |||||
| <item row="8" column="0" colspan="2"><widget class="QPushButton" name="applyLogicPropertiesButton"><property name="text"><string>应用属性</string></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | </widget> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| </layout> | </layout> | ||||
| </widget> | </widget> | ||||
| </widget> | </widget> | ||||
| <widget class="QDockWidget" name="outputDock"> | |||||
| <widget class="QDockWidget" name="outputDock"> | |||||
| <property name="minimumSize"> | <property name="minimumSize"> | ||||
| <size> | <size> | ||||
| <width>300</width> | <width>300</width> | ||||
| @@ -422,13 +512,77 @@ | |||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| </layout> | </layout> | ||||
| </widget> | |||||
| </widget> | </widget> | ||||
| </widget> | |||||
| <action name="exitAction"> | |||||
| <widget class="QDockWidget" name="dataPointDock"> | |||||
| <property name="minimumSize"><size><width>420</width><height>170</height></size></property> | |||||
| <property name="windowTitle"><string>数据点</string></property> | |||||
| <attribute name="dockWidgetArea"><number>8</number></attribute> | |||||
| <widget class="QWidget" name="dataPointDockContents"> | |||||
| <layout class="QVBoxLayout" name="dataPointDockLayout"> | |||||
| <property name="leftMargin"><number>6</number></property> | |||||
| <property name="topMargin"><number>6</number></property> | |||||
| <property name="rightMargin"><number>6</number></property> | |||||
| <property name="bottomMargin"><number>6</number></property> | |||||
| <item> | |||||
| <widget class="QTableWidget" name="dataPointTable"> | |||||
| <property name="selectionMode"><enum>QAbstractItemView::SingleSelection</enum></property> | |||||
| <property name="selectionBehavior"><enum>QAbstractItemView::SelectRows</enum></property> | |||||
| <property name="editTriggers"><set>QAbstractItemView::NoEditTriggers</set></property> | |||||
| <column><property name="text"><string>地址</string></property></column> | |||||
| <column><property name="text"><string>类型</string></property></column> | |||||
| <column><property name="text"><string>名称</string></property></column> | |||||
| <column><property name="text"><string>注释</string></property></column> | |||||
| <column><property name="text"><string>当前值</string></property></column> | |||||
| <column><property name="text"><string>引用位置</string></property></column> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <layout class="QHBoxLayout" name="dataPointEditorLayout"> | |||||
| <item><widget class="QLabel" name="dataPointAreaLabel"><property name="text"><string>区域</string></property></widget></item> | |||||
| <item><widget class="QComboBox" name="dataPointAreaComboBox"><item><property name="text"><string>M</string></property><property name="userData"><string notr="true">0</string></property></item><item><property name="text"><string>D</string></property><property name="userData"><string notr="true">1</string></property></item></widget></item> | |||||
| <item><widget class="QLabel" name="dataPointAddressLabel"><property name="text"><string>地址</string></property></widget></item> | |||||
| <item><widget class="QSpinBox" name="dataPointAddressSpinBox"><property name="maximum"><number>4000</number></property></widget></item> | |||||
| <item><widget class="QLabel" name="dataPointNameLabel"><property name="text"><string>名称</string></property></widget></item> | |||||
| <item><widget class="QLineEdit" name="dataPointNameEdit"/></item> | |||||
| <item><widget class="QLabel" name="dataPointCommentLabel"><property name="text"><string>注释</string></property></widget></item> | |||||
| <item><widget class="QLineEdit" name="dataPointCommentEdit"/></item> | |||||
| <item><widget class="QPushButton" name="addDataPointButton"><property name="text"><string>新增</string></property></widget></item> | |||||
| <item><widget class="QPushButton" name="updateDataPointButton"><property name="text"><string>更新</string></property></widget></item> | |||||
| <item><widget class="QPushButton" name="deleteDataPointButton"><property name="text"><string>删除</string></property></widget></item> | |||||
| </layout> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| </widget> | |||||
| <action name="exitAction"> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>退出(&X)</string> | <string>退出(&X)</string> | ||||
| </property> | </property> | ||||
| </action> | |||||
| </action> | |||||
| <action name="newProjectAction"><property name="text"><string>新建工程</string></property></action> | |||||
| <action name="saveProjectAction"><property name="text"><string>保存工程</string></property></action> | |||||
| <action name="saveAsProjectAction"><property name="text"><string>工程另存为</string></property></action> | |||||
| <action name="loadProjectAction"><property name="text"><string>加载工程</string></property></action> | |||||
| <action name="configurePlcAction"><property name="text"><string>PLC 配置</string></property><property name="toolTip"><string>配置参数并连接真实 PLC</string></property></action> | |||||
| <action name="disconnectPlcAction"><property name="text"><string>断开 PLC</string></property><property name="toolTip"><string>断开当前 PLC 连接</string></property></action> | |||||
| <action name="addButtonAction"><property name="text"><string>按钮</string></property><property name="toolTip"><string>添加按钮控件</string></property></action> | |||||
| <action name="addIndicatorAction"><property name="text"><string>指示灯</string></property><property name="toolTip"><string>添加指示灯控件</string></property></action> | |||||
| <action name="addNumericDisplayAction"><property name="text"><string>数值显示</string></property><property name="toolTip"><string>添加数值显示控件</string></property></action> | |||||
| <action name="addNumericInputAction"><property name="text"><string>数值输入</string></property><property name="toolTip"><string>添加数值输入控件</string></property></action> | |||||
| <action name="deleteControlAction"><property name="text"><string>删除</string></property><property name="toolTip"><string>删除当前控件</string></property></action> | |||||
| <action name="addRungAction"><property name="text"><string>新建网络</string></property><property name="toolTip"><string>在梯形图末尾新增网络</string></property></action> | |||||
| <action name="appendInsertAction"><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property><property name="text"><string>末尾追加</string></property><property name="toolTip"><string>将下一个条件追加到当前网络末尾</string></property></action> | |||||
| <action name="beforeInsertAction"><property name="checkable"><bool>true</bool></property><property name="text"><string>前插</string></property><property name="toolTip"><string>将下一个条件串联到选中节点或支路之前</string></property></action> | |||||
| <action name="afterInsertAction"><property name="checkable"><bool>true</bool></property><property name="text"><string>后插</string></property><property name="toolTip"><string>将下一个条件串联到选中节点或支路之后</string></property></action> | |||||
| <action name="parallelInsertAction"><property name="checkable"><bool>true</bool></property><property name="text"><string>并联插入</string></property><property name="toolTip"><string>将下一个条件并联到选中节点或整条支路</string></property></action> | |||||
| <action name="addNormallyOpenAction"><property name="text"><string>常开</string></property><property name="toolTip"><string>向当前网络添加常开触点</string></property></action> | |||||
| <action name="addNormallyClosedAction"><property name="text"><string>常闭</string></property><property name="toolTip"><string>向当前网络添加常闭触点</string></property></action> | |||||
| <action name="addNormalCoilAction"><property name="text"><string>线圈</string></property><property name="toolTip"><string>设置当前网络的普通线圈</string></property></action> | |||||
| <action name="addSetCoilAction"><property name="text"><string>置位</string></property><property name="toolTip"><string>设置当前网络的置位线圈</string></property></action> | |||||
| <action name="addResetCoilAction"><property name="text"><string>复位</string></property><property name="toolTip"><string>设置当前网络的复位线圈</string></property></action> | |||||
| <action name="addCompareAction"><property name="text"><string>D 比较</string></property><property name="toolTip"><string>向当前网络添加 D 值与常量比较</string></property></action> | |||||
| <action name="deleteLogicAction"><property name="text"><string>删除</string></property><property name="toolTip"><string>删除选中的逻辑节点或网络</string></property></action> | |||||
| <action name="editingModeAction"> | <action name="editingModeAction"> | ||||
| <property name="checkable"> | <property name="checkable"> | ||||
| <bool>true</bool> | <bool>true</bool> | ||||
| @@ -468,4 +622,4 @@ | |||||
| </widget> | </widget> | ||||
| <resources/> | <resources/> | ||||
| <connections/> | <connections/> | ||||
| </ui> | |||||
| </ui> | |||||
| @@ -0,0 +1,88 @@ | |||||
| #include "plc_connection_dialog.h" | |||||
| #include "ui_plc_connection_dialog.h" | |||||
| #include <QComboBox> | |||||
| #include <QSerialPortInfo> | |||||
| #include <initializer_list> | |||||
| namespace { | |||||
| void setItemData(QComboBox *combo_box, const std::initializer_list<int> &values) | |||||
| { | |||||
| int index = 0; | |||||
| for (int value : values) | |||||
| { | |||||
| combo_box->setItemData(index, value); | |||||
| ++index; | |||||
| } | |||||
| } | |||||
| void selectData(QComboBox *combo_box, int value) | |||||
| { | |||||
| const int index = combo_box->findData(value); | |||||
| if (index >= 0) | |||||
| { | |||||
| combo_box->setCurrentIndex(index); | |||||
| } | |||||
| } | |||||
| } // namespace | |||||
| PlcConnectionDialog::PlcConnectionDialog( | |||||
| const PlcSerialConfiguration &configuration, | |||||
| QWidget *parent) | |||||
| : QDialog(parent), | |||||
| ui_(std::make_unique<Ui::PlcConnectionDialog>()) | |||||
| { | |||||
| ui_->setupUi(this); | |||||
| setItemData(ui_->baudRateComboBox, {9600, 19200, 38400, 57600, 115200}); | |||||
| setItemData(ui_->dataBitsComboBox, {7, 8}); | |||||
| setItemData(ui_->parityComboBox, {0, 3, 2}); | |||||
| setItemData(ui_->stopBitsComboBox, {1, 2}); | |||||
| for (const QSerialPortInfo &port : QSerialPortInfo::availablePorts()) | |||||
| { | |||||
| ui_->serialPortComboBox->addItem( | |||||
| port.portName(), | |||||
| port.description().isEmpty() ? port.portName() : port.description()); | |||||
| } | |||||
| const QString port_name = QString::fromUtf8(configuration.portName.c_str()); | |||||
| if (!port_name.isEmpty()) | |||||
| { | |||||
| const int port_index = ui_->serialPortComboBox->findText(port_name); | |||||
| if (port_index < 0) | |||||
| { | |||||
| ui_->serialPortComboBox->addItem(port_name); | |||||
| } | |||||
| ui_->serialPortComboBox->setCurrentText(port_name); | |||||
| } | |||||
| ui_->serverAddressSpinBox->setValue(configuration.serverAddress); | |||||
| selectData(ui_->baudRateComboBox, configuration.baudRate); | |||||
| selectData(ui_->dataBitsComboBox, configuration.dataBits); | |||||
| selectData(ui_->parityComboBox, configuration.parity); | |||||
| selectData(ui_->stopBitsComboBox, configuration.stopBits); | |||||
| ui_->responseTimeoutSpinBox->setValue(configuration.responseTimeoutMs); | |||||
| ui_->retriesSpinBox->setValue(configuration.retries); | |||||
| ui_->pollIntervalSpinBox->setValue(configuration.pollIntervalMs); | |||||
| } | |||||
| PlcConnectionDialog::~PlcConnectionDialog() = default; | |||||
| PlcSerialConfiguration PlcConnectionDialog::configuration() const | |||||
| { | |||||
| PlcSerialConfiguration result; | |||||
| const QByteArray port_name = ui_->serialPortComboBox->currentText().trimmed().toUtf8(); | |||||
| result.portName.assign(port_name.constData(), static_cast<std::size_t>(port_name.size())); | |||||
| result.serverAddress = ui_->serverAddressSpinBox->value(); | |||||
| result.baudRate = ui_->baudRateComboBox->currentData().toInt(); | |||||
| result.dataBits = ui_->dataBitsComboBox->currentData().toInt(); | |||||
| result.parity = ui_->parityComboBox->currentData().toInt(); | |||||
| result.stopBits = ui_->stopBitsComboBox->currentData().toInt(); | |||||
| result.responseTimeoutMs = ui_->responseTimeoutSpinBox->value(); | |||||
| result.retries = ui_->retriesSpinBox->value(); | |||||
| result.pollIntervalMs = ui_->pollIntervalSpinBox->value(); | |||||
| return result; | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| #pragma once | |||||
| #include "services/plc_communication_gateway.h" | |||||
| #include <QDialog> | |||||
| #include <memory> | |||||
| namespace Ui { | |||||
| class PlcConnectionDialog; | |||||
| } | |||||
| class PlcConnectionDialog final : public QDialog | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| explicit PlcConnectionDialog( | |||||
| const PlcSerialConfiguration &configuration, | |||||
| QWidget *parent = nullptr); | |||||
| ~PlcConnectionDialog() override; | |||||
| PlcSerialConfiguration configuration() const; | |||||
| private: | |||||
| std::unique_ptr<Ui::PlcConnectionDialog> ui_; | |||||
| }; | |||||
| @@ -0,0 +1,85 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>PlcConnectionDialog</class> | |||||
| <widget class="QDialog" name="PlcConnectionDialog"> | |||||
| <property name="windowTitle"> | |||||
| <string>PLC 通信配置</string> | |||||
| </property> | |||||
| <property name="minimumSize"> | |||||
| <size><width>420</width><height>0</height></size> | |||||
| </property> | |||||
| <layout class="QVBoxLayout" name="dialogLayout"> | |||||
| <item> | |||||
| <widget class="QGroupBox" name="serialGroupBox"> | |||||
| <property name="title"><string>Modbus RTU 串口</string></property> | |||||
| <layout class="QFormLayout" name="serialFormLayout"> | |||||
| <property name="fieldGrowthPolicy"><enum>QFormLayout::AllNonFixedFieldsGrow</enum></property> | |||||
| <item row="0" column="0"><widget class="QLabel" name="serialPortLabel"><property name="text"><string>端口</string></property></widget></item> | |||||
| <item row="0" column="1"> | |||||
| <widget class="QComboBox" name="serialPortComboBox"> | |||||
| <property name="editable"><bool>true</bool></property> | |||||
| <property name="placeholderText"><string>选择或输入 COM 端口</string></property> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="serverAddressLabel"><property name="text"><string>PLC 站号</string></property></widget></item> | |||||
| <item row="1" column="1"><widget class="QSpinBox" name="serverAddressSpinBox"><property name="minimum"><number>1</number></property><property name="maximum"><number>247</number></property></widget></item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="baudRateLabel"><property name="text"><string>波特率</string></property></widget></item> | |||||
| <item row="2" column="1"> | |||||
| <widget class="QComboBox" name="baudRateComboBox"> | |||||
| <item><property name="text"><string>9600</string></property><property name="userData"><string notr="true">9600</string></property></item> | |||||
| <item><property name="text"><string>19200</string></property><property name="userData"><string notr="true">19200</string></property></item> | |||||
| <item><property name="text"><string>38400</string></property><property name="userData"><string notr="true">38400</string></property></item> | |||||
| <item><property name="text"><string>57600</string></property><property name="userData"><string notr="true">57600</string></property></item> | |||||
| <item><property name="text"><string>115200</string></property><property name="userData"><string notr="true">115200</string></property></item> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="3" column="0"><widget class="QLabel" name="dataBitsLabel"><property name="text"><string>数据位</string></property></widget></item> | |||||
| <item row="3" column="1"> | |||||
| <widget class="QComboBox" name="dataBitsComboBox"> | |||||
| <item><property name="text"><string>7</string></property><property name="userData"><string notr="true">7</string></property></item> | |||||
| <item><property name="text"><string>8</string></property><property name="userData"><string notr="true">8</string></property></item> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="4" column="0"><widget class="QLabel" name="parityLabel"><property name="text"><string>校验方式</string></property></widget></item> | |||||
| <item row="4" column="1"> | |||||
| <widget class="QComboBox" name="parityComboBox"> | |||||
| <item><property name="text"><string>无校验</string></property><property name="userData"><string notr="true">0</string></property></item> | |||||
| <item><property name="text"><string>奇校验</string></property><property name="userData"><string notr="true">3</string></property></item> | |||||
| <item><property name="text"><string>偶校验</string></property><property name="userData"><string notr="true">2</string></property></item> | |||||
| </widget> | |||||
| </item> | |||||
| <item row="5" column="0"><widget class="QLabel" name="stopBitsLabel"><property name="text"><string>停止位</string></property></widget></item> | |||||
| <item row="5" column="1"> | |||||
| <widget class="QComboBox" name="stopBitsComboBox"> | |||||
| <item><property name="text"><string>1</string></property><property name="userData"><string notr="true">1</string></property></item> | |||||
| <item><property name="text"><string>2</string></property><property name="userData"><string notr="true">2</string></property></item> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QGroupBox" name="timingGroupBox"> | |||||
| <property name="title"><string>通信时序</string></property> | |||||
| <layout class="QFormLayout" name="timingFormLayout"> | |||||
| <item row="0" column="0"><widget class="QLabel" name="responseTimeoutLabel"><property name="text"><string>响应超时</string></property></widget></item> | |||||
| <item row="0" column="1"><widget class="QSpinBox" name="responseTimeoutSpinBox"><property name="suffix"><string> ms</string></property><property name="minimum"><number>100</number></property><property name="maximum"><number>30000</number></property><property name="singleStep"><number>100</number></property><property name="value"><number>1000</number></property></widget></item> | |||||
| <item row="1" column="0"><widget class="QLabel" name="retriesLabel"><property name="text"><string>失败重试</string></property></widget></item> | |||||
| <item row="1" column="1"><widget class="QSpinBox" name="retriesSpinBox"><property name="suffix"><string> 次</string></property><property name="maximum"><number>10</number></property><property name="value"><number>2</number></property></widget></item> | |||||
| <item row="2" column="0"><widget class="QLabel" name="pollIntervalLabel"><property name="text"><string>轮询周期</string></property></widget></item> | |||||
| <item row="2" column="1"><widget class="QSpinBox" name="pollIntervalSpinBox"><property name="suffix"><string> ms</string></property><property name="minimum"><number>50</number></property><property name="maximum"><number>10000</number></property><property name="singleStep"><number>50</number></property><property name="value"><number>200</number></property></widget></item> | |||||
| </layout> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QDialogButtonBox" name="buttonBox"> | |||||
| <property name="standardButtons"><set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set></property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| <connections> | |||||
| <connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>PlcConnectionDialog</receiver><slot>accept()</slot><hints><hint type="sourcelabel"><x>320</x><y>360</y></hint><hint type="destinationlabel"><x>210</x><y>180</y></hint></hints></connection> | |||||
| <connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>PlcConnectionDialog</receiver><slot>reject()</slot><hints><hint type="sourcelabel"><x>320</x><y>360</y></hint><hint type="destinationlabel"><x>210</x><y>180</y></hint></hints></connection> | |||||
| </connections> | |||||
| </ui> | |||||
| @@ -2,6 +2,7 @@ | |||||
| #include "domain/register_repository.h" | #include "domain/register_repository.h" | ||||
| #include "services/hmi_editor_service.h" | #include "services/hmi_editor_service.h" | ||||
| #include "services/hmi_runtime_service.h" | #include "services/hmi_runtime_service.h" | ||||
| #include "services/data_point_service.h" | |||||
| #include "services/logic_editor_service.h" | #include "services/logic_editor_service.h" | ||||
| #include "services/offline_simulation_service.h" | #include "services/offline_simulation_service.h" | ||||
| #include "services/project_service.h" | #include "services/project_service.h" | ||||
| @@ -9,12 +10,17 @@ | |||||
| #include "ui/hmi_editor_widget.h" | #include "ui/hmi_editor_widget.h" | ||||
| #include "ui/logic_editor_widget.h" | #include "ui/logic_editor_widget.h" | ||||
| #include "ui/main_window.h" | #include "ui/main_window.h" | ||||
| #include "ui/plc_connection_dialog.h" | |||||
| #include <QAction> | #include <QAction> | ||||
| #include <QApplication> | #include <QApplication> | ||||
| #include <QComboBox> | |||||
| #include <QDialog> | |||||
| #include <QDockWidget> | #include <QDockWidget> | ||||
| #include <QLabel> | #include <QLabel> | ||||
| #include <QLineEdit> | #include <QLineEdit> | ||||
| #include <QSpinBox> | |||||
| #include <QTimer> | |||||
| #include <iostream> | #include <iostream> | ||||
| #include <stdexcept> | #include <stdexcept> | ||||
| @@ -60,6 +66,7 @@ void testModeActionsControlEditingAvailability() | |||||
| TestProjectStorage storage; | TestProjectStorage storage; | ||||
| ProjectService project_service(storage); | ProjectService project_service(storage); | ||||
| HmiEditorService editor_service(project_service); | HmiEditorService editor_service(project_service); | ||||
| DataPointService data_point_service(project_service); | |||||
| LogicEditorService logic_editor_service(project_service); | LogicEditorService logic_editor_service(project_service); | ||||
| VirtualRegisterRepository repository; | VirtualRegisterRepository repository; | ||||
| HmiRuntimeService runtime_service(repository); | HmiRuntimeService runtime_service(repository); | ||||
| @@ -69,6 +76,7 @@ void testModeActionsControlEditingAvailability() | |||||
| mode_service, | mode_service, | ||||
| project_service, | project_service, | ||||
| editor_service, | editor_service, | ||||
| data_point_service, | |||||
| logic_editor_service, | logic_editor_service, | ||||
| runtime_service); | runtime_service); | ||||
| window.resize(1000, 640); | window.resize(1000, 640); | ||||
| @@ -124,9 +132,11 @@ void testModeActionsControlEditingAvailability() | |||||
| logic_editor_service.firstLogicId()); | logic_editor_service.firstLogicId()); | ||||
| require(logic != nullptr && logic->rungs.size() == 1, | require(logic != nullptr && logic->rungs.size() == 1, | ||||
| "logic actions must edit the default ladder rung"); | "logic actions must edit the default ladder rung"); | ||||
| require(logic->rungs.front().stages.size() == 1 | |||||
| && logic->rungs.front().stages.front().branches.size() == 2, | |||||
| "parallel insert mode must add a branch to the selected stage"); | |||||
| require(logic->rungs.front().condition.has_value() | |||||
| && logic->rungs.front().condition->kind | |||||
| == ConditionExpressionKind::Parallel | |||||
| && logic->rungs.front().condition->children.size() == 2U, | |||||
| "parallel insert mode must create a parallel expression"); | |||||
| require(logic->rungs.front().output.has_value(), | require(logic->rungs.front().output.has_value(), | ||||
| "coil action must set the fixed ladder output"); | "coil action must set the fixed ladder output"); | ||||
| @@ -134,11 +144,13 @@ void testModeActionsControlEditingAvailability() | |||||
| require(mode_service.mode() == ApplicationMode::Editing, | require(mode_service.mode() == ApplicationMode::Editing, | ||||
| "unconfigured ladder nodes must block offline running"); | "unconfigured ladder nodes must block offline running"); | ||||
| const LadderRung &rung = logic->rungs.front(); | const LadderRung &rung = logic->rungs.front(); | ||||
| for (const LogicNode &node : rung.stages.front().branches) | |||||
| std::vector<const LogicNode *> condition_nodes; | |||||
| collectConditionNodes(*rung.condition, &condition_nodes); | |||||
| for (const LogicNode *node : condition_nodes) | |||||
| { | { | ||||
| require(logic_editor_service.updateNodeConfig( | require(logic_editor_service.updateNodeConfig( | ||||
| logic_editor_service.firstLogicId(), | logic_editor_service.firstLogicId(), | ||||
| node.id, | |||||
| node->id, | |||||
| ContactNodeConfig{ | ContactNodeConfig{ | ||||
| RegisterAddress{RegisterArea::M, 0}, | RegisterAddress{RegisterArea::M, 0}, | ||||
| ContactMode::NormallyOpen}) | ContactMode::NormallyOpen}) | ||||
| @@ -198,6 +210,70 @@ void testModeActionsControlEditingAvailability() | |||||
| "rejected online running must restore the editing action"); | "rejected online running must restore the editing action"); | ||||
| } | } | ||||
| void testPlcConfigurationUsesDialog() | |||||
| { | |||||
| PlcSerialConfiguration initial; | |||||
| initial.portName = "COM17"; | |||||
| initial.serverAddress = 12; | |||||
| initial.baudRate = 38400; | |||||
| initial.dataBits = 7; | |||||
| initial.parity = 3; | |||||
| initial.stopBits = 2; | |||||
| initial.responseTimeoutMs = 2500; | |||||
| initial.retries = 4; | |||||
| initial.pollIntervalMs = 350; | |||||
| PlcConnectionDialog configuration_dialog(initial); | |||||
| const PlcSerialConfiguration actual = configuration_dialog.configuration(); | |||||
| require(actual.portName == initial.portName, | |||||
| "PLC dialog must preserve a manually entered serial port"); | |||||
| require(actual.serverAddress == initial.serverAddress | |||||
| && actual.baudRate == initial.baudRate | |||||
| && actual.dataBits == initial.dataBits | |||||
| && actual.parity == initial.parity | |||||
| && actual.stopBits == initial.stopBits, | |||||
| "PLC dialog must preserve Modbus RTU serial parameters"); | |||||
| require(actual.responseTimeoutMs == initial.responseTimeoutMs | |||||
| && actual.retries == initial.retries | |||||
| && actual.pollIntervalMs == initial.pollIntervalMs, | |||||
| "PLC dialog must preserve communication timing parameters"); | |||||
| TestProjectStorage storage; | |||||
| ProjectService project_service(storage); | |||||
| HmiEditorService editor_service(project_service); | |||||
| DataPointService data_point_service(project_service); | |||||
| LogicEditorService logic_editor_service(project_service); | |||||
| VirtualRegisterRepository repository; | |||||
| HmiRuntimeService runtime_service(repository); | |||||
| OfflineSimulationService simulation_service(repository); | |||||
| RuntimeModeService mode_service(project_service, simulation_service); | |||||
| MainWindow window( | |||||
| mode_service, | |||||
| project_service, | |||||
| editor_service, | |||||
| data_point_service, | |||||
| logic_editor_service, | |||||
| runtime_service); | |||||
| require(window.findChild<QComboBox *>(QStringLiteral("serialPortComboBox")) == nullptr, | |||||
| "serial configuration controls must not be embedded in the main window"); | |||||
| QAction *configure_action = requiredChild<QAction>(window, "configurePlcAction"); | |||||
| bool dialog_opened = false; | |||||
| QTimer::singleShot( | |||||
| 0, | |||||
| [&dialog_opened] | |||||
| { | |||||
| auto *dialog = qobject_cast<PlcConnectionDialog *>( | |||||
| QApplication::activeModalWidget()); | |||||
| dialog_opened = dialog != nullptr; | |||||
| if (dialog != nullptr) | |||||
| { | |||||
| dialog->reject(); | |||||
| } | |||||
| }); | |||||
| configure_action->trigger(); | |||||
| require(dialog_opened, | |||||
| "PLC configuration action must open the dedicated configuration dialog"); | |||||
| } | |||||
| } // namespace | } // namespace | ||||
| int main(int argc, char *argv[]) | int main(int argc, char *argv[]) | ||||
| @@ -209,6 +285,7 @@ int main(int argc, char *argv[]) | |||||
| try | try | ||||
| { | { | ||||
| testModeActionsControlEditingAvailability(); | testModeActionsControlEditingAvailability(); | ||||
| testPlcConfigurationUsesDialog(); | |||||
| } | } | ||||
| catch (const std::exception &error) | catch (const std::exception &error) | ||||
| { | { | ||||
| @@ -1,4 +1,4 @@ | |||||
| QT += widgets | |||||
| QT += widgets serialport | |||||
| TEMPLATE = app | TEMPLATE = app | ||||
| TARGET = main_window_tests | TARGET = main_window_tests | ||||
| @@ -11,15 +11,19 @@ INCLUDEPATH += ../src | |||||
| SOURCES += \ | SOURCES += \ | ||||
| main_window_tests.cpp \ | main_window_tests.cpp \ | ||||
| ../src/ui/main_window.cpp \ | ../src/ui/main_window.cpp \ | ||||
| ../src/ui/plc_connection_dialog.cpp \ | |||||
| ../src/ui/hmi_editor_widget.cpp \ | ../src/ui/hmi_editor_widget.cpp \ | ||||
| ../src/ui/logic_editor_widget.cpp \ | ../src/ui/logic_editor_widget.cpp \ | ||||
| ../src/domain/register_address.cpp \ | ../src/domain/register_address.cpp \ | ||||
| ../src/domain/register_repository.cpp \ | ../src/domain/register_repository.cpp \ | ||||
| ../src/domain/active_register_repository.cpp \ | |||||
| ../src/domain/data_point_model.cpp \ | |||||
| ../src/domain/hmi_model.cpp \ | ../src/domain/hmi_model.cpp \ | ||||
| ../src/domain/control_logic_model.cpp \ | ../src/domain/control_logic_model.cpp \ | ||||
| ../src/domain/project_model.cpp \ | ../src/domain/project_model.cpp \ | ||||
| ../src/domain/runtime_state.cpp \ | ../src/domain/runtime_state.cpp \ | ||||
| ../src/services/project_service.cpp \ | ../src/services/project_service.cpp \ | ||||
| ../src/services/data_point_service.cpp \ | |||||
| ../src/services/hmi_editor_service.cpp \ | ../src/services/hmi_editor_service.cpp \ | ||||
| ../src/services/logic_editor_service.cpp \ | ../src/services/logic_editor_service.cpp \ | ||||
| ../src/services/hmi_runtime_service.cpp \ | ../src/services/hmi_runtime_service.cpp \ | ||||
| @@ -29,16 +33,20 @@ SOURCES += \ | |||||
| HEADERS += \ | HEADERS += \ | ||||
| ../src/ui/main_window.h \ | ../src/ui/main_window.h \ | ||||
| ../src/ui/plc_connection_dialog.h \ | |||||
| ../src/ui/hmi_editor_widget.h \ | ../src/ui/hmi_editor_widget.h \ | ||||
| ../src/ui/logic_editor_widget.h \ | ../src/ui/logic_editor_widget.h \ | ||||
| ../src/domain/register_address.h \ | ../src/domain/register_address.h \ | ||||
| ../src/domain/register_repository.h \ | ../src/domain/register_repository.h \ | ||||
| ../src/domain/active_register_repository.h \ | |||||
| ../src/domain/data_point_model.h \ | |||||
| ../src/domain/hmi_model.h \ | ../src/domain/hmi_model.h \ | ||||
| ../src/domain/control_logic_model.h \ | ../src/domain/control_logic_model.h \ | ||||
| ../src/domain/project_model.h \ | ../src/domain/project_model.h \ | ||||
| ../src/domain/project_storage.h \ | ../src/domain/project_storage.h \ | ||||
| ../src/domain/runtime_state.h \ | ../src/domain/runtime_state.h \ | ||||
| ../src/services/project_service.h \ | ../src/services/project_service.h \ | ||||
| ../src/services/data_point_service.h \ | |||||
| ../src/services/hmi_editor_service.h \ | ../src/services/hmi_editor_service.h \ | ||||
| ../src/services/logic_editor_service.h \ | ../src/services/logic_editor_service.h \ | ||||
| ../src/services/hmi_runtime_service.h \ | ../src/services/hmi_runtime_service.h \ | ||||
| @@ -46,5 +54,9 @@ HEADERS += \ | |||||
| ../src/services/offline_simulation_service.h \ | ../src/services/offline_simulation_service.h \ | ||||
| ../src/services/runtime_mode_service.h | ../src/services/runtime_mode_service.h | ||||
| HEADERS += \ | |||||
| ../src/services/plc_communication_gateway.h | |||||
| FORMS += \ | FORMS += \ | ||||
| ../src/ui/main_window.ui | |||||
| ../src/ui/main_window.ui \ | |||||
| ../src/ui/plc_connection_dialog.ui | |||||