Typica is a free program for professional coffee roasters. https://typica.us
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.

modbus.w 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. @** Another Approach for Modbus RTU Support.
  2. \noindent The original code for dealing with Modbus RTU devices had a number of
  3. limitations. It was awkward to configure, limited to using a single device per
  4. bus, supported a small number of channels, and only worked with fixed point
  5. numeric representations. The following sections are an initial draft of a more
  6. flexible reworking of Modbus RTU support which should be easier to extend to
  7. cover Modbus TCP, allows a wider range of numeric representations, and allows
  8. for the use of any number of devices on the bus and any number of channels.
  9. Initial support is focused on function 3 and 4 registers with a known
  10. configuration. Outputs and the ability to configure inputs based on the values
  11. at other addresses will be added later. Modbus TCP support is also planned.
  12. Once feature parity with the old Modbus code is reached, the older code will be
  13. removed.
  14. Rather than use a single configuration widget for the entire bus, this is now
  15. split to use one configuration widget for the bus and another widget for an
  16. input channel.
  17. @<Class declarations@>=
  18. class ModbusNGConfWidget : public BasicDeviceConfigurationWidget
  19. {
  20. Q_OBJECT
  21. public:
  22. Q_INVOKABLE ModbusNGConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  23. private slots:
  24. void updatePort(const QString &value);
  25. void updateBaudRate(const QString &value);
  26. void updateParity(int value);
  27. void updateFlowControl(int value);
  28. void updateStopBits(int value);
  29. void addInput();
  30. private:
  31. ParitySelector *m_parity;
  32. FlowSelector *m_flow;
  33. StopSelector *m_stop;
  34. };
  35. @ The configuration widget for the bus only handles the details of the serial
  36. port and allows adding child nodes representing input channels.
  37. @<ModbusNG implementation@>=
  38. ModbusNGConfWidget::ModbusNGConfWidget(DeviceTreeModel *model, const QModelIndex &index) :
  39. BasicDeviceConfigurationWidget(model, index), m_parity(new ParitySelector),
  40. m_flow(new FlowSelector), m_stop(new StopSelector)
  41. {
  42. QFormLayout *layout = new QFormLayout;
  43. PortSelector *port = new PortSelector;
  44. BaudSelector *baud = new BaudSelector;
  45. QPushButton *newInput = new QPushButton(tr("Add Input Channel"));
  46. layout->addRow(QString(tr("Port:")), port);
  47. layout->addRow(QString(tr("Baud rate:")), baud);
  48. layout->addRow(QString(tr("Parity:")), m_parity);
  49. layout->addRow(QString(tr("Flow control:")), m_flow);
  50. layout->addRow(QString(tr("Stop bits:")), m_stop);
  51. layout->addRow(newInput);
  52. @<Get device configuration data for current node@>@;
  53. for(int i = 0; i < configData.size(); i++)
  54. {
  55. node = configData.at(i).toElement();
  56. if(node.attribute("name") == "port")
  57. {
  58. int idx = port->findText(node.attribute("value"));
  59. if(idx >= 0)
  60. {
  61. port->setCurrentIndex(idx);
  62. }
  63. else
  64. {
  65. port->addItem(node.attribute("value"));
  66. }
  67. }
  68. else if(node.attribute("name") == "baud")
  69. {
  70. baud->setCurrentIndex(baud->findText(node.attribute("value")));
  71. }
  72. else if(node.attribute("name") == "parity")
  73. {
  74. m_parity->setCurrentIndex(m_parity->findData(node.attribute("value")));
  75. }
  76. else if(node.attribute("name") == "flow")
  77. {
  78. m_flow->setCurrentIndex(m_flow->findData(node.attribute("value")));
  79. }
  80. else if(node.attribute("name") == "stop")
  81. {
  82. m_stop->setCurrentIndex(m_stop->findData(node.attribute("value")));
  83. }
  84. }
  85. updatePort(port->currentText());
  86. updateBaudRate(baud->currentText());
  87. updateParity(m_parity->currentIndex());
  88. updateFlowControl(m_flow->currentIndex());
  89. updateStopBits(m_stop->currentIndex());
  90. connect(port, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
  91. connect(port, SIGNAL(editTextChanged(QString)), this, SLOT(updatePort(QString)));
  92. connect(baud, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateBaudRate(QString)));
  93. connect(m_parity, SIGNAL(currentIndexChanged(int)), this, SLOT(updateParity(int)));
  94. connect(m_flow, SIGNAL(currentIndexChanged(int)),
  95. this, SLOT(updateFlowControl(int)));
  96. connect(m_stop, SIGNAL(currentIndexChanged(int)), this, SLOT(updateStopBits(int)));
  97. connect(newInput, SIGNAL(clicked()), this, SLOT(addInput()));
  98. setLayout(layout);
  99. }
  100. void ModbusNGConfWidget::updatePort(const QString &value)
  101. {
  102. updateAttribute("port", value);
  103. }
  104. void ModbusNGConfWidget::updateBaudRate(const QString &value)
  105. {
  106. updateAttribute("baud", value);
  107. }
  108. void ModbusNGConfWidget::updateParity(int value)
  109. {
  110. updateAttribute("parity", m_parity->itemData(value).toString());
  111. }
  112. void ModbusNGConfWidget::updateFlowControl(int value)
  113. {
  114. updateAttribute("flow", m_flow->itemData(value).toString());
  115. }
  116. void ModbusNGConfWidget::updateStopBits(int value)
  117. {
  118. updateAttribute("stop", m_stop->itemData(value).toString());
  119. }
  120. void ModbusNGConfWidget::addInput()
  121. {
  122. insertChildNode(tr("Input"), "modbusnginput");
  123. }
  124. @ Next, there is a configuration widget for input channels.
  125. @<Class declarations@>=
  126. class ModbusNGInputConfWidget : public BasicDeviceConfigurationWidget
  127. {
  128. Q_OBJECT
  129. public:
  130. Q_INVOKABLE ModbusNGInputConfWidget(DeviceTreeModel *model, const QModelIndex &index);
  131. private slots:
  132. void updateStation(int value);
  133. void updateAddress(int value);
  134. void updateFunction(int value);
  135. void updateFormat(int value);
  136. void updateDecimals(int value);
  137. void updateUnit(int value);
  138. void updateColumnName(const QString &value);
  139. void updateHidden(bool value);
  140. };
  141. @ This is where the function, address, and additional required details about
  142. an input channel are defind.
  143. @<ModbusNG implementation@>=
  144. ModbusNGInputConfWidget::ModbusNGInputConfWidget(DeviceTreeModel *model, const QModelIndex &index) : BasicDeviceConfigurationWidget(model, index)
  145. {
  146. QFormLayout *layout = new QFormLayout;
  147. QSpinBox *station = new QSpinBox;
  148. station->setMinimum(1);
  149. station->setMaximum(247);
  150. layout->addRow(tr("Station ID"), station);
  151. QComboBox *function = new QComboBox;
  152. function->addItem("3", "3");
  153. function->addItem("4", "4");
  154. function->setCurrentIndex(1);
  155. layout->addRow(tr("Function"), function);
  156. ShortHexSpinBox *address = new ShortHexSpinBox;
  157. layout->addRow(tr("Address"), address);
  158. QComboBox *format = new QComboBox;
  159. format->addItem(tr("16 bits fixed point"), "16fixedint");
  160. format->addItem(tr("32 bits floating point (High Low)"), "32floathl");
  161. format->addItem(tr("32 bits floating point (Low High)"), "32floatlh");
  162. layout->addRow(tr("Data format"), format);
  163. QSpinBox *decimals = new QSpinBox;
  164. decimals->setMinimum(0);
  165. decimals->setMaximum(9);
  166. layout->addRow(tr("Decimal places"), decimals);
  167. QComboBox *unit = new QComboBox;
  168. unit->addItem("Celsius", "C");
  169. unit->addItem("Fahrenheit", "F");
  170. unit->addItem("Control", "Control");
  171. unit->setCurrentIndex(1);
  172. layout->addRow(tr("Unit"), unit);
  173. QLineEdit *column = new QLineEdit;
  174. layout->addRow(tr("Column name"), column);
  175. QCheckBox *hidden = new QCheckBox(tr("Hide this channel"));
  176. layout->addRow(hidden);
  177. @<Get device configuration data for current node@>@;
  178. for(int i = 0; i < configData.size(); i++)
  179. {
  180. node = configData.at(i).toElement();
  181. if(node.attribute("name") == "station")
  182. {
  183. station->setValue(node.attribute("value").toInt());
  184. }
  185. else if(node.attribute("name") == "function")
  186. {
  187. function->setCurrentIndex(function->findText(node.attribute("value")));
  188. }
  189. else if(node.attribute("name") == "address")
  190. {
  191. address->setValue(node.attribute("value").toInt());
  192. }
  193. else if(node.attribute("name") == "format")
  194. {
  195. format->setCurrentIndex(format->findData(node.attribute("value")));
  196. }
  197. else if(node.attribute("name") == "decimals")
  198. {
  199. decimals->setValue(node.attribute("value").toInt());
  200. }
  201. else if(node.attribute("name") == "unit")
  202. {
  203. unit->setCurrentIndex(unit->findData(node.attribute("value")));
  204. }
  205. else if(node.attribute("name") == "column")
  206. {
  207. column->setText(node.attribute("value"));
  208. }
  209. else if(node.attribute("name") == "hidden")
  210. {
  211. hidden->setChecked(node.attribute("value") == "true" ? true : false);
  212. }
  213. }
  214. updateStation(station->value());
  215. updateFunction(function->currentIndex());
  216. updateAddress(address->value());
  217. updateFormat(format->currentIndex());
  218. updateDecimals(decimals->value());
  219. updateUnit(unit->currentIndex());
  220. updateColumnName(column->text());
  221. updateHidden(hidden->isChecked());
  222. connect(station, SIGNAL(valueChanged(int)), this, SLOT(updateStation(int)));
  223. connect(function, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFunction(int)));
  224. connect(address, SIGNAL(valueChanged(int)), this, SLOT(updateAddress(int)));
  225. connect(format, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFormat(int)));
  226. connect(decimals, SIGNAL(valueChanged(int)), this, SLOT(updateDecimals(int)));
  227. connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(updateUnit(int)));
  228. connect(column, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  229. connect(hidden, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  230. setLayout(layout);
  231. }
  232. void ModbusNGInputConfWidget::updateStation(int value)
  233. {
  234. updateAttribute("station", QString("%1").arg(value));
  235. }
  236. void ModbusNGInputConfWidget::updateFunction(int value)
  237. {
  238. updateAttribute("function", QString("%1").arg(value == 0 ? "3" : "4"));
  239. }
  240. void ModbusNGInputConfWidget::updateAddress(int value)
  241. {
  242. updateAttribute("address", QString("%1").arg(value));
  243. }
  244. void ModbusNGInputConfWidget::updateFormat(int value)
  245. {
  246. switch(value)
  247. {
  248. case 0:
  249. updateAttribute("format", "16fixedint");
  250. break;
  251. case 1:
  252. updateAttribute("format", "32floathl");
  253. break;
  254. case 2:
  255. updateAttribute("format", "32floatlh");
  256. break;
  257. }
  258. }
  259. void ModbusNGInputConfWidget::updateDecimals(int value)
  260. {
  261. updateAttribute("decimals", QString("%1").arg(value));
  262. }
  263. void ModbusNGInputConfWidget::updateUnit(int value)
  264. {
  265. switch(value)
  266. {
  267. case 0:
  268. updateAttribute("unit", "C");
  269. break;
  270. case 1:
  271. updateAttribute("unit", "F");
  272. break;
  273. case 2:
  274. updateAttribute("unit", "Control");
  275. break;
  276. }
  277. }
  278. void ModbusNGInputConfWidget::updateColumnName(const QString &value)
  279. {
  280. updateAttribute("column", value);
  281. }
  282. void ModbusNGInputConfWidget::updateHidden(bool value)
  283. {
  284. updateAttribute("hidden", value ? "true" : "false");
  285. }
  286. @ The configuration widgets need to be registered.
  287. @<Register device configuration widgets@>=
  288. app.registerDeviceConfigurationWidget("modbusngport", ModbusNGConfWidget::staticMetaObject);
  289. app.registerDeviceConfigurationWidget("modbusnginput",
  290. ModbusNGInputConfWidget::staticMetaObject);
  291. @ A |NodeInserter| is also needed.
  292. @<Register top level device configuration nodes@>=
  293. inserter = new NodeInserter(tr("ModbusNG Port"), tr("Modbus RTU Port"), "modbusngport", NULL);
  294. topLevelNodeInserters.append(inserter);
  295. @ While the old design only needed to deal with a small number of potential
  296. messages and responses, it makes sense for the new design to use a scan list
  297. of arbitrary length. An initial implementation can simply store the data needed
  298. to make requests, properly interpret the results, and output data to the
  299. correct channels. There is room to improve operational efficiency later by
  300. batching operations on adjacent addresses on the same function into a single
  301. request, but there are known devices in use in coffee roasters which do not
  302. support reading from multiple registers simultaneously, so there must also be a
  303. way to turn such optimizations off.
  304. @<Class declarations@>=
  305. enum ModbusDataFormat
  306. {
  307. Int16,
  308. FloatHL,
  309. FloatLH
  310. };
  311. struct ModbusScanItem
  312. {
  313. QByteArray request;
  314. ModbusDataFormat format;
  315. int decimalPosition;
  316. Units::Unit unit;
  317. mutable double lastValue;
  318. };
  319. @ Another class is used to handle the communication with the bus and serve as
  320. an integration point for \pn{}.
  321. @<Class declarations@>=
  322. class ModbusNG : public QObject
  323. {
  324. Q_OBJECT
  325. public:
  326. ModbusNG(DeviceTreeModel *model, const QModelIndex &index);
  327. ~ModbusNG();
  328. Q_INVOKABLE int channelCount();
  329. Channel* getChannel(int);
  330. Q_INVOKABLE QString channelColumnName(int);
  331. Q_INVOKABLE QString channelIndicatorText(int);
  332. Q_INVOKABLE bool isChannelHidden(int);
  333. Q_INVOKABLE QString channelType(int);
  334. private slots:
  335. void sendNextMessage();
  336. void timeout();
  337. void dataAvailable();
  338. void rateLimitTimeout();
  339. private:
  340. quint16 calculateCRC(QByteArray data);
  341. QextSerialPort *port;
  342. int delayTime;
  343. QTimer *messageDelayTimer;
  344. QTimer *commTimeout;
  345. QTimer *rateLimiter;
  346. int scanPosition;
  347. bool waiting;
  348. QByteArray responseBuffer;
  349. QList<Channel*> channels;
  350. QList<ModbusScanItem> scanList;
  351. QList<QString> channelNames;
  352. QList<QString> channelLabels;
  353. QList<bool> hiddenStates;
  354. QList<QString> channelTypeList;
  355. QVector<double> lastMeasurement;
  356. };
  357. @ One of the things that the old Modbus code got right was in allowing the
  358. constructor to handle device configuration by accepting its configuration
  359. sub-tree. In this design, child nodes establish a scan list.
  360. @<ModbusNG implementation@>=
  361. ModbusNG::ModbusNG(DeviceTreeModel *model, const QModelIndex &index) :
  362. QObject(NULL), messageDelayTimer(new QTimer), commTimeout(new QTimer),
  363. rateLimiter(new QTimer), scanPosition(0), waiting(false)
  364. {
  365. QDomElement portReferenceElement =
  366. model->referenceElement(model->data(index, Qt::UserRole).toString());
  367. QDomNodeList portConfigData = portReferenceElement.elementsByTagName("attribute");
  368. QDomElement node;
  369. QVariantMap attributes;
  370. for(int i = 0; i < portConfigData.size(); i++)
  371. {
  372. node = portConfigData.at(i).toElement();
  373. attributes.insert(node.attribute("name"), node.attribute("value"));
  374. }
  375. port = new QextSerialPort(attributes.value("port").toString(),
  376. QextSerialPort::EventDriven);
  377. port->setBaudRate((BaudRateType)(attributes.value("baud").toInt()));
  378. port->setDataBits(DATA_8);
  379. port->setParity((ParityType)attributes.value("parity").toInt());
  380. port->setStopBits((StopBitsType)attributes.value("stop").toInt());
  381. port->setFlowControl((FlowType)attributes.value("flow").toInt());
  382. delayTime = (int)(((double)(1)/(double)(attributes.value("baud").toInt())) * 144000.0);
  383. messageDelayTimer->setSingleShot(true);
  384. commTimeout->setSingleShot(true);
  385. rateLimiter->setSingleShot(true);
  386. rateLimiter->setInterval(0);
  387. connect(messageDelayTimer, SIGNAL(timeout()), this, SLOT(sendNextMessage()));
  388. connect(commTimeout, SIGNAL(timeout()), this, SLOT(timeout()));
  389. connect(port, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
  390. connect(rateLimiter, SIGNAL(timeout()), this, SLOT(rateLimitTimeout()));
  391. if(!port->open(QIODevice::ReadWrite))
  392. {
  393. qDebug() << "Failed to open serial port";
  394. }
  395. for(int i = 0; i < model->rowCount(index); i++)
  396. {
  397. QModelIndex channelIndex = model->index(i, 0, index);
  398. QDomElement channelReferenceElement =
  399. model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
  400. QDomNodeList channelConfigData =
  401. channelReferenceElement.elementsByTagName("attribute");
  402. QDomElement channelNode;
  403. QVariantMap channelAttributes;
  404. for(int j = 0; j < channelConfigData.size(); j++)
  405. {
  406. channelNode = channelConfigData.at(j).toElement();
  407. channelAttributes.insert(channelNode.attribute("name"),
  408. channelNode.attribute("value"));
  409. }
  410. ModbusScanItem scanItem;
  411. QString format = channelAttributes.value("format").toString();
  412. if(format == "16fixedint")
  413. {
  414. scanItem.format = Int16;
  415. }
  416. else if(format == "32floathl")
  417. {
  418. scanItem.format = FloatHL;
  419. }
  420. else if(format == "32floatlh")
  421. {
  422. scanItem.format = FloatLH;
  423. }
  424. scanItem.request.append((char)channelAttributes.value("station").toInt());
  425. scanItem.request.append((char)channelAttributes.value("function").toInt());
  426. quint16 startAddress = (quint16)channelAttributes.value("address").toInt();
  427. char *startAddressBytes = (char*)&startAddress;
  428. scanItem.request.append(startAddressBytes[1]);
  429. scanItem.request.append(startAddressBytes[0]);
  430. scanItem.request.append((char)0x00);
  431. if(scanItem.format == Int16)
  432. {
  433. scanItem.request.append((char)0x01);
  434. }
  435. else
  436. {
  437. scanItem.request.append((char)0x02);
  438. }
  439. quint16 crc = calculateCRC(scanItem.request);
  440. char *crcBytes = (char*)&crc;
  441. scanItem.request.append(crcBytes[0]);
  442. scanItem.request.append(crcBytes[1]);
  443. scanItem.decimalPosition = channelAttributes.value("decimals").toInt();
  444. if(channelAttributes.value("unit").toString() == "C")
  445. {
  446. scanItem.unit = Units::Celsius;
  447. channelTypeList.append("T");
  448. }
  449. else if(channelAttributes.value("unit").toString() == "F")
  450. {
  451. scanItem.unit = Units::Fahrenheit;
  452. channelTypeList.append("T");
  453. }
  454. else
  455. {
  456. scanItem.unit = Units::Unitless;
  457. channelTypeList.append("C");
  458. }
  459. scanList.append(scanItem);
  460. lastMeasurement.append(0.0);
  461. channels.append(new Channel);
  462. channelNames.append(channelAttributes.value("column").toString());
  463. hiddenStates.append(
  464. channelAttributes.value("hidden").toString() == "true" ? true : false);
  465. channelLabels.append(model->data(channelIndex, 0).toString());
  466. }
  467. messageDelayTimer->start();
  468. }
  469. ModbusNG::~ModbusNG()
  470. {
  471. commTimeout->stop();
  472. messageDelayTimer->stop();
  473. port->close();
  474. }
  475. void ModbusNG::sendNextMessage()
  476. {
  477. if(scanList.length() > 0 && !waiting)
  478. {
  479. port->write(scanList.at(scanPosition).request);
  480. commTimeout->start(2000);
  481. messageDelayTimer->start(delayTime);
  482. waiting = true;
  483. }
  484. }
  485. void ModbusNG::timeout()
  486. {
  487. qDebug() << "Communications timeout.";
  488. messageDelayTimer->start();
  489. }
  490. void ModbusNG::rateLimitTimeout()
  491. {
  492. messageDelayTimer->start();
  493. }
  494. void ModbusNG::dataAvailable()
  495. {
  496. if(messageDelayTimer->isActive())
  497. {
  498. messageDelayTimer->stop();
  499. }
  500. responseBuffer.append(port->readAll());
  501. if(responseBuffer.size() < 5)
  502. {
  503. return;
  504. }
  505. if(responseBuffer.size() < 5 + responseBuffer.at(2))
  506. {
  507. return;
  508. }
  509. responseBuffer = responseBuffer.left(5 + responseBuffer.at(2));
  510. commTimeout->stop();
  511. if(calculateCRC(responseBuffer) == 0)
  512. {
  513. quint16 intresponse;
  514. float floatresponse;
  515. char *ibytes = (char*)&intresponse;
  516. char *fbytes = (char*)&floatresponse;
  517. double output = 0.0;
  518. switch(scanList.at(scanPosition).format)
  519. {
  520. case Int16:
  521. ibytes[0] = responseBuffer.at(4);
  522. ibytes[1] = responseBuffer.at(3);
  523. output = intresponse;
  524. for(int i = 0; i < scanList.at(scanPosition).decimalPosition; i++)
  525. {
  526. output /= 10;
  527. }
  528. break;
  529. case FloatHL:
  530. fbytes[0] = responseBuffer.at(4);
  531. fbytes[1] = responseBuffer.at(3);
  532. fbytes[2] = responseBuffer.at(6);
  533. fbytes[3] = responseBuffer.at(5);
  534. output = floatresponse;
  535. break;
  536. case FloatLH:
  537. fbytes[0] = responseBuffer.at(6);
  538. fbytes[1] = responseBuffer.at(5);
  539. fbytes[2] = responseBuffer.at(4);
  540. fbytes[3] = responseBuffer.at(3);
  541. output = floatresponse;
  542. break;
  543. }
  544. if(scanList.at(scanPosition).unit == Units::Celsius)
  545. {
  546. output = output * 9.0 / 5.0 + 32.0;
  547. }
  548. scanList.at(scanPosition).lastValue = output;
  549. }
  550. else
  551. {
  552. qDebug() << "CRC failed";
  553. }
  554. scanPosition = (scanPosition + 1) % scanList.size();
  555. if(scanPosition == 0)
  556. {
  557. QTime time = QTime::currentTime();
  558. bool doOutput = false;
  559. for(int i = 0; i < scanList.size(); i++)
  560. {
  561. if(scanList.at(i).lastValue != lastMeasurement.at(i))
  562. {
  563. doOutput = true;
  564. break;
  565. }
  566. }
  567. if(doOutput)
  568. {
  569. for(int i = 0; i < scanList.size(); i++)
  570. {
  571. lastMeasurement[i] = scanList.at(i).lastValue;
  572. if(scanList.at(scanPosition).unit == Units::Unitless)
  573. {
  574. channels.at(i)->input(Measurement(scanList.at(i).lastValue, time, Units::Unitless));
  575. }
  576. else
  577. {
  578. channels.at(i)->input(Measurement(scanList.at(i).lastValue, time, Units::Fahrenheit));
  579. }
  580. }
  581. }
  582. }
  583. responseBuffer.clear();
  584. waiting = false;
  585. if(scanPosition == 0)
  586. {
  587. rateLimiter->start();
  588. }
  589. else
  590. {
  591. messageDelayTimer->start(delayTime);
  592. }
  593. }
  594. quint16 ModbusNG::calculateCRC(QByteArray data)
  595. {
  596. quint16 retval = 0xFFFF;
  597. int i = 0;
  598. while(i < data.size())
  599. {
  600. retval ^= 0x00FF & (quint16)data.at(i);
  601. for(int j = 0; j < 8; j++)
  602. {
  603. if(retval & 1)
  604. {
  605. retval = (retval >> 1) ^ 0xA001;
  606. }
  607. else
  608. {
  609. retval >>= 1;
  610. }
  611. }
  612. i++;
  613. }
  614. return retval;
  615. }
  616. int ModbusNG::channelCount()
  617. {
  618. return channels.size();
  619. }
  620. Channel* ModbusNG::getChannel(int channel)
  621. {
  622. return channels.at(channel);
  623. }
  624. QString ModbusNG::channelColumnName(int channel)
  625. {
  626. return channelNames.at(channel);
  627. }
  628. QString ModbusNG::channelIndicatorText(int channel)
  629. {
  630. return channelLabels.at(channel);
  631. }
  632. bool ModbusNG::isChannelHidden(int channel)
  633. {
  634. return hiddenStates.at(channel);
  635. }
  636. QString ModbusNG::channelType(int channel)
  637. {
  638. return channelTypeList.at(channel);
  639. }
  640. @ This class must be exposed to the host environment.
  641. @<Function prototypes for scripting@>=
  642. QScriptValue constructModbusNG(QScriptContext *context, QScriptEngine *engine);
  643. void setModbusNGProperties(QScriptValue value, QScriptEngine *engine);
  644. QScriptValue ModbusNG_getChannel(QScriptContext *context, QScriptEngine *engine);
  645. @ The host environment is informed of the constructor.
  646. @<Set up the scripting engine@>=
  647. constructor = engine->newFunction(constructModbusNG);
  648. value = engine->newQMetaObject(&ModbusNG::staticMetaObject, constructor);
  649. engine->globalObject().setProperty("ModbusNG", value);
  650. @ The constructor takes the configuration model and the index to the device as
  651. arguments.
  652. @<Functions for scripting@>=
  653. QScriptValue constructModbusNG(QScriptContext *context, QScriptEngine *engine)
  654. {
  655. QScriptValue object;
  656. if(context->argumentCount() == 2)
  657. {
  658. object = engine->newQObject(new ModbusNG(argument<DeviceTreeModel *>(0, context),
  659. argument<QModelIndex>(1, context)),
  660. QScriptEngine::ScriptOwnership);
  661. setModbusNGProperties(object, engine);
  662. }
  663. else
  664. {
  665. context->throwError("Incorrect number of arguments passed to "@|
  666. "ModbusNG constructor.");
  667. }
  668. return object;
  669. }
  670. void setModbusNGProperties(QScriptValue value, QScriptEngine *engine)
  671. {
  672. setQObjectProperties(value, engine);
  673. value.setProperty("getChannel", engine->newFunction(ModbusNG_getChannel));
  674. }
  675. QScriptValue ModbusNG_getChannel(QScriptContext *context, QScriptEngine *engine)
  676. {
  677. ModbusNG *self = getself<ModbusNG *>(context);
  678. QScriptValue object;
  679. if(self)
  680. {
  681. object = engine->newQObject(self->getChannel(argument<int>(0, context)));
  682. setChannelProperties(object, engine);
  683. }
  684. return object;
  685. }