综合平台编程器项目的远程存储
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.
 
 
 
 

419 lines
13 KiB

  1. #include "plc_communication_service.h"
  2. #include "plc_register_repository.h"
  3. #include <QModbusDataUnit>
  4. #include <QModbusDevice>
  5. #include <QModbusReply>
  6. #include <QModbusRtuSerialMaster>
  7. #include <QSerialPort>
  8. #include <QVariant>
  9. #include <algorithm>
  10. #include <utility>
  11. namespace {
  12. constexpr int kMaximumReadCount = 120;
  13. std::string toUtf8(const QString &value)
  14. {
  15. const QByteArray bytes = value.toUtf8();
  16. return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
  17. }
  18. QModbusDataUnit::RegisterType registerType(RegisterArea area)
  19. {
  20. return area == RegisterArea::M
  21. ? QModbusDataUnit::Coils : QModbusDataUnit::HoldingRegisters;
  22. }
  23. QString modbusErrorText(QModbusDevice::Error error)
  24. {
  25. switch (error)
  26. {
  27. case QModbusDevice::ReadError:
  28. return QStringLiteral("读取 PLC 数据失败");
  29. case QModbusDevice::WriteError:
  30. return QStringLiteral("写入 PLC 数据失败");
  31. case QModbusDevice::ConnectionError:
  32. return QStringLiteral("PLC 串口连接失败");
  33. case QModbusDevice::ConfigurationError:
  34. return QStringLiteral("PLC 串口参数配置错误");
  35. case QModbusDevice::TimeoutError:
  36. return QStringLiteral("PLC 通信超时,请检查站号、串口参数和 RS-485 接线");
  37. case QModbusDevice::ProtocolError:
  38. return QStringLiteral("PLC 返回了无效或异常的 Modbus 响应");
  39. case QModbusDevice::ReplyAbortedError:
  40. return QStringLiteral("PLC 通信请求已取消");
  41. case QModbusDevice::UnknownError:
  42. return QStringLiteral("PLC 通信发生未知错误");
  43. case QModbusDevice::NoError:
  44. default:
  45. return QStringLiteral("PLC 通信失败");
  46. }
  47. }
  48. } // namespace
  49. PlcCommunicationService::PlcCommunicationService(
  50. PlcRegisterRepository &repository,
  51. QObject *parent)
  52. : QObject(parent),
  53. repository_(repository),
  54. master_(std::make_unique<QModbusRtuSerialMaster>())
  55. {
  56. repository_.setWriteHandlers(
  57. [this](const RegisterAddress &address, bool value)
  58. {
  59. return sendBitWrite(address, value);
  60. },
  61. [this](const RegisterAddress &address, std::int16_t value)
  62. {
  63. return sendWordWrite(address, value);
  64. });
  65. connect(&poll_timer_, &QTimer::timeout, this, &PlcCommunicationService::pollNextBlock);
  66. connect(master_.get(), &QModbusClient::stateChanged,
  67. this,
  68. [this](QModbusDevice::State device_state)
  69. {
  70. if (device_state == QModbusDevice::ConnectedState)
  71. {
  72. setState(PlcConnectionState::Connected);
  73. poll_timer_.start(configuration_.pollIntervalMs);
  74. pollNextBlock();
  75. }
  76. else if (device_state == QModbusDevice::ConnectingState)
  77. {
  78. setState(PlcConnectionState::Connecting);
  79. }
  80. else if (device_state == QModbusDevice::UnconnectedState
  81. && state_ != PlcConnectionState::Faulted)
  82. {
  83. setState(PlcConnectionState::Disconnected);
  84. }
  85. });
  86. connect(master_.get(), &QModbusClient::errorOccurred,
  87. this,
  88. [this](QModbusDevice::Error error)
  89. {
  90. if (error != QModbusDevice::NoError)
  91. {
  92. setError(modbusErrorText(error));
  93. }
  94. });
  95. }
  96. PlcCommunicationService::~PlcCommunicationService() = default;
  97. PlcCommunicationResult PlcCommunicationService::connectDevice(
  98. const PlcSerialConfiguration &configuration)
  99. {
  100. if (QString::fromStdString(configuration.portName).trimmed().isEmpty()
  101. || configuration.serverAddress < 1 || configuration.serverAddress > 247)
  102. {
  103. return {false, "必须填写串口端口,并将 PLC 站号设置为 1~247"};
  104. }
  105. if (master_->state() != QModbusDevice::UnconnectedState)
  106. {
  107. return {false, "PLC 连接已经启动,请先断开当前连接"};
  108. }
  109. configuration_ = configuration;
  110. last_error_.clear();
  111. master_->setConnectionParameter(
  112. QModbusDevice::SerialPortNameParameter,
  113. QString::fromStdString(configuration.portName));
  114. master_->setConnectionParameter(
  115. QModbusDevice::SerialBaudRateParameter, configuration.baudRate);
  116. master_->setConnectionParameter(
  117. QModbusDevice::SerialDataBitsParameter,
  118. static_cast<QSerialPort::DataBits>(configuration.dataBits));
  119. master_->setConnectionParameter(
  120. QModbusDevice::SerialParityParameter,
  121. static_cast<QSerialPort::Parity>(configuration.parity));
  122. master_->setConnectionParameter(
  123. QModbusDevice::SerialStopBitsParameter,
  124. static_cast<QSerialPort::StopBits>(configuration.stopBits));
  125. master_->setTimeout(configuration.responseTimeoutMs);
  126. master_->setNumberOfRetries(configuration.retries);
  127. repository_.invalidate();
  128. initial_read_completed_ = false;
  129. emit initialReadCompletedChanged(false);
  130. if (initial_read_changed_callback_)
  131. {
  132. initial_read_changed_callback_(false);
  133. }
  134. rebuildPollBlocks();
  135. setState(PlcConnectionState::Connecting);
  136. if (!master_->connectDevice())
  137. {
  138. setError(modbusErrorText(master_->error()));
  139. return {false, last_error_};
  140. }
  141. return {true, {}};
  142. }
  143. void PlcCommunicationService::disconnectDevice()
  144. {
  145. poll_timer_.stop();
  146. master_->disconnectDevice();
  147. repository_.invalidate();
  148. initial_read_completed_ = false;
  149. emit initialReadCompletedChanged(false);
  150. if (initial_read_changed_callback_)
  151. {
  152. initial_read_changed_callback_(false);
  153. }
  154. setState(PlcConnectionState::Disconnected);
  155. }
  156. void PlcCommunicationService::setPollAddresses(
  157. const std::vector<RegisterAddress> &addresses)
  158. {
  159. poll_addresses_ = addresses;
  160. rebuildPollBlocks();
  161. }
  162. PlcConnectionState PlcCommunicationService::state() const
  163. {
  164. return state_;
  165. }
  166. bool PlcCommunicationService::initialReadCompleted() const
  167. {
  168. return initial_read_completed_;
  169. }
  170. const std::string &PlcCommunicationService::lastError() const
  171. {
  172. return last_error_;
  173. }
  174. const PlcSerialConfiguration &PlcCommunicationService::configuration() const
  175. {
  176. return configuration_;
  177. }
  178. void PlcCommunicationService::setCallbacks(
  179. std::function<void()> state_changed,
  180. std::function<void(bool)> initial_read_changed,
  181. std::function<void()> cache_updated,
  182. std::function<void(const std::string &)> error_reported)
  183. {
  184. state_changed_callback_ = std::move(state_changed);
  185. initial_read_changed_callback_ = std::move(initial_read_changed);
  186. cache_updated_callback_ = std::move(cache_updated);
  187. error_reported_callback_ = std::move(error_reported);
  188. }
  189. void PlcCommunicationService::rebuildPollBlocks()
  190. {
  191. std::vector<RegisterAddress> addresses = poll_addresses_;
  192. if (addresses.empty())
  193. {
  194. addresses = {
  195. RegisterAddress{RegisterArea::M, 0},
  196. RegisterAddress{RegisterArea::D, 0}};
  197. }
  198. std::sort(
  199. addresses.begin(), addresses.end(),
  200. [](const RegisterAddress &left, const RegisterAddress &right)
  201. {
  202. if (left.area() != right.area())
  203. {
  204. return left.area() == RegisterArea::M;
  205. }
  206. return left.index() < right.index();
  207. });
  208. addresses.erase(std::unique(addresses.begin(), addresses.end()), addresses.end());
  209. poll_blocks_.clear();
  210. for (const RegisterAddress &address : addresses)
  211. {
  212. if (!address.isValid())
  213. {
  214. continue;
  215. }
  216. if (poll_blocks_.empty()
  217. || poll_blocks_.back().area != address.area()
  218. || address.index() > poll_blocks_.back().startAddress
  219. + poll_blocks_.back().count
  220. || poll_blocks_.back().count >= kMaximumReadCount)
  221. {
  222. poll_blocks_.push_back({address.area(), address.index(), 1});
  223. }
  224. else
  225. {
  226. poll_blocks_.back().count = address.index()
  227. - poll_blocks_.back().startAddress + 1;
  228. }
  229. }
  230. next_poll_block_ = 0;
  231. initial_blocks_read_.assign(poll_blocks_.size(), false);
  232. initial_read_completed_ = false;
  233. }
  234. void PlcCommunicationService::pollNextBlock()
  235. {
  236. if (state_ != PlcConnectionState::Connected
  237. || pending_reply_ != nullptr || poll_blocks_.empty())
  238. {
  239. return;
  240. }
  241. const std::size_t block_index = next_poll_block_;
  242. const PollBlock block = poll_blocks_.at(block_index);
  243. next_poll_block_ = (next_poll_block_ + 1U) % poll_blocks_.size();
  244. QModbusDataUnit request(
  245. registerType(block.area), block.startAddress, static_cast<quint16>(block.count));
  246. QModbusReply *reply = master_->sendReadRequest(request, configuration_.serverAddress);
  247. if (reply == nullptr)
  248. {
  249. setError(modbusErrorText(master_->error()));
  250. return;
  251. }
  252. pending_reply_ = reply;
  253. connect(reply, &QModbusReply::finished,
  254. this,
  255. [this, reply, block, block_index]
  256. {
  257. handleReadFinished(reply, block);
  258. if (state_ == PlcConnectionState::Connected
  259. && reply->error() == QModbusDevice::NoError
  260. && block_index < initial_blocks_read_.size())
  261. {
  262. initial_blocks_read_[block_index] = true;
  263. const bool completed = std::all_of(
  264. initial_blocks_read_.cbegin(),
  265. initial_blocks_read_.cend(),
  266. [](bool read) { return read; });
  267. if (completed && !initial_read_completed_)
  268. {
  269. initial_read_completed_ = true;
  270. emit initialReadCompletedChanged(true);
  271. if (initial_read_changed_callback_)
  272. {
  273. initial_read_changed_callback_(true);
  274. }
  275. }
  276. }
  277. if (pending_reply_ == reply)
  278. {
  279. pending_reply_ = nullptr;
  280. }
  281. reply->deleteLater();
  282. });
  283. }
  284. void PlcCommunicationService::handleReadFinished(QModbusReply *reply, PollBlock block)
  285. {
  286. if (state_ != PlcConnectionState::Connected)
  287. {
  288. return;
  289. }
  290. if (reply->error() != QModbusDevice::NoError)
  291. {
  292. setError(modbusErrorText(reply->error()));
  293. return;
  294. }
  295. const QModbusDataUnit result = reply->result();
  296. for (uint index = 0; index < result.valueCount(); ++index)
  297. {
  298. const int address = block.startAddress + static_cast<int>(index);
  299. if (block.area == RegisterArea::M)
  300. {
  301. repository_.updateBit(address, result.value(index) != 0U);
  302. }
  303. else
  304. {
  305. repository_.updateWord(address, static_cast<std::int16_t>(result.value(index)));
  306. }
  307. }
  308. last_error_.clear();
  309. emit cacheUpdated();
  310. if (cache_updated_callback_)
  311. {
  312. cache_updated_callback_();
  313. }
  314. }
  315. RegisterWriteResult PlcCommunicationService::sendBitWrite(
  316. const RegisterAddress &address, bool value)
  317. {
  318. if (state_ != PlcConnectionState::Connected)
  319. {
  320. return {false, RegisterError::Unavailable};
  321. }
  322. QModbusDataUnit unit(QModbusDataUnit::Coils, address.index(), 1);
  323. unit.setValue(0, value ? 1U : 0U);
  324. QModbusReply *reply = master_->sendWriteRequest(unit, configuration_.serverAddress);
  325. if (reply == nullptr)
  326. {
  327. setError(modbusErrorText(master_->error()));
  328. return {false, RegisterError::WriteRejected};
  329. }
  330. connect(reply, &QModbusReply::finished,
  331. this,
  332. [this, reply]
  333. {
  334. if (reply->error() != QModbusDevice::NoError)
  335. {
  336. setError(modbusErrorText(reply->error()));
  337. }
  338. reply->deleteLater();
  339. });
  340. return {true, RegisterError::None};
  341. }
  342. RegisterWriteResult PlcCommunicationService::sendWordWrite(
  343. const RegisterAddress &address, std::int16_t value)
  344. {
  345. if (state_ != PlcConnectionState::Connected)
  346. {
  347. return {false, RegisterError::Unavailable};
  348. }
  349. QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, address.index(), 1);
  350. unit.setValue(0, static_cast<quint16>(value));
  351. QModbusReply *reply = master_->sendWriteRequest(unit, configuration_.serverAddress);
  352. if (reply == nullptr)
  353. {
  354. setError(modbusErrorText(master_->error()));
  355. return {false, RegisterError::WriteRejected};
  356. }
  357. connect(reply, &QModbusReply::finished,
  358. this,
  359. [this, reply]
  360. {
  361. if (reply->error() != QModbusDevice::NoError)
  362. {
  363. setError(modbusErrorText(reply->error()));
  364. }
  365. reply->deleteLater();
  366. });
  367. return {true, RegisterError::None};
  368. }
  369. void PlcCommunicationService::setState(PlcConnectionState state)
  370. {
  371. if (state_ == state)
  372. {
  373. return;
  374. }
  375. state_ = state;
  376. emit stateChanged();
  377. if (state_changed_callback_)
  378. {
  379. state_changed_callback_();
  380. }
  381. }
  382. void PlcCommunicationService::setError(const QString &message)
  383. {
  384. last_error_ = toUtf8(message);
  385. poll_timer_.stop();
  386. setState(PlcConnectionState::Faulted);
  387. emit communicationError(message);
  388. if (error_reported_callback_)
  389. {
  390. error_reported_callback_(last_error_);
  391. }
  392. }