综合平台编程器项目的远程存储
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

287 satır
11 KiB

  1. #include "domain/hmi_model.h"
  2. #include "domain/register_repository.h"
  3. #include "services/hmi_runtime_service.h"
  4. #include "services/offline_simulation_service.h"
  5. #include "services/software_logic_executor.h"
  6. #include <QCoreApplication>
  7. #include <array>
  8. #include <iostream>
  9. #include <stdexcept>
  10. #include <string>
  11. #include <vector>
  12. namespace {
  13. void require(bool condition, const std::string &message)
  14. {
  15. if (!condition)
  16. {
  17. throw std::runtime_error(message);
  18. }
  19. }
  20. LogicNode contact(const std::string &id, int address,
  21. ContactMode mode = ContactMode::NormallyOpen)
  22. {
  23. return {id, ContactNodeConfig{RegisterAddress{RegisterArea::M, address}, mode}, true};
  24. }
  25. LogicNode comparison(const std::string &id, int address,
  26. ComparisonOperator operation, std::int16_t value)
  27. {
  28. return {id,
  29. CompareNodeConfig{RegisterAddress{RegisterArea::D, address}, operation, value},
  30. true};
  31. }
  32. LogicNode coil(const std::string &id, int address,
  33. CoilMode mode = CoilMode::Normal)
  34. {
  35. return {id, CoilNodeConfig{RegisterAddress{RegisterArea::M, address}, mode}, true};
  36. }
  37. LadderRung rung(const std::string &id,
  38. const std::vector<std::vector<LogicNode>> &stages,
  39. const LogicNode &output)
  40. {
  41. LadderRung result;
  42. result.id = id;
  43. result.name = id;
  44. for (std::size_t index = 0; index < stages.size(); ++index)
  45. {
  46. result.stages.push_back({id + "-stage-" + std::to_string(index), stages[index]});
  47. }
  48. result.output = output;
  49. return result;
  50. }
  51. ControlLogic logic(const std::vector<LadderRung> &rungs)
  52. {
  53. return {"logic-1", "logic-1", rungs, true};
  54. }
  55. bool readBit(RegisterRepository &repository, int address)
  56. {
  57. const BitReadResult result = repository.readBit(
  58. RegisterAddress{RegisterArea::M, address});
  59. require(result.succeeded, "test register read must succeed");
  60. return result.value;
  61. }
  62. void writeBit(RegisterRepository &repository, int address, bool value)
  63. {
  64. require(repository.writeBit(RegisterAddress{RegisterArea::M, address}, value).succeeded,
  65. "test bit write must succeed");
  66. }
  67. void writeWord(RegisterRepository &repository, int address, std::int16_t value)
  68. {
  69. require(repository.writeWord(RegisterAddress{RegisterArea::D, address}, value).succeeded,
  70. "test word write must succeed");
  71. }
  72. void testSeriesParallelContactsAndSequentialVisibility()
  73. {
  74. VirtualRegisterRepository repository;
  75. SoftwareLogicExecutor executor;
  76. const ControlLogic program = logic({
  77. rung("rung-1",
  78. {{contact("start", 0), contact("alternate", 1)},
  79. {contact("stop", 2, ContactMode::NormallyClosed)}},
  80. coil("run", 3)),
  81. rung("rung-2", {{contact("run-feedback", 3)}}, coil("downstream", 4))});
  82. writeBit(repository, 1, true);
  83. require(executor.executeScan({program}, repository).succeeded,
  84. "parallel and series scan must succeed");
  85. require(readBit(repository, 3), "parallel OR and series AND must energize output");
  86. require(readBit(repository, 4),
  87. "a later rung must see an earlier rung write in the same scan");
  88. writeBit(repository, 2, true);
  89. require(executor.executeScan({program}, repository).succeeded,
  90. "normally closed scan must succeed");
  91. require(!readBit(repository, 3), "normally closed stop contact must open the rung");
  92. require(!readBit(repository, 4), "downstream normal coil must follow the new value");
  93. }
  94. void testAllComparisons()
  95. {
  96. const std::array<ComparisonOperator, 6> operations{
  97. ComparisonOperator::Equal, ComparisonOperator::NotEqual,
  98. ComparisonOperator::LessThan, ComparisonOperator::LessThanOrEqual,
  99. ComparisonOperator::GreaterThan, ComparisonOperator::GreaterThanOrEqual};
  100. const std::array<std::int16_t, 6> actual_values{10, 9, 9, 10, 11, 10};
  101. for (std::size_t index = 0; index < operations.size(); ++index)
  102. {
  103. VirtualRegisterRepository repository;
  104. SoftwareLogicExecutor executor;
  105. writeWord(repository, 0, actual_values[index]);
  106. const ControlLogic program = logic({
  107. rung("compare-rung", {{comparison("compare", 0, operations[index], 10)},
  108. }, coil("result", 10))});
  109. require(executor.executeScan({program}, repository).succeeded,
  110. "comparison scan must succeed");
  111. require(readBit(repository, 10), "comparison operator must evaluate true");
  112. }
  113. }
  114. void testSetResetAndDisabledLogic()
  115. {
  116. VirtualRegisterRepository repository;
  117. SoftwareLogicExecutor executor;
  118. ControlLogic program = logic({
  119. rung("set-rung", {{contact("set-input", 0)}}, coil("set-output", 5, CoilMode::Set)),
  120. rung("reset-rung", {{contact("reset-input", 1)}},
  121. coil("reset-output", 6, CoilMode::Reset))});
  122. writeBit(repository, 0, true);
  123. require(executor.executeScan({program}, repository).succeeded, "set scan must succeed");
  124. require(readBit(repository, 5), "set coil must latch true");
  125. writeBit(repository, 0, false);
  126. require(executor.executeScan({program}, repository).succeeded,
  127. "inactive set scan must succeed");
  128. require(readBit(repository, 5), "inactive set coil must retain its value");
  129. writeBit(repository, 1, true);
  130. writeBit(repository, 6, true);
  131. require(executor.executeScan({program}, repository).succeeded,
  132. "reset scan must succeed");
  133. require(!readBit(repository, 6), "reset coil must write false");
  134. program.enabled = false;
  135. writeBit(repository, 5, true);
  136. require(executor.executeScan({program}, repository).succeeded,
  137. "disabled logic scan must be ignored successfully");
  138. require(readBit(repository, 5), "disabled logic must not change outputs");
  139. }
  140. void testConflictingCoilsAreRejected()
  141. {
  142. SoftwareLogicExecutor executor;
  143. const ControlLogic program = logic({
  144. rung("normal-rung", {{contact("normal-input", 0)}}, coil("normal", 8)),
  145. rung("set-rung", {{contact("set-input", 1)}}, coil("set", 8, CoilMode::Set))});
  146. const LogicScanResult result = executor.validate({program});
  147. require(!result.succeeded && result.error == LogicScanError::ConflictingOutput,
  148. "mixed output modes for one address must be rejected");
  149. require(result.rungId == "set-rung" && result.nodeId == "set",
  150. "conflict error must identify the offending rung and node");
  151. }
  152. void testHmiSimulationClosedLoop()
  153. {
  154. VirtualRegisterRepository repository;
  155. HmiRuntimeService hmi(repository);
  156. SoftwareLogicExecutor executor;
  157. HmiControl start;
  158. start.id = "start-button";
  159. start.type = HmiControlType::Button;
  160. start.bounds = {0, 0, 80, 30};
  161. start.text = "start";
  162. start.binding = RegisterAddress{RegisterArea::M, 0};
  163. HmiControl indicator;
  164. indicator.id = "run-indicator";
  165. indicator.type = HmiControlType::Indicator;
  166. indicator.bounds = {0, 40, 80, 30};
  167. indicator.text = "run";
  168. indicator.binding = RegisterAddress{RegisterArea::M, 2};
  169. const ControlLogic program = logic({
  170. rung("hold-rung",
  171. {{contact("stop", 1, ContactMode::NormallyClosed)},
  172. {contact("start", 0), contact("feedback", 2)}},
  173. coil("run", 2))});
  174. require(hmi.toggleButton(start).succeeded, "HMI start button must write virtual M");
  175. require(executor.executeScan({program}, repository).succeeded,
  176. "closed-loop scan must succeed");
  177. require(hmi.readControl(indicator).bit_value,
  178. "HMI indicator must observe the logic output");
  179. require(hmi.toggleButton(start).succeeded, "HMI start button must toggle off");
  180. require(executor.executeScan({program}, repository).succeeded, "holding scan must succeed");
  181. require(hmi.readControl(indicator).bit_value,
  182. "feedback contact must hold the output after start turns off");
  183. writeBit(repository, 1, true);
  184. require(executor.executeScan({program}, repository).succeeded, "stop scan must succeed");
  185. require(!hmi.readControl(indicator).bit_value,
  186. "stop contact must clear the HMI run indication");
  187. }
  188. void testSimulationLifecycleSnapshotAndFault()
  189. {
  190. VirtualRegisterRepository repository;
  191. OfflineSimulationService simulation(repository);
  192. ControlLogic program = logic({
  193. rung("snapshot-rung", {{contact("input", 0)}}, coil("output", 1))});
  194. writeBit(repository, 0, true);
  195. require(simulation.start({program}).succeeded, "simulation must start");
  196. require(simulation.state() == SimulationState::Running, "simulation must report running");
  197. require(!readBit(repository, 0), "starting a session must clear virtual registers");
  198. require(simulation.scanIntervalMs() == 50, "default scan interval must be 50 ms");
  199. writeBit(repository, 0, true);
  200. program.rungs.front().output = coil("changed-output", 9);
  201. require(simulation.executeOnce().succeeded, "manual lifecycle scan must succeed");
  202. require(readBit(repository, 1), "simulation must use its start-time snapshot");
  203. require(simulation.successfulScanCount() == 1, "successful scans must be counted");
  204. simulation.stop();
  205. require(simulation.state() == SimulationState::Stopped, "simulation must stop");
  206. require(simulation.start({program}).succeeded, "stopped simulation must support restart");
  207. require(!readBit(repository, 1), "new session must clear prior output values");
  208. simulation.stop();
  209. }
  210. class FailingVirtualRegisterRepository final : public VirtualRegisterRepository
  211. {
  212. public:
  213. BitReadResult readBit(const RegisterAddress &) const override
  214. {
  215. return {false, false, RegisterError::Unavailable};
  216. }
  217. };
  218. void testRepositoryFailureEntersFaultState()
  219. {
  220. FailingVirtualRegisterRepository repository;
  221. OfflineSimulationService simulation(repository);
  222. const ControlLogic program = logic({
  223. rung("fault-rung", {{contact("fault-input", 0)}}, coil("fault-output", 1))});
  224. require(simulation.start({program}).succeeded,
  225. "valid logic must pass simulation startup");
  226. const LogicScanResult result = simulation.executeOnce();
  227. require(!result.succeeded && result.error == LogicScanError::RegisterReadFailed,
  228. "repository read failure must fail the scan");
  229. require(simulation.state() == SimulationState::Faulted,
  230. "repository failure must enter the fault state");
  231. require(simulation.lastError().logicId == "logic-1"
  232. && simulation.lastError().rungId == "fault-rung"
  233. && simulation.lastError().nodeId == "fault-input",
  234. "fault feedback must retain logic, rung and node context");
  235. }
  236. } // namespace
  237. int main(int argc, char *argv[])
  238. {
  239. QCoreApplication application(argc, argv);
  240. try
  241. {
  242. testSeriesParallelContactsAndSequentialVisibility();
  243. testAllComparisons();
  244. testSetResetAndDisabledLogic();
  245. testConflictingCoilsAreRejected();
  246. testHmiSimulationClosedLoop();
  247. testSimulationLifecycleSnapshotAndFault();
  248. testRepositoryFailureEntersFaultState();
  249. }
  250. catch (const std::exception &error)
  251. {
  252. std::cerr << "offline simulation service tests failed: " << error.what() << '\n';
  253. return 1;
  254. }
  255. std::cout << "offline simulation service tests passed\n";
  256. return 0;
  257. }