综合平台编程器项目的远程存储
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

255 linhas
9.3 KiB

  1. #include "services/runtime_mode_service.h"
  2. #include "services/offline_simulation_service.h"
  3. #include "services/project_service.h"
  4. #include "domain/active_register_repository.h"
  5. #include "domain/project_storage.h"
  6. #include "domain/register_repository.h"
  7. #include "domain/virtual_register_repository.h"
  8. #include "support/test_support.h"
  9. #include <functional>
  10. #include <iostream>
  11. #include <stdexcept>
  12. #include <string>
  13. #include <utility>
  14. namespace {
  15. using TestProjectStorage = TestSupport::InMemoryProjectStorage;
  16. class ReadyPlcGateway final : public PlcCommunicationGateway
  17. {
  18. public:
  19. PlcCommunicationResult connectDevice(const PlcSerialConfiguration &) override
  20. {
  21. connection_state = PlcConnectionState::Connected;
  22. if (state_changed)
  23. {
  24. state_changed();
  25. }
  26. return {true, {}};
  27. }
  28. void disconnectDevice() override
  29. {
  30. connection_state = PlcConnectionState::Disconnected;
  31. initial_read = false;
  32. }
  33. PlcCommunicationResult setPollAddresses(
  34. const std::vector<RegisterAddress> &addresses) override
  35. {
  36. poll_addresses = addresses;
  37. return {true, {}};
  38. }
  39. PlcConnectionState state() const override { return connection_state; }
  40. bool initialReadCompleted() const override { return initial_read; }
  41. PlcCommunicationError lastErrorType() const override
  42. {
  43. return PlcCommunicationError::None;
  44. }
  45. const std::string &lastError() const override { return last_error; }
  46. void setCallbacks(
  47. std::function<void()> state_callback,
  48. std::function<void(bool)> initial_callback,
  49. std::function<void()> cache_callback,
  50. std::function<void(const std::string &)> error_callback) override
  51. {
  52. state_changed = std::move(state_callback);
  53. initial_read_changed = std::move(initial_callback);
  54. cache_updated = std::move(cache_callback);
  55. error_reported = std::move(error_callback);
  56. }
  57. void completeInitialRead()
  58. {
  59. initial_read = true;
  60. if (initial_read_changed)
  61. {
  62. initial_read_changed(true);
  63. }
  64. }
  65. const std::vector<RegisterAddress> &pollAddresses() const
  66. {
  67. return poll_addresses;
  68. }
  69. private:
  70. PlcConnectionState connection_state = PlcConnectionState::Disconnected;
  71. bool initial_read = false;
  72. std::string last_error;
  73. std::function<void()> state_changed;
  74. std::function<void(bool)> initial_read_changed;
  75. std::function<void()> cache_updated;
  76. std::function<void(const std::string &)> error_reported;
  77. std::vector<RegisterAddress> poll_addresses;
  78. };
  79. using TestSupport::require;
  80. void testModeTransitions()
  81. {
  82. // 验证服务将 PLC 首读状态与领域模式切换规则正确组合
  83. TestProjectStorage storage;
  84. ProjectService project_service(storage);
  85. VirtualRegisterRepository virtual_repository;
  86. VirtualRegisterRepository plc_repository;
  87. ActiveRegisterRepository active_repository(virtual_repository);
  88. OfflineSimulationService simulation_service(virtual_repository);
  89. ReadyPlcGateway gateway;
  90. RuntimeModeService service(project_service, simulation_service);
  91. service.configurePlc(
  92. gateway, active_repository, virtual_repository, plc_repository);
  93. Project &project = project_service.editProject();
  94. project.alarmDefinitions.push_back(
  95. {"alarm-m", {RegisterArea::M, 12}, AlarmCondition::MOn, 0, "M alarm"});
  96. project.alarmDefinitions.push_back(
  97. {"alarm-d", {RegisterArea::D, 34}, AlarmCondition::DHigh, 100, "D alarm"});
  98. project.registerComments = {
  99. {RegisterAddress{RegisterArea::M, 3999}, "只用于说明"},
  100. {RegisterAddress{RegisterArea::D, 3999}, "只用于说明"}};
  101. ControlLogic logic;
  102. logic.id = "poll-logic";
  103. logic.name = "Poll logic";
  104. LogicNode edge;
  105. edge.id = "poll-edge";
  106. edge.config = EdgeContactNodeConfig{
  107. RegisterAddress{RegisterArea::M, 20}, EdgeMode::Rising};
  108. LogicNode edge_coil;
  109. edge_coil.id = "poll-edge-coil";
  110. edge_coil.config = CoilNodeConfig{
  111. RegisterAddress{RegisterArea::M, 21}, CoilMode::Normal};
  112. LadderRung edge_rung;
  113. edge_rung.id = "poll-edge-rung";
  114. edge_rung.name = "Poll edge";
  115. edge_rung.condition = ConditionExpression::fromNode(edge);
  116. edge_rung.output = edge_coil;
  117. logic.rungs.push_back(edge_rung);
  118. LogicNode comparison;
  119. comparison.id = "poll-comparison";
  120. comparison.config = CompareNodeConfig{
  121. RegisterAddress{RegisterArea::D, 35}, ComparisonOperator::GreaterThan, 0};
  122. LogicNode comparison_coil;
  123. comparison_coil.id = "poll-comparison-coil";
  124. comparison_coil.config = CoilNodeConfig{
  125. RegisterAddress{RegisterArea::M, 22}, CoilMode::Normal};
  126. LadderRung comparison_rung;
  127. comparison_rung.id = "poll-comparison-rung";
  128. comparison_rung.name = "Poll comparison";
  129. comparison_rung.condition = ConditionExpression::fromNode(comparison);
  130. comparison_rung.output = comparison_coil;
  131. logic.rungs.push_back(comparison_rung);
  132. LogicNode move_input;
  133. move_input.id = "poll-move-input";
  134. move_input.config = ContactNodeConfig{
  135. RegisterAddress{RegisterArea::M, 27}, ContactMode::NormallyOpen};
  136. LogicNode move_output;
  137. move_output.id = "poll-move";
  138. move_output.config = MoveNodeConfig{
  139. WordOperand{
  140. WordOperandKind::Register,
  141. RegisterAddress{RegisterArea::D, 38},
  142. 0},
  143. RegisterAddress{RegisterArea::D, 39}};
  144. LadderRung move_rung;
  145. move_rung.id = "poll-move-rung";
  146. move_rung.name = "Poll MOVE";
  147. move_rung.condition = ConditionExpression::fromNode(move_input);
  148. move_rung.output = move_output;
  149. logic.rungs.push_back(move_rung);
  150. LogicNode add_input;
  151. add_input.id = "poll-add-input";
  152. add_input.config = ContactNodeConfig{
  153. RegisterAddress{RegisterArea::M, 28}, ContactMode::NormallyOpen};
  154. LogicNode add_output;
  155. add_output.id = "poll-add";
  156. add_output.config = ArithmeticNodeConfig{
  157. ArithmeticOperation::Add,
  158. WordOperand{
  159. WordOperandKind::Register,
  160. RegisterAddress{RegisterArea::D, 40},
  161. 0},
  162. WordOperand{
  163. WordOperandKind::Constant,
  164. RegisterAddress{RegisterArea::D, 0},
  165. 1},
  166. RegisterAddress{RegisterArea::D, 41}};
  167. LadderRung add_rung;
  168. add_rung.id = "poll-add-rung";
  169. add_rung.name = "Poll ADD";
  170. add_rung.condition = ConditionExpression::fromNode(add_input);
  171. add_rung.output = add_output;
  172. logic.rungs.push_back(add_rung);
  173. project.controlLogics.push_back(logic);
  174. require(service.mode() == ApplicationMode::Editing,
  175. "service must start in editing mode");
  176. require(service.policy().allowsProjectEditing,
  177. "editing mode must allow project editing");
  178. require(service.enterOfflineRunning().succeeded,
  179. "editing mode must enter offline running");
  180. require(service.simulationState() == SimulationState::Running,
  181. "offline mode must start the software executor");
  182. require(service.policy().usesVirtualRegisters,
  183. "offline running must use virtual registers");
  184. require(service.enterOnlineRunning().error
  185. == ModeTransitionError::MustReturnToEditing,
  186. "running modes must not switch directly");
  187. require(service.enterEditing().succeeded,
  188. "offline running must return to editing");
  189. require(service.simulationState() == SimulationState::Stopped,
  190. "returning to editing must stop the software executor first");
  191. require(service.enterOnlineRunning().error
  192. == ModeTransitionError::InitialPlcReadRequired,
  193. "online running must require an initial PLC read");
  194. require(service.connectPlc(
  195. {"COM9", 1, 9600, 8, 2, 1, 1000, 2, 200}).succeeded,
  196. "PLC connection must be established before its initial read can complete");
  197. require(gateway.pollAddresses()
  198. == std::vector<RegisterAddress>({
  199. {RegisterArea::M, 12}, {RegisterArea::M, 20},
  200. {RegisterArea::M, 21}, {RegisterArea::M, 22},
  201. {RegisterArea::M, 27}, {RegisterArea::M, 28},
  202. {RegisterArea::D, 34}, {RegisterArea::D, 35},
  203. {RegisterArea::D, 38}, {RegisterArea::D, 39},
  204. {RegisterArea::D, 40}, {RegisterArea::D, 41}}),
  205. "all instruction M/D references must be polled while comments stay metadata-only");
  206. gateway.completeInitialRead();
  207. require(service.initialPlcReadCompleted(),
  208. "service must retain the initial PLC read state");
  209. require(service.enterOnlineRunning().succeeded,
  210. "online running must start after an initial PLC read");
  211. require(service.simulationState() == SimulationState::Stopped,
  212. "online running must never start the software executor");
  213. require(service.policy().usesPlcRegisters,
  214. "online running must use PLC registers");
  215. require(!service.policy().runsLogicExecutor,
  216. "online running must keep the software executor stopped");
  217. }
  218. } // namespace
  219. int main()
  220. {
  221. try
  222. {
  223. // 运行模式只有这一组状态机边界测试
  224. testModeTransitions();
  225. }
  226. catch (const std::exception &error)
  227. {
  228. std::cerr << "runtime mode service tests failed: " << error.what() << '\n';
  229. return 1;
  230. }
  231. std::cout << "runtime mode service tests passed\n";
  232. return 0;
  233. }