综合平台编程器项目的远程存储
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

297 righe
11 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()> poll_cycle_callback,
  51. std::function<void(const std::string &)> error_callback) override
  52. {
  53. state_changed = std::move(state_callback);
  54. initial_read_changed = std::move(initial_callback);
  55. cache_updated = std::move(cache_callback);
  56. poll_cycle_completed = std::move(poll_cycle_callback);
  57. error_reported = std::move(error_callback);
  58. }
  59. void completeInitialRead()
  60. {
  61. initial_read = true;
  62. if (initial_read_changed)
  63. {
  64. initial_read_changed(true);
  65. }
  66. }
  67. void completePollCycle()
  68. {
  69. if (poll_cycle_completed)
  70. {
  71. poll_cycle_completed();
  72. }
  73. }
  74. const std::vector<RegisterAddress> &pollAddresses() const
  75. {
  76. return poll_addresses;
  77. }
  78. private:
  79. PlcConnectionState connection_state = PlcConnectionState::Disconnected;
  80. bool initial_read = false;
  81. std::string last_error;
  82. std::function<void()> state_changed;
  83. std::function<void(bool)> initial_read_changed;
  84. std::function<void()> cache_updated;
  85. std::function<void()> poll_cycle_completed;
  86. std::function<void(const std::string &)> error_reported;
  87. std::vector<RegisterAddress> poll_addresses;
  88. };
  89. using TestSupport::require;
  90. ConditionExpression conditionWithOutputWire(
  91. LogicNode node,
  92. const std::string &wire_id)
  93. {
  94. ConditionExpression series;
  95. series.id = wire_id + "-series";
  96. series.kind = ConditionExpressionKind::Series;
  97. series.children = {
  98. ConditionExpression::fromNode(std::move(node)),
  99. ConditionExpression::fromWire(wire_id, 9)};
  100. return series;
  101. }
  102. void testModeTransitions()
  103. {
  104. // 验证服务将 PLC 首读状态与领域模式切换规则正确组合
  105. TestProjectStorage storage;
  106. ProjectService project_service(storage);
  107. VirtualRegisterRepository virtual_repository;
  108. VirtualRegisterRepository plc_repository;
  109. ActiveRegisterRepository active_repository(virtual_repository);
  110. OfflineSimulationService simulation_service(virtual_repository);
  111. OnlineLogicMonitorService online_monitor_service(plc_repository);
  112. ReadyPlcGateway gateway;
  113. RuntimeModeService service(
  114. project_service, simulation_service, online_monitor_service);
  115. service.configurePlc(
  116. gateway, active_repository, virtual_repository, plc_repository);
  117. Project &project = project_service.editProject();
  118. project.alarmDefinitions.push_back(
  119. {"alarm-m", {RegisterArea::M, 12}, AlarmCondition::MOn, 0, "M alarm"});
  120. project.alarmDefinitions.push_back(
  121. {"alarm-d", {RegisterArea::D, 34}, AlarmCondition::DHigh, 100, "D alarm"});
  122. project.registerComments = {
  123. {RegisterAddress{RegisterArea::M, 3999}, "只用于说明"},
  124. {RegisterAddress{RegisterArea::D, 3999}, "只用于说明"}};
  125. ControlLogic logic;
  126. logic.id = "poll-logic";
  127. logic.name = "Poll logic";
  128. LogicNode edge;
  129. edge.id = "poll-edge";
  130. edge.config = EdgeContactNodeConfig{
  131. RegisterAddress{RegisterArea::M, 20}, EdgeMode::Rising};
  132. LogicNode edge_coil;
  133. edge_coil.id = "poll-edge-coil";
  134. edge_coil.config = CoilNodeConfig{
  135. RegisterAddress{RegisterArea::M, 21}, CoilMode::Normal};
  136. LadderRung edge_rung;
  137. edge_rung.id = "poll-edge-rung";
  138. edge_rung.name = "Poll edge";
  139. edge_rung.condition = conditionWithOutputWire(
  140. edge, "poll-edge-output-wire");
  141. edge_rung.output = edge_coil;
  142. logic.rungs.push_back(edge_rung);
  143. LogicNode comparison;
  144. comparison.id = "poll-comparison";
  145. comparison.config = CompareNodeConfig{
  146. RegisterAddress{RegisterArea::D, 35}, ComparisonOperator::GreaterThan, 0};
  147. LogicNode comparison_coil;
  148. comparison_coil.id = "poll-comparison-coil";
  149. comparison_coil.config = CoilNodeConfig{
  150. RegisterAddress{RegisterArea::M, 22}, CoilMode::Normal};
  151. LadderRung comparison_rung;
  152. comparison_rung.id = "poll-comparison-rung";
  153. comparison_rung.name = "Poll comparison";
  154. comparison_rung.condition = conditionWithOutputWire(
  155. comparison, "poll-comparison-output-wire");
  156. comparison_rung.output = comparison_coil;
  157. logic.rungs.push_back(comparison_rung);
  158. LogicNode move_input;
  159. move_input.id = "poll-move-input";
  160. move_input.config = ContactNodeConfig{
  161. RegisterAddress{RegisterArea::M, 27}, ContactMode::NormallyOpen};
  162. LogicNode move_output;
  163. move_output.id = "poll-move";
  164. move_output.config = MoveNodeConfig{
  165. WordOperand{
  166. WordOperandKind::Register,
  167. RegisterAddress{RegisterArea::D, 38},
  168. 0},
  169. RegisterAddress{RegisterArea::D, 39}};
  170. LadderRung move_rung;
  171. move_rung.id = "poll-move-rung";
  172. move_rung.name = "Poll MOVE";
  173. move_rung.condition = conditionWithOutputWire(
  174. move_input, "poll-move-output-wire");
  175. move_rung.output = move_output;
  176. logic.rungs.push_back(move_rung);
  177. LogicNode add_input;
  178. add_input.id = "poll-add-input";
  179. add_input.config = ContactNodeConfig{
  180. RegisterAddress{RegisterArea::M, 28}, ContactMode::NormallyOpen};
  181. LogicNode add_output;
  182. add_output.id = "poll-add";
  183. add_output.config = ArithmeticNodeConfig{
  184. ArithmeticOperation::Add,
  185. WordOperand{
  186. WordOperandKind::Register,
  187. RegisterAddress{RegisterArea::D, 40},
  188. 0},
  189. WordOperand{
  190. WordOperandKind::Constant,
  191. RegisterAddress{RegisterArea::D, 0},
  192. 1},
  193. RegisterAddress{RegisterArea::D, 41}};
  194. LadderRung add_rung;
  195. add_rung.id = "poll-add-rung";
  196. add_rung.name = "Poll ADD";
  197. add_rung.condition = conditionWithOutputWire(
  198. add_input, "poll-add-output-wire");
  199. add_rung.output = add_output;
  200. logic.rungs.push_back(add_rung);
  201. project.controlLogics.push_back(logic);
  202. require(service.mode() == ApplicationMode::Editing,
  203. "service must start in editing mode");
  204. require(service.policy().allowsProjectEditing,
  205. "editing mode must allow project editing");
  206. require(service.enterOfflineRunning().succeeded,
  207. "editing mode must enter offline running");
  208. require(service.simulationState() == SimulationState::Running,
  209. "offline mode must start the software executor");
  210. require(service.policy().usesVirtualRegisters,
  211. "offline running must use virtual registers");
  212. require(service.enterOnlineRunning().error
  213. == ModeTransitionError::MustReturnToEditing,
  214. "running modes must not switch directly");
  215. require(service.enterEditing().succeeded,
  216. "offline running must return to editing");
  217. require(service.simulationState() == SimulationState::Stopped,
  218. "returning to editing must stop the software executor first");
  219. require(service.enterOnlineRunning().error
  220. == ModeTransitionError::InitialPlcReadRequired,
  221. "online running must require an initial PLC read");
  222. require(service.connectPlc(
  223. {"COM9", 1, 9600, 8, 2, 1, 1000, 2, 200}).succeeded,
  224. "PLC connection must be established before its initial read can complete");
  225. require(gateway.pollAddresses()
  226. == std::vector<RegisterAddress>({
  227. {RegisterArea::M, 12}, {RegisterArea::M, 20},
  228. {RegisterArea::M, 21}, {RegisterArea::M, 22},
  229. {RegisterArea::M, 27}, {RegisterArea::M, 28},
  230. {RegisterArea::D, 34}, {RegisterArea::D, 35},
  231. {RegisterArea::D, 38}, {RegisterArea::D, 39},
  232. {RegisterArea::D, 40}, {RegisterArea::D, 41}}),
  233. "all instruction M/D references must be polled while comments stay metadata-only");
  234. gateway.completeInitialRead();
  235. require(service.initialPlcReadCompleted(),
  236. "service must retain the initial PLC read state");
  237. plc_repository.writeBit({RegisterArea::M, 20}, true);
  238. require(service.enterOnlineRunning().succeeded,
  239. "online running must start after an initial PLC read");
  240. require(service.simulationState() == SimulationState::Stopped,
  241. "online running must keep the offline executor stopped");
  242. require(service.onlineLogicMonitorService().state()
  243. == OnlineLogicMonitorState::Running,
  244. "online running must start the local read-only trace executor");
  245. require(!plc_repository.readBit({RegisterArea::M, 21}).value,
  246. "the local trace output must not change the PLC source repository");
  247. require(service.policy().usesPlcRegisters,
  248. "online running must use PLC registers");
  249. require(service.policy().runsLogicExecutor,
  250. "online running must advertise the local read-only trace executor");
  251. const std::uint64_t scan_count = service.onlineLogicMonitorService()
  252. .successfulScanCount();
  253. gateway.completePollCycle();
  254. require(service.onlineLogicMonitorService().successfulScanCount()
  255. == scan_count + 1U,
  256. "a completed PLC poll cycle must trigger one new local trace scan");
  257. }
  258. } // namespace
  259. int main()
  260. {
  261. try
  262. {
  263. // 运行模式只有这一组状态机边界测试
  264. testModeTransitions();
  265. }
  266. catch (const std::exception &error)
  267. {
  268. std::cerr << "runtime mode service tests failed: " << error.what() << '\n';
  269. return 1;
  270. }
  271. std::cout << "runtime mode service tests passed\n";
  272. return 0;
  273. }