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

391 lines
14 KiB

  1. #include "free_monitor_widget.h"
  2. #include "services/register_monitor_service.h"
  3. #include "toolbar_icon_factory.h"
  4. #include "ui_free_monitor_widget.h"
  5. #include <QComboBox>
  6. #include <QDoubleValidator>
  7. #include <QHeaderView>
  8. #include <QIntValidator>
  9. #include <QLineEdit>
  10. #include <QLocale>
  11. #include <QMessageBox>
  12. #include <QPushButton>
  13. #include <QTableWidgetItem>
  14. #include <cstdint>
  15. #include <cmath>
  16. #include <limits>
  17. #include <set>
  18. #include <type_traits>
  19. namespace {
  20. QString fromUtf8(const std::string &value)
  21. {
  22. return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
  23. }
  24. QString addressText(const RegisterAddress &address)
  25. {
  26. return QString::fromStdString(address.toString());
  27. }
  28. QString numericValueText(const RegisterNumericValue &value)
  29. {
  30. return std::visit(
  31. [](const auto &typed_value)
  32. {
  33. using Value = std::decay_t<decltype(typed_value)>;
  34. if constexpr (std::is_same_v<Value, float>)
  35. {
  36. return QString::number(
  37. static_cast<double>(typed_value), 'g',
  38. std::numeric_limits<float>::max_digits10);
  39. }
  40. else if constexpr (std::is_same_v<Value, double>)
  41. {
  42. return QString::number(
  43. typed_value, 'g', std::numeric_limits<double>::max_digits10);
  44. }
  45. else
  46. {
  47. return QString::number(static_cast<qlonglong>(typed_value));
  48. }
  49. },
  50. value);
  51. }
  52. } // namespace
  53. FreeMonitorWidget::FreeMonitorWidget(
  54. RegisterMonitorService &service,
  55. QWidget *parent)
  56. : QWidget(parent),
  57. ui_(std::make_unique<Ui::FreeMonitorWidget>()),
  58. service_(service)
  59. {
  60. ui_->setupUi(this);
  61. ui_->removeButton->setIcon(makeUiIcon(UiIcon::Delete));
  62. ui_->clearButton->setIcon(makeUiIcon(UiIcon::ClearList));
  63. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
  64. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
  65. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
  66. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
  67. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
  68. ui_->monitorTable->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
  69. ui_->dataTypeComboBox->setItemData(0, static_cast<int>(RegisterDataType::Int16));
  70. ui_->dataTypeComboBox->setItemData(1, static_cast<int>(RegisterDataType::Int32));
  71. ui_->dataTypeComboBox->setItemData(2, static_cast<int>(RegisterDataType::Float32));
  72. ui_->dataTypeComboBox->setItemData(3, static_cast<int>(RegisterDataType::Float64));
  73. ui_->monitorTable->verticalHeader()->setVisible(false);
  74. connect(ui_->addButton, &QPushButton::clicked, this, &FreeMonitorWidget::addAddresses);
  75. connect(ui_->addressEdit, &QLineEdit::returnPressed,
  76. this, &FreeMonitorWidget::addAddresses);
  77. connect(ui_->removeButton, &QToolButton::clicked,
  78. this, &FreeMonitorWidget::removeSelectedAddresses);
  79. connect(ui_->clearButton, &QToolButton::clicked,
  80. this, &FreeMonitorWidget::clearAddresses);
  81. reloadAddresses();
  82. }
  83. FreeMonitorWidget::~FreeMonitorWidget() = default;
  84. void FreeMonitorWidget::setWriteEnabled(bool enabled)
  85. {
  86. write_enabled_ = enabled;
  87. updateWriteControls();
  88. }
  89. void FreeMonitorWidget::refreshValues(
  90. ApplicationMode mode, PlcConnectionState plc_state)
  91. {
  92. QString source = tr("数据源:未启用");
  93. bool values_available = false;
  94. bool communication_fault = false;
  95. if (mode == ApplicationMode::Editing)
  96. {
  97. source = tr("数据源:离线初始值 · 虚拟 M/D");
  98. values_available = true;
  99. }
  100. else if (mode == ApplicationMode::OfflineRunning)
  101. {
  102. source = tr("数据源:虚拟 M/D");
  103. values_available = true;
  104. }
  105. else if (mode == ApplicationMode::OnlineRunning)
  106. {
  107. source = tr("数据源:PLC 缓存");
  108. values_available = true;
  109. communication_fault = plc_state == PlcConnectionState::Faulted;
  110. }
  111. ui_->sourceLabel->setText(source);
  112. const std::vector<MonitorValue> values = values_available
  113. ? service_.values(communication_fault) : std::vector<MonitorValue>{};
  114. for (int row = 0; row < ui_->monitorTable->rowCount(); ++row)
  115. {
  116. QTableWidgetItem *value_item = ui_->monitorTable->item(row, 2);
  117. QTableWidgetItem *state_item = ui_->monitorTable->item(row, 5);
  118. if (!values_available || static_cast<std::size_t>(row) >= values.size())
  119. {
  120. value_item->setText(QStringLiteral("--"));
  121. state_item->setText(tr("未运行"));
  122. continue;
  123. }
  124. const MonitorValue &value = values.at(static_cast<std::size_t>(row));
  125. if (value.state == MonitorValueState::Unavailable)
  126. {
  127. value_item->setText(QStringLiteral("--"));
  128. state_item->setText(mode == ApplicationMode::OnlineRunning
  129. ? tr("等待读取") : tr("不可用"));
  130. continue;
  131. }
  132. if (value.address.area() == RegisterArea::M)
  133. {
  134. value_item->setText(value.bitValue ? QStringLiteral("ON")
  135. : QStringLiteral("OFF"));
  136. }
  137. else
  138. {
  139. value_item->setText(numericValueText(value.numericValue));
  140. }
  141. state_item->setText(value.state == MonitorValueState::CommunicationFault
  142. ? tr("通信故障,最后有效值") : tr("有效"));
  143. }
  144. updateWriteControls();
  145. }
  146. void FreeMonitorWidget::reloadAddresses()
  147. {
  148. const std::vector<RegisterAddress> &addresses = service_.addresses();
  149. ui_->monitorTable->setRowCount(static_cast<int>(addresses.size()));
  150. for (std::size_t index = 0; index < addresses.size(); ++index)
  151. {
  152. const RegisterAddress &address = addresses[index];
  153. const int row = static_cast<int>(index);
  154. ui_->monitorTable->setItem(row, 0, new QTableWidgetItem(addressText(address)));
  155. const MonitorPoint &point = service_.points().at(index);
  156. QString type_text = tr("位");
  157. if (address.area() == RegisterArea::D)
  158. {
  159. const RegisterDataTypeDescriptor &descriptor =
  160. registerDataTypeDescriptor(point.dataType);
  161. type_text = QString::fromLatin1(descriptor.displayName);
  162. if (descriptor.wordCount > 1)
  163. {
  164. type_text += tr(" (%1~%2)").arg(
  165. addressText(address),
  166. addressText(RegisterAddress{
  167. RegisterArea::D,
  168. point.address.index() + descriptor.wordCount - 1}));
  169. }
  170. }
  171. ui_->monitorTable->setItem(row, 1, new QTableWidgetItem(type_text));
  172. ui_->monitorTable->setItem(row, 2, new QTableWidgetItem(QStringLiteral("--")));
  173. if (address.area() == RegisterArea::M)
  174. {
  175. auto *target = new QComboBox(ui_->monitorTable);
  176. target->addItem(tr("OFF"), false);
  177. target->addItem(tr("ON"), true);
  178. ui_->monitorTable->setCellWidget(row, 3, target);
  179. }
  180. else
  181. {
  182. auto *target = new QLineEdit(ui_->monitorTable);
  183. if (point.dataType == RegisterDataType::Int16)
  184. {
  185. target->setValidator(new QIntValidator(-32768, 32767, target));
  186. }
  187. else if (point.dataType == RegisterDataType::Int32)
  188. {
  189. target->setValidator(new QIntValidator(
  190. std::numeric_limits<int>::min(),
  191. std::numeric_limits<int>::max(), target));
  192. }
  193. else
  194. {
  195. const double maximum = point.dataType == RegisterDataType::Float32
  196. ? static_cast<double>(std::numeric_limits<float>::max())
  197. : std::numeric_limits<double>::max();
  198. const int digits = point.dataType == RegisterDataType::Float32
  199. ? std::numeric_limits<float>::max_digits10
  200. : std::numeric_limits<double>::max_digits10;
  201. auto *validator = new QDoubleValidator(-maximum, maximum, digits, target);
  202. validator->setNotation(QDoubleValidator::ScientificNotation);
  203. validator->setLocale(QLocale::c());
  204. target->setValidator(validator);
  205. target->setPlaceholderText(tr("小数或科学计数法"));
  206. }
  207. target->setText(QStringLiteral("0"));
  208. target->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
  209. ui_->monitorTable->setCellWidget(row, 3, target);
  210. }
  211. auto *write_button = new QPushButton(tr("写入"), ui_->monitorTable);
  212. write_button->setProperty("monitorRow", row);
  213. connect(write_button, &QPushButton::clicked, this,
  214. [this, write_button]
  215. {
  216. writeRow(write_button->property("monitorRow").toInt());
  217. });
  218. ui_->monitorTable->setCellWidget(row, 4, write_button);
  219. ui_->monitorTable->setItem(row, 5, new QTableWidgetItem(tr("未运行")));
  220. }
  221. updateWriteControls();
  222. }
  223. void FreeMonitorWidget::writeRow(int row)
  224. {
  225. if (!write_enabled_ || row < 0
  226. || static_cast<std::size_t>(row) >= service_.addresses().size())
  227. {
  228. return;
  229. }
  230. const MonitorPoint point = service_.points().at(static_cast<std::size_t>(row));
  231. const RegisterAddress address = point.address;
  232. RegisterMonitorWriteResult result;
  233. if (address.area() == RegisterArea::M)
  234. {
  235. auto *target = qobject_cast<QComboBox *>(ui_->monitorTable->cellWidget(row, 3));
  236. if (target == nullptr)
  237. {
  238. return;
  239. }
  240. result = service_.writeBit(address, target->currentData().toBool());
  241. }
  242. else
  243. {
  244. auto *target = qobject_cast<QLineEdit *>(ui_->monitorTable->cellWidget(row, 3));
  245. if (target == nullptr)
  246. {
  247. return;
  248. }
  249. bool converted = false;
  250. const double value = QLocale::c().toDouble(target->text(), &converted);
  251. if (!converted
  252. || !encodeRegisterNumericValue(point.dataType, value).has_value())
  253. {
  254. ui_->monitorTable->item(row, 5)->setText(tr("目标值无效"));
  255. emit operationMessage(
  256. tr("写入 D 区地址失败:请输入有效的 %1 数值")
  257. .arg(QString::fromLatin1(
  258. registerDataTypeDescriptor(point.dataType).displayName)));
  259. return;
  260. }
  261. result = service_.writeNumeric(address, point.dataType, value);
  262. }
  263. QTableWidgetItem *state_item = ui_->monitorTable->item(row, 5);
  264. if (result.succeeded)
  265. {
  266. state_item->setText(tr("写入请求已发送"));
  267. emit operationMessage(tr("%1 写入请求已发送,等待当前值刷新")
  268. .arg(addressText(address)));
  269. return;
  270. }
  271. state_item->setText(tr("写入失败"));
  272. handleResult(tr("写入 %1").arg(addressText(address)), false, result.message);
  273. }
  274. void FreeMonitorWidget::updateWriteControls()
  275. {
  276. for (int row = 0; row < ui_->monitorTable->rowCount(); ++row)
  277. {
  278. QWidget *target = ui_->monitorTable->cellWidget(row, 3);
  279. if (target != nullptr)
  280. {
  281. target->setEnabled(write_enabled_);
  282. }
  283. QWidget *write_button = ui_->monitorTable->cellWidget(row, 4);
  284. if (write_button != nullptr)
  285. {
  286. write_button->setEnabled(write_enabled_);
  287. }
  288. }
  289. }
  290. void FreeMonitorWidget::addAddresses()
  291. {
  292. const QByteArray address = ui_->addressEdit->text().toUtf8();
  293. const RegisterMonitorResult result = service_.addRange(
  294. std::string(address.constData(), static_cast<std::size_t>(address.size())),
  295. ui_->countSpinBox->value(),
  296. static_cast<RegisterDataType>(ui_->dataTypeComboBox->currentData().toInt()));
  297. if (result.succeeded)
  298. {
  299. reloadAddresses();
  300. ui_->addressEdit->selectAll();
  301. emit monitorAddressesChanged();
  302. const QString message = fromUtf8(result.message);
  303. emit operationMessage(
  304. message.isEmpty()
  305. ? tr("已添加 %1 个监控点").arg(result.affectedCount)
  306. : tr("已添加 %1 个监控点;%2")
  307. .arg(result.affectedCount)
  308. .arg(message));
  309. return;
  310. }
  311. handleResult(tr("添加监控地址"), false, result.message);
  312. }
  313. void FreeMonitorWidget::removeSelectedAddresses()
  314. {
  315. std::set<int> rows;
  316. for (const QModelIndex &index : ui_->monitorTable->selectionModel()->selectedRows())
  317. {
  318. rows.insert(index.row());
  319. }
  320. std::vector<MonitorPoint> points;
  321. const std::vector<MonitorPoint> &all_points = service_.points();
  322. for (int row : rows)
  323. {
  324. if (row >= 0 && static_cast<std::size_t>(row) < all_points.size())
  325. {
  326. points.push_back(all_points.at(static_cast<std::size_t>(row)));
  327. }
  328. }
  329. const RegisterMonitorResult result = service_.remove(points);
  330. if (result.succeeded)
  331. {
  332. reloadAddresses();
  333. emit monitorAddressesChanged();
  334. emit operationMessage(tr("已删除 %1 个监控地址").arg(result.affectedCount));
  335. return;
  336. }
  337. handleResult(tr("删除监控地址"), false, result.message);
  338. }
  339. void FreeMonitorWidget::clearAddresses()
  340. {
  341. if (service_.addresses().empty())
  342. {
  343. return;
  344. }
  345. if (QMessageBox::question(
  346. this, tr("清空自由监控"), tr("确定清空当前监控表吗?"))
  347. != QMessageBox::Yes)
  348. {
  349. return;
  350. }
  351. const RegisterMonitorResult result = service_.clear();
  352. if (result.succeeded)
  353. {
  354. reloadAddresses();
  355. emit monitorAddressesChanged();
  356. emit operationMessage(tr("自由监控已清空"));
  357. return;
  358. }
  359. handleResult(tr("清空自由监控"), false, result.message);
  360. }
  361. void FreeMonitorWidget::handleResult(
  362. const QString &action, bool succeeded, const std::string &message)
  363. {
  364. if (!succeeded)
  365. {
  366. emit operationMessage(action + QStringLiteral(":") + fromUtf8(message));
  367. }
  368. }