综合平台编程器项目的远程存储
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

220 lines
7.5 KiB

  1. #include "domain/active_register_repository.h"
  2. #include "domain/project_storage.h"
  3. #include "infrastructure/plc_register_repository.h"
  4. #include "services/offline_simulation_service.h"
  5. #include "services/plc_communication_gateway.h"
  6. #include "services/project_service.h"
  7. #include "services/runtime_mode_service.h"
  8. #include <functional>
  9. #include <iostream>
  10. #include <stdexcept>
  11. #include <utility>
  12. namespace {
  13. class TestProjectStorage final : public ProjectStorage
  14. {
  15. public:
  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. class FakePlcGateway final : public PlcCommunicationGateway
  26. {
  27. public:
  28. PlcCommunicationResult connectDevice(
  29. const PlcSerialConfiguration &configuration) override
  30. {
  31. last_configuration = configuration;
  32. connection_state = PlcConnectionState::Connected;
  33. if (state_changed)
  34. {
  35. state_changed();
  36. }
  37. return {true, {}};
  38. }
  39. void disconnectDevice() override
  40. {
  41. connection_state = PlcConnectionState::Disconnected;
  42. initial_read = false;
  43. if (initial_read_changed)
  44. {
  45. initial_read_changed(false);
  46. }
  47. if (state_changed)
  48. {
  49. state_changed();
  50. }
  51. }
  52. void setPollAddresses(const std::vector<RegisterAddress> &addresses) override
  53. {
  54. poll_addresses = addresses;
  55. }
  56. PlcConnectionState state() const override { return connection_state; }
  57. bool initialReadCompleted() const override { return initial_read; }
  58. const std::string &lastError() const override { return last_error; }
  59. void setCallbacks(
  60. std::function<void()> state_callback,
  61. std::function<void(bool)> initial_callback,
  62. std::function<void()> cache_callback,
  63. std::function<void(const std::string &)> error_callback) override
  64. {
  65. state_changed = std::move(state_callback);
  66. initial_read_changed = std::move(initial_callback);
  67. cache_updated = std::move(cache_callback);
  68. error_reported = std::move(error_callback);
  69. }
  70. void completeInitialRead()
  71. {
  72. initial_read = true;
  73. if (initial_read_changed)
  74. {
  75. initial_read_changed(true);
  76. }
  77. }
  78. PlcConnectionState connection_state = PlcConnectionState::Disconnected;
  79. bool initial_read = false;
  80. std::string last_error;
  81. PlcSerialConfiguration last_configuration;
  82. std::vector<RegisterAddress> poll_addresses;
  83. std::function<void()> state_changed;
  84. std::function<void(bool)> initial_read_changed;
  85. std::function<void()> cache_updated;
  86. std::function<void(const std::string &)> error_reported;
  87. };
  88. void require(bool condition, const std::string &message)
  89. {
  90. if (!condition)
  91. {
  92. throw std::runtime_error(message);
  93. }
  94. }
  95. void testPlcCacheAndWriteForwarding()
  96. {
  97. PlcRegisterRepository repository;
  98. const RegisterAddress m0{RegisterArea::M, 0};
  99. const RegisterAddress d0{RegisterArea::D, 0};
  100. require(repository.readBit(m0).error == RegisterError::Unavailable,
  101. "uninitialized PLC bit cache must be unavailable");
  102. require(repository.readWord(d0).error == RegisterError::Unavailable,
  103. "uninitialized PLC word cache must be unavailable");
  104. repository.updateBit(0, true);
  105. repository.updateWord(0, -123);
  106. require(repository.readBit(m0).succeeded && repository.readBit(m0).value,
  107. "valid PLC bit cache must be readable");
  108. require(repository.readWord(d0).succeeded
  109. && repository.readWord(d0).value == -123,
  110. "valid PLC word cache must preserve signed values");
  111. RegisterAddress written_address{RegisterArea::M, 1};
  112. bool written_bit = false;
  113. std::int16_t written_word = 0;
  114. repository.setWriteHandlers(
  115. [&](const RegisterAddress &address, bool value)
  116. {
  117. written_address = address;
  118. written_bit = value;
  119. return RegisterWriteResult{true, RegisterError::None};
  120. },
  121. [&](const RegisterAddress &address, std::int16_t value)
  122. {
  123. written_address = address;
  124. written_word = value;
  125. return RegisterWriteResult{true, RegisterError::None};
  126. });
  127. require(repository.writeBit(m0, false).succeeded
  128. && written_address == m0 && !written_bit,
  129. "PLC bit writes must be forwarded without changing the cache");
  130. require(repository.writeWord(d0, 456).succeeded
  131. && written_address == d0 && written_word == 456,
  132. "PLC word writes must be forwarded without changing the cache");
  133. repository.invalidate();
  134. require(!repository.hasAnyValidValue(),
  135. "disconnecting must invalidate all PLC cache validity flags");
  136. }
  137. void testRuntimeRepositorySwitchingAndDisconnect()
  138. {
  139. TestProjectStorage storage;
  140. ProjectService project_service(storage);
  141. HmiPage page;
  142. page.id = "main-page";
  143. page.name = "Main";
  144. HmiControl indicator;
  145. indicator.id = "run-state";
  146. indicator.type = HmiControlType::Indicator;
  147. indicator.bounds = {0, 0, 80, 40};
  148. indicator.text = "Run";
  149. indicator.binding = RegisterAddress{RegisterArea::M, 5};
  150. page.controls.push_back(indicator);
  151. project_service.editProject().hmiPages.push_back(page);
  152. VirtualRegisterRepository virtual_repository;
  153. PlcRegisterRepository plc_repository;
  154. ActiveRegisterRepository active_repository(virtual_repository);
  155. OfflineSimulationService simulation_service(virtual_repository);
  156. FakePlcGateway gateway;
  157. RuntimeModeService service(project_service, simulation_service);
  158. service.configurePlc(
  159. gateway, active_repository, virtual_repository, plc_repository);
  160. const PlcCommunicationResult connected = service.connectPlc(
  161. {"COM9", 2, 19200, 8, 2, 1, 1000, 2, 200});
  162. require(connected.succeeded && gateway.poll_addresses.size() == 1U,
  163. "PLC connection must receive the project address poll set");
  164. require(service.enterOnlineRunning().error
  165. == ModeTransitionError::InitialPlcReadRequired,
  166. "online mode must wait for the first valid PLC read");
  167. plc_repository.updateBit(5, true);
  168. gateway.completeInitialRead();
  169. require(service.enterOnlineRunning().succeeded,
  170. "online mode must start after the first valid PLC read");
  171. require(active_repository.readBit({RegisterArea::M, 5}).succeeded
  172. && active_repository.readBit({RegisterArea::M, 5}).value,
  173. "online mode must expose the PLC cache through the active repository");
  174. gateway.disconnectDevice();
  175. require(service.mode() == ApplicationMode::Editing,
  176. "an online disconnect must return the application to editing mode");
  177. require(!service.initialPlcReadCompleted(),
  178. "an online disconnect must clear the initial read flag");
  179. require(active_repository.readBit({RegisterArea::M, 5}).succeeded
  180. && !active_repository.readBit({RegisterArea::M, 5}).value,
  181. "editing after disconnect must switch back to the virtual repository");
  182. }
  183. } // namespace
  184. int main()
  185. {
  186. try
  187. {
  188. testPlcCacheAndWriteForwarding();
  189. testRuntimeRepositorySwitchingAndDisconnect();
  190. }
  191. catch (const std::exception &error)
  192. {
  193. std::cerr << "PLC runtime tests failed: " << error.what() << '\n';
  194. return 1;
  195. }
  196. std::cout << "PLC runtime tests passed\n";
  197. return 0;
  198. }