综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

192 строки
8.8 KiB

  1. #include "domain/active_register_repository.h"
  2. #include "domain/register_monitor_model.h"
  3. #include "domain/register_repository.h"
  4. #include "domain/virtual_register_repository.h"
  5. #include "services/register_monitor_service.h"
  6. #include "support/test_support.h"
  7. #include <iostream>
  8. #include <cmath>
  9. #include <limits>
  10. #include <stdexcept>
  11. #include <string>
  12. namespace {
  13. using TestSupport::require;
  14. void testRangeManagement()
  15. {
  16. VirtualRegisterRepository repository;
  17. RegisterMonitorService service(repository);
  18. int change_count = 0;
  19. service.setAddressesChangedCallback([&change_count] { ++change_count; });
  20. const RegisterMonitorResult added = service.addRange(" d10 ", 3);
  21. require(added.succeeded && added.affectedCount == 3
  22. && service.addresses().size() == 3U,
  23. "monitor service must expand a normalized continuous address range");
  24. require(service.addresses().front() == RegisterAddress{RegisterArea::D, 10}
  25. && service.addresses().back() == RegisterAddress{RegisterArea::D, 12},
  26. "monitor service must preserve expanded address order");
  27. const RegisterMonitorResult duplicate = service.addRange("D11", 2);
  28. require(!duplicate.succeeded && duplicate.error == RegisterMonitorError::NoChange,
  29. "monitor service must reject a fully duplicated range");
  30. require(!service.addRange("M4000", 2).succeeded,
  31. "monitor service must reject a range crossing address 4000");
  32. require(change_count == 1,
  33. "failed additions must not report a monitor address change");
  34. require(service.remove({RegisterAddress{RegisterArea::D, 11}}).succeeded
  35. && service.addresses().size() == 2U,
  36. "monitor service must remove selected addresses");
  37. require(service.clear().succeeded && service.addresses().empty(),
  38. "monitor service must clear the current monitor table");
  39. require(change_count == 3,
  40. "successful add, remove and clear operations must notify address changes");
  41. }
  42. void testSharedActiveRepositoryValues()
  43. {
  44. VirtualRegisterRepository virtual_repository;
  45. VirtualRegisterRepository plc_repository;
  46. ActiveRegisterRepository active_repository(virtual_repository);
  47. RegisterMonitorService service(active_repository);
  48. require(service.addRange("M0", 1).succeeded
  49. && service.addRange("D0", 1).succeeded,
  50. "monitor service must accept one M and one D address");
  51. virtual_repository.writeBit({RegisterArea::M, 0}, true);
  52. virtual_repository.writeWord({RegisterArea::D, 0}, -123);
  53. std::vector<MonitorValue> values = service.values(false);
  54. require(values.size() == 2U && values[0].bitValue && values[1].wordValue == -123,
  55. "monitor values must reflect the active virtual repository");
  56. plc_repository.writeBit({RegisterArea::M, 0}, false);
  57. plc_repository.writeWord({RegisterArea::D, 0}, 456);
  58. active_repository.use(plc_repository);
  59. values = service.values(false);
  60. require(!values[0].bitValue && values[1].wordValue == 456,
  61. "monitor values must follow the active repository switch");
  62. values = service.values(true);
  63. require(values[0].state == MonitorValueState::CommunicationFault
  64. && values[1].state == MonitorValueState::CommunicationFault,
  65. "valid cached values must be marked stale during a communication fault");
  66. }
  67. void testRegisterWrites()
  68. {
  69. VirtualRegisterRepository repository;
  70. RegisterMonitorService service(repository);
  71. const RegisterAddress m0{RegisterArea::M, 0};
  72. const RegisterAddress d0{RegisterArea::D, 0};
  73. const RegisterMonitorWriteResult bit_write = service.writeBit(m0, true);
  74. require(bit_write.succeeded && repository.readBit(m0).value,
  75. "monitor service must write M values through the active repository");
  76. const RegisterMonitorWriteResult word_write = service.writeWord(d0, -123);
  77. require(word_write.succeeded && repository.readWord(d0).value == -123,
  78. "monitor service must write signed D values through the active repository");
  79. const RegisterMonitorWriteResult wrong_bit = service.writeBit(d0, true);
  80. require(!wrong_bit.succeeded && wrong_bit.error == RegisterError::AreaMismatch,
  81. "monitor bit writes must reject D addresses");
  82. const RegisterMonitorWriteResult wrong_word = service.writeWord(m0, 123);
  83. require(!wrong_word.succeeded && wrong_word.error == RegisterError::AreaMismatch,
  84. "monitor word writes must reject M addresses");
  85. require(service.writeWord(d0, static_cast<std::int16_t>(-32768)).succeeded
  86. && repository.readWord(d0).value == -32768
  87. && service.writeWord(d0, static_cast<std::int16_t>(32767)).succeeded
  88. && repository.readWord(d0).value == 32767,
  89. "monitor word writes must preserve signed 16-bit boundary values");
  90. }
  91. void testOfflineInitialValueCapture()
  92. {
  93. VirtualRegisterRepository virtual_repository;
  94. VirtualRegisterRepository initial_repository;
  95. ActiveRegisterRepository active_repository(virtual_repository);
  96. RegisterMonitorService service(active_repository);
  97. service.setOfflineInitialRepository(initial_repository);
  98. service.setOfflineInitialCaptureEnabled(true);
  99. require(service.writeBit({RegisterArea::M, 3}, true).succeeded
  100. && service.writeWord({RegisterArea::D, 10}, -456).succeeded
  101. && service.writeFloat({RegisterArea::D, 20}, 1.5f).succeeded,
  102. "editing monitor writes must succeed against the virtual repository");
  103. require(initial_repository.readBit({RegisterArea::M, 3}).value
  104. && initial_repository.readWord({RegisterArea::D, 10}).value == -456
  105. && std::fabs(Float32Codec::decode(
  106. initial_repository.readWordPair({RegisterArea::D, 20}).values[0],
  107. initial_repository.readWordPair({RegisterArea::D, 20}).values[1])
  108. .value_or(0.0f) - 1.5f) < 0.000001f,
  109. "editing monitor writes must be captured as offline initial values");
  110. service.setOfflineInitialCaptureEnabled(false);
  111. require(service.writeBit({RegisterArea::M, 3}, false).succeeded
  112. && initial_repository.readBit({RegisterArea::M, 3}).value,
  113. "runtime writes must not overwrite the captured initial values");
  114. }
  115. void testFloat32Monitoring()
  116. {
  117. VirtualRegisterRepository repository;
  118. RegisterMonitorService service(repository);
  119. const RegisterMonitorResult added = service.addRange(
  120. "D1", 3, RegisterDataType::Float32);
  121. require(added.succeeded && added.affectedCount == 3
  122. && service.points().size() == 3U
  123. && service.points()[0].address == RegisterAddress{RegisterArea::D, 1}
  124. && service.points()[1].address == RegisterAddress{RegisterArea::D, 3}
  125. && service.points()[2].address == RegisterAddress{RegisterArea::D, 5},
  126. "Float32 monitor batches must advance by two D words");
  127. require(service.float32Starts().size() == 3U
  128. && service.pollAddresses().size() == 6U,
  129. "Float32 monitor points must expose both words for polling");
  130. const RegisterMonitorResult duplicate = service.addRange(
  131. "D1", 1, RegisterDataType::Float32);
  132. require(!duplicate.succeeded && duplicate.error == RegisterMonitorError::NoChange,
  133. "identical Float32 monitor points must be rejected");
  134. const RegisterMonitorResult overlap = service.addRange(
  135. "D2", 1, RegisterDataType::Int16);
  136. require(overlap.succeeded && !overlap.message.empty(),
  137. "partially overlapping monitor points must be added with a warning");
  138. require(service.remove(std::vector<MonitorPoint>{
  139. MonitorPoint{{RegisterArea::D, 2}, RegisterDataType::Int16}}).succeeded
  140. && service.points().size() == 3U,
  141. "overlapping monitor removal must identify the point type");
  142. require(service.writeFloat({RegisterArea::D, 1}, -2.5f).succeeded,
  143. "Float32 monitor writes must use the pair write path");
  144. const std::vector<MonitorValue> values = service.values(false);
  145. require(values.front().state == MonitorValueState::Valid
  146. && std::fabs(values.front().floatValue - (-2.5f)) < 0.000001f,
  147. "Float32 monitor values must decode the pair correctly");
  148. require(!service.writeFloat({RegisterArea::D, 4000}, 1.0f).succeeded,
  149. "Float32 monitor writes at D4000 must be rejected");
  150. }
  151. } // namespace
  152. int main()
  153. {
  154. try
  155. {
  156. testRangeManagement();
  157. testSharedActiveRepositoryValues();
  158. testRegisterWrites();
  159. testOfflineInitialValueCapture();
  160. testFloat32Monitoring();
  161. }
  162. catch (const std::exception &error)
  163. {
  164. std::cerr << "register monitor service tests failed: " << error.what() << '\n';
  165. return 1;
  166. }
  167. std::cout << "register monitor service tests passed\n";
  168. return 0;
  169. }