综合平台编程器项目的远程存储
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 

239 rader
11 KiB

  1. #include "domain/project_storage.h"
  2. #include "domain/register_repository.h"
  3. #include "services/hmi_editor_service.h"
  4. #include "services/hmi_runtime_service.h"
  5. #include "services/hmi_navigation_service.h"
  6. #include "services/project_service.h"
  7. #include <exception>
  8. #include <iostream>
  9. #include <stdexcept>
  10. #include <string>
  11. namespace {
  12. class TestProjectStorage final : public ProjectStorage
  13. {
  14. public:
  15. // 编辑服务测试只关注内存模型变更,不依赖真实文件系统
  16. ProjectSaveResult save(const Project &, const std::string &) override
  17. {
  18. return {true, ProjectStorageError::None, {}};
  19. }
  20. ProjectLoadResult load(const std::string &) override
  21. {
  22. return {false, {}, ProjectStorageError::FileReadFailed, {}};
  23. }
  24. };
  25. void require(bool condition, const std::string &message)
  26. {
  27. if (!condition)
  28. {
  29. throw std::runtime_error(message);
  30. }
  31. }
  32. void testControlEditing()
  33. {
  34. // 覆盖控件创建、移动、绑定校验、重命名冲突和删除流程
  35. TestProjectStorage storage;
  36. ProjectService project_service(storage);
  37. HmiEditorService service(project_service);
  38. const HmiEditorResult page_result = service.ensureDefaultPage();
  39. require(page_result.succeeded, "default HMI page creation must succeed");
  40. const std::string page_id = page_result.id;
  41. const HmiEditorResult button = service.addControl(page_id, HmiControlType::Button);
  42. const HmiEditorResult indicator = service.addControl(page_id, HmiControlType::Indicator);
  43. const HmiEditorResult display = service.addControl(
  44. page_id, HmiControlType::NumericDisplay);
  45. const HmiEditorResult input = service.addControl(page_id, HmiControlType::NumericInput);
  46. require(button.succeeded && indicator.succeeded && display.succeeded && input.succeeded,
  47. "four basic HMI controls must be added");
  48. const HmiPage *page = service.findPage(page_id);
  49. require(page != nullptr && page->controls.size() == 4,
  50. "all added controls must be kept in the page model");
  51. require(service.moveControl(page_id, button.id, {120, 80, 120, 40}).succeeded,
  52. "a valid control move must succeed");
  53. require(!service.moveControl(page_id, button.id, {790, 460, 120, 40}).succeeded,
  54. "a move outside the page must be rejected");
  55. HmiControl updated = *service.findControl(page_id, button.id);
  56. updated.binding = RegisterAddress{RegisterArea::D, 0};
  57. require(!service.updateControl(page_id, button.id, updated).succeeded,
  58. "buttons must reject D address bindings during editing");
  59. updated.binding = RegisterAddress{RegisterArea::M, 7};
  60. updated.text = "启动";
  61. require(service.updateControl(page_id, button.id, updated).succeeded,
  62. "a button M binding and edited text must be accepted");
  63. HmiControl duplicate = *service.findControl(page_id, indicator.id);
  64. duplicate.id = button.id;
  65. require(service.updateControl(page_id, indicator.id, duplicate).error
  66. == HmiEditorError::DuplicateId,
  67. "duplicate control ids must be rejected");
  68. require(service.removeControl(page_id, indicator.id).succeeded,
  69. "deleting a selected control must succeed");
  70. require(service.findControl(page_id, indicator.id) == nullptr,
  71. "deleted controls must not remain in the model");
  72. }
  73. void testRuntimeUsesRegisterRepository()
  74. {
  75. // 运行服务只能经由仓库接口读写 M/D,不依赖具体离线实现
  76. VirtualRegisterRepository repository;
  77. HmiRuntimeService runtime_service(repository);
  78. HmiControl button;
  79. button.id = "start";
  80. button.type = HmiControlType::Button;
  81. button.binding = RegisterAddress{RegisterArea::M, 12};
  82. require(button.buttonOperation == HmiButtonOperation::MomentaryOn,
  83. "new buttons must default to momentary ON");
  84. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  85. "momentary button press must be accepted by the repository");
  86. require(repository.readBit(*button.binding).value,
  87. "momentary button press must write ON");
  88. require(runtime_service.operateButton(button, HmiButtonEvent::Released).succeeded,
  89. "momentary button release must be accepted by the repository");
  90. require(!repository.readBit(*button.binding).value,
  91. "momentary button release must write OFF");
  92. button.buttonOperation = HmiButtonOperation::SetOn;
  93. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  94. "set ON button press must succeed");
  95. require(repository.readBit(*button.binding).value,
  96. "set ON button press must write ON");
  97. require(runtime_service.operateButton(button, HmiButtonEvent::Released).succeeded,
  98. "set ON button release must be ignored successfully");
  99. require(repository.readBit(*button.binding).value,
  100. "set ON button release must keep the bit ON");
  101. button.buttonOperation = HmiButtonOperation::SetOff;
  102. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  103. "set OFF button press must succeed");
  104. require(!repository.readBit(*button.binding).value,
  105. "set OFF button press must write OFF");
  106. button.buttonOperation = HmiButtonOperation::Toggle;
  107. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  108. "toggle button press must succeed");
  109. require(repository.readBit(*button.binding).value,
  110. "toggle button press must invert the current value");
  111. HmiControl indicator;
  112. indicator.id = "running";
  113. indicator.type = HmiControlType::Indicator;
  114. indicator.binding = RegisterAddress{RegisterArea::M, 12};
  115. const HmiRuntimeReadResult indicator_value = runtime_service.readControl(indicator);
  116. require(indicator_value.succeeded && indicator_value.bit_value,
  117. "indicators must read M values through the repository");
  118. HmiControl numeric_input;
  119. numeric_input.id = "target";
  120. numeric_input.type = HmiControlType::NumericInput;
  121. numeric_input.binding = RegisterAddress{RegisterArea::D, 9};
  122. require(runtime_service.writeNumericInput(numeric_input, -18).succeeded,
  123. "numeric input runtime writes must be accepted by the repository");
  124. HmiControl numeric_display;
  125. numeric_display.id = "actual";
  126. numeric_display.type = HmiControlType::NumericDisplay;
  127. numeric_display.binding = RegisterAddress{RegisterArea::D, 9};
  128. const HmiRuntimeReadResult numeric_value = runtime_service.readControl(numeric_display);
  129. require(numeric_value.succeeded && numeric_value.word_value == -18,
  130. "numeric display must read D values through the repository");
  131. }
  132. void testPageLifecycleAndNavigation()
  133. {
  134. TestProjectStorage storage;
  135. ProjectService project_service(storage);
  136. HmiEditorService service(project_service);
  137. const std::string main_id = service.ensureDefaultPage().id;
  138. require(project_service.project().initialHmiPageId == main_id,
  139. "the first page must become the initial HMI page");
  140. const HmiEditorResult settings = service.addPage("Settings");
  141. const HmiEditorResult maintenance = service.addPage("Maintenance");
  142. require(settings.succeeded && maintenance.succeeded,
  143. "multiple HMI pages must be creatable");
  144. require(service.renamePage(settings.id, "Parameters").succeeded,
  145. "an HMI page must be renamable by stable id");
  146. require(service.renamePage(maintenance.id, "Parameters").error
  147. == HmiEditorError::DuplicateName,
  148. "HMI page names must remain unique");
  149. require(service.movePage(maintenance.id, -1).succeeded
  150. && project_service.project().hmiPages.at(1).id == maintenance.id,
  151. "HMI page order must be editable independently from page ids");
  152. const HmiEditorResult label = service.addControl(main_id, HmiControlType::Label);
  153. require(label.succeeded
  154. && service.findControl(main_id, label.id)->isConfigured(),
  155. "the editor service must expose a configured static Label control");
  156. const HmiEditorResult alarm_list = service.addControl(
  157. main_id, HmiControlType::AlarmList);
  158. require(alarm_list.succeeded
  159. && service.findControl(main_id, alarm_list.id)->isConfigured()
  160. && !service.findControl(main_id, alarm_list.id)->binding.has_value(),
  161. "AlarmList must be configured without a register binding");
  162. HmiControl bound_alarm_list = *service.findControl(main_id, alarm_list.id);
  163. bound_alarm_list.binding = RegisterAddress{RegisterArea::M, 20};
  164. require(!service.updateControl(
  165. main_id, alarm_list.id, bound_alarm_list).succeeded,
  166. "AlarmList must reject direct register bindings");
  167. const HmiEditorResult jump = service.addControl(main_id, HmiControlType::PageJump);
  168. require(jump.succeeded, "the editor service must expose PageJump creation");
  169. HmiControl jump_control = *service.findControl(main_id, jump.id);
  170. jump_control.pageJump = HmiPageJumpConfig{maintenance.id};
  171. require(service.updateControl(main_id, jump.id, jump_control).succeeded,
  172. "PageJump target updates must resolve stable page ids");
  173. require(service.removePage(maintenance.id).error == HmiEditorError::PageReferenced,
  174. "a page referenced by PageJump must not be deletable");
  175. require(service.setInitialPage(settings.id).succeeded,
  176. "the user must be able to select a different initial page");
  177. require(service.removePage(settings.id).error
  178. == HmiEditorError::InitialPageCannotBeRemoved,
  179. "the initial page must not be deleted implicitly");
  180. HmiNavigationService navigation(project_service);
  181. const HmiNavigationResult start = navigation.start();
  182. require(start.succeeded && start.pageId == settings.id,
  183. "runtime navigation must start from the persisted initial page");
  184. require(navigation.navigateTo(maintenance.id).succeeded
  185. && navigation.currentPageId() == maintenance.id,
  186. "runtime navigation must switch to an existing page");
  187. require(navigation.navigateTo("missing").error
  188. == HmiNavigationError::PageNotFound,
  189. "runtime navigation must reject an unknown page id");
  190. navigation.stop();
  191. require(navigation.currentPageId().empty(),
  192. "stopping runtime navigation must clear session state");
  193. }
  194. } // namespace
  195. int main()
  196. {
  197. try
  198. {
  199. // 编辑和运行场景分别验证服务层两条独立职责
  200. testControlEditing();
  201. testRuntimeUsesRegisterRepository();
  202. testPageLifecycleAndNavigation();
  203. }
  204. catch (const std::exception &error)
  205. {
  206. std::cerr << "HMI editor service tests failed: " << error.what() << '\n';
  207. return 1;
  208. }
  209. std::cout << "HMI editor service tests passed\n";
  210. return 0;
  211. }