综合平台编程器项目的远程存储
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

175 rivejä
6.3 KiB

  1. #include "domain/control_logic_model.h"
  2. #include "domain/hmi_model.h"
  3. #include "domain/project_model.h"
  4. #include "domain/register_address.h"
  5. #include "domain/register_repository.h"
  6. #include "domain/runtime_state.h"
  7. #include <cstdint>
  8. #include <exception>
  9. #include <iostream>
  10. #include <stdexcept>
  11. #include <string>
  12. namespace domain = integrated_platform::domain;
  13. namespace {
  14. void require(bool condition, const std::string &message)
  15. {
  16. if (!condition)
  17. {
  18. throw std::runtime_error(message);
  19. }
  20. }
  21. void testRegisterAddressBoundaries()
  22. {
  23. require(domain::RegisterAddress{domain::RegisterArea::M, 0}.isValid(),
  24. "M0 must be valid");
  25. require(domain::RegisterAddress{domain::RegisterArea::D, 4000}.isValid(),
  26. "D4000 must be valid");
  27. require(!(domain::RegisterAddress{domain::RegisterArea::M, -1}.isValid()),
  28. "negative register index must be rejected");
  29. require(!(domain::RegisterAddress{domain::RegisterArea::D, 4001}.isValid()),
  30. "register index above 4000 must be rejected");
  31. require(!(domain::RegisterAddress{static_cast<domain::RegisterArea>(99), 0}.isValid()),
  32. "unknown register area must be rejected");
  33. }
  34. void testRegisterRepositorySeparatesAreas()
  35. {
  36. domain::VirtualRegisterRepository repository;
  37. const domain::RegisterAddress m0{domain::RegisterArea::M, 0};
  38. const domain::RegisterAddress d0{domain::RegisterArea::D, 0};
  39. require(repository.writeBit(m0, true).succeeded, "M bit write must succeed");
  40. require(repository.readBit(m0).value, "M bit read must return written value");
  41. require(repository.writeWord(d0, static_cast<std::int16_t>(-123)).succeeded,
  42. "D word write must succeed");
  43. require(repository.readWord(d0).value == -123, "D word read must return written value");
  44. require(repository.readBit(d0).error == domain::RegisterError::AreaMismatch,
  45. "D address must not be read as a bit");
  46. require(repository.readWord(m0).error == domain::RegisterError::AreaMismatch,
  47. "M address must not be read as a word");
  48. }
  49. domain::Project makeValidProject()
  50. {
  51. domain::HmiControl start_button;
  52. start_button.id = "start-button";
  53. start_button.type = domain::HmiControlType::Button;
  54. start_button.text = "Start";
  55. start_button.binding = {domain::RegisterArea::M, 0};
  56. domain::HmiPage page;
  57. page.id = "main-page";
  58. page.name = "Main";
  59. page.controls.push_back(start_button);
  60. domain::LogicNode contact;
  61. contact.id = "start-contact";
  62. contact.config = domain::ContactNodeConfig{
  63. domain::RegisterAddress{domain::RegisterArea::M, 0},
  64. domain::ContactMode::NormallyOpen};
  65. domain::LogicNode coil;
  66. coil.id = "run-coil";
  67. coil.config = domain::CoilNodeConfig{
  68. domain::RegisterAddress{domain::RegisterArea::M, 1},
  69. domain::CoilMode::Normal};
  70. domain::ControlLogic logic;
  71. logic.id = "start-logic";
  72. logic.name = "Start logic";
  73. logic.nodes = {contact, coil};
  74. logic.connections.push_back({contact.id, coil.id});
  75. domain::Project project;
  76. project.metadata = {"sample-project", "Sample project", "1.0"};
  77. project.hmiPages.push_back(page);
  78. project.controlLogics.push_back(logic);
  79. return project;
  80. }
  81. void testLogicNodeConfigurationBoundaries()
  82. {
  83. domain::LogicNode contact;
  84. contact.id = "contact";
  85. contact.config = domain::ContactNodeConfig{
  86. domain::RegisterAddress{domain::RegisterArea::M, 0},
  87. domain::ContactMode::NormallyOpen};
  88. require(contact.validate(), "contact node bound to M address must be valid");
  89. contact.config = domain::ContactNodeConfig{
  90. domain::RegisterAddress{domain::RegisterArea::D, 0},
  91. domain::ContactMode::NormallyOpen};
  92. require(!contact.validate(), "contact node bound to D address must be rejected");
  93. domain::LogicNode comparison;
  94. comparison.id = "comparison";
  95. comparison.config = domain::CompareNodeConfig{
  96. domain::RegisterAddress{domain::RegisterArea::D, 0},
  97. domain::ComparisonOperator::GreaterThan,
  98. static_cast<std::int16_t>(100)};
  99. require(comparison.validate(), "comparison node bound to D address must be valid");
  100. }
  101. void testModelsValidateBindingsAndIdentifiers()
  102. {
  103. domain::Project project = makeValidProject();
  104. require(project.validate(), "valid project model must pass validation");
  105. project.hmiPages.front().controls.front().binding =
  106. domain::RegisterAddress{domain::RegisterArea::D, 0};
  107. require(!project.validate(), "button bound to D area must be rejected");
  108. project = makeValidProject();
  109. project.hmiPages.push_back(project.hmiPages.front());
  110. require(!project.validate(), "duplicate HMI page id must be rejected");
  111. }
  112. void testRuntimeStateBoundaries()
  113. {
  114. domain::RuntimeState state;
  115. require(state.policy().allowsProjectEditing, "editing mode must allow project editing");
  116. require(state.enterOfflineRunning().succeeded, "editing may enter offline running");
  117. require(state.policy().usesVirtualRegisters, "offline mode must use virtual registers");
  118. require(state.policy().runsLogicExecutor, "offline mode must run logic executor");
  119. require(state.enterOnlineRunning(true).error
  120. == domain::ModeTransitionError::MustReturnToEditing,
  121. "offline mode must not directly enter online mode");
  122. require(state.enterEditing().succeeded, "offline mode may return to editing");
  123. require(state.enterOnlineRunning(false).error
  124. == domain::ModeTransitionError::InitialPlcReadRequired,
  125. "online mode must require an initial PLC read");
  126. require(state.enterOnlineRunning(true).succeeded,
  127. "editing may enter online mode after initial PLC read");
  128. require(!state.policy().runsLogicExecutor,
  129. "online mode must keep the software logic executor stopped");
  130. require(state.policy().usesPlcRegisters, "online mode must use PLC registers");
  131. }
  132. } // namespace
  133. int main()
  134. {
  135. try
  136. {
  137. testRegisterAddressBoundaries();
  138. testRegisterRepositorySeparatesAreas();
  139. testLogicNodeConfigurationBoundaries();
  140. testModelsValidateBindingsAndIdentifiers();
  141. testRuntimeStateBoundaries();
  142. }
  143. catch (const std::exception &error)
  144. {
  145. std::cerr << "domain tests failed: " << error.what() << '\n';
  146. return 1;
  147. }
  148. std::cout << "domain tests passed\n";
  149. return 0;
  150. }