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.

unsupportedserial.w 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. @** Script Driven Devices.
  2. \noindent There are many data acquisition products that are reasonable to use
  3. with \pn which are not natively supported due to lack of available hardware
  4. for testing, lack of time or money to develop that support, or lack of
  5. documentation. It has also become relatively simple for hardware tinkerers to
  6. develop new devices matching this description as well. Vendors in this space
  7. tend to give inadequate consideration to interoperability and with some devices
  8. the communications protocol used changes significantly between firmware
  9. revisions. There are simply far too many devices like this to support
  10. everything. At the same time there are people with these devices who are
  11. capable of programming the basic communications handling but have difficulty
  12. with integrating that with \pn. By providing an in-program environment that
  13. handles much of the boilerplate and allowing people to write scripts
  14. implementing these protocols without the need to modify core \pn code or
  15. recompile the program, these people may find it easier to make their existing
  16. hardware work. Such scripts can also serve as prototypes for native support.
  17. Configuration widgets for these devices allow key value pairs to be specified
  18. both at the device level and on a per-channel basis. This is intentionally kept
  19. generic as it is impossible to know what configurable details may be required.
  20. Common configurations will have a device node representing a single logical
  21. device, usually a single physical device but this is not in any way enforced,
  22. and one child node per channel. These details are made available to the device
  23. script and are used to integrate with the logging view.
  24. Some of the naming conventions used here are legacy of the initial conception
  25. of this feature and should be changed before release if there is time to do so.
  26. While initial support will be focused on devices that present as a serial port,
  27. there is no reason this could not be extended to cover devices that are
  28. interfaced through USB HID, Bluetooth, COM, output piped from an external
  29. console program, devices interfaced through arbitrary libraries, or any other
  30. class of device not directly supported in the core code should there be an
  31. interest in any of these.
  32. @<Class declarations@>=
  33. class UnsupportedSerialDeviceConfWidget : public BasicDeviceConfigurationWidget
  34. {
  35. Q_OBJECT
  36. public:
  37. Q_INVOKABLE UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
  38. const QModelIndex &index);
  39. private slots:
  40. void updateConfiguration();
  41. void saveScript();
  42. void addChannel();
  43. private:
  44. SaltModel *deviceSettingsModel;
  45. QTextEdit *scriptEditor;
  46. };
  47. @ The device configuration widget consists of two tabs. One tab provides a
  48. button for adding channels and an area for entering device specific
  49. configuration details. The other provides an area for entering the device
  50. script. This may be extended later to provide better editing, testing, and
  51. debugging support, but the initial concern is simply having a working feature.
  52. @<UnsupportedSerialDeviceConfWidget implementation@>=
  53. UnsupportedSerialDeviceConfWidget::UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
  54. const QModelIndex &index)
  55. : BasicDeviceConfigurationWidget(model, index),
  56. deviceSettingsModel(new SaltModel(2)),
  57. scriptEditor(new QTextEdit)
  58. {
  59. scriptEditor->setTabStopWidth(20);
  60. QVBoxLayout *dummyLayout = new QVBoxLayout;
  61. QTabWidget *central = new QTabWidget;
  62. QWidget *deviceConfigurationWidget = new QWidget;
  63. QVBoxLayout *deviceConfigurationLayout = new QVBoxLayout;
  64. QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
  65. deviceConfigurationLayout->addWidget(addChannelButton);
  66. connect(addChannelButton, SIGNAL(clicked()), this, SLOT(addChannel()));
  67. QLabel *deviceSettingsLabel = new QLabel(tr("Device Settings:"));
  68. deviceConfigurationLayout->addWidget(deviceSettingsLabel);
  69. QTableView *deviceSettingsView = new QTableView;
  70. deviceSettingsModel->setHeaderData(0, Qt::Horizontal, tr("Key"));
  71. deviceSettingsModel->setHeaderData(1, Qt::Horizontal, tr("Value"));
  72. deviceSettingsView->setModel(deviceSettingsModel);
  73. deviceConfigurationLayout->addWidget(deviceSettingsView);
  74. deviceConfigurationWidget->setLayout(deviceConfigurationLayout);
  75. central->addTab(deviceConfigurationWidget, tr("Configuration"));
  76. central->addTab(scriptEditor, tr("Script"));
  77. dummyLayout->addWidget(central);
  78. @<Get device configuration data for current node@>@;
  79. for(int i = 0; i < configData.size(); i++)
  80. {
  81. node = configData.at(i).toElement();
  82. if(node.attribute("name") == "keys" || node.attribute("name") == "values")
  83. {
  84. int column = 0;
  85. if(node.attribute("name") == "values")
  86. {
  87. column = 1;
  88. }
  89. QString data = node.attribute("value");
  90. if(data.length() > 3)
  91. {
  92. data.chop(2);
  93. data = data.remove(0, 2);
  94. }
  95. QStringList keyList = data.split(", ");
  96. for(int j = 0; j < keyList.size(); j++)
  97. {
  98. deviceSettingsModel->setData(deviceSettingsModel->index(j, column),
  99. QVariant(keyList.at(j)),
  100. Qt::EditRole);
  101. }
  102. }
  103. else if(node.attribute("name") == "script")
  104. {
  105. scriptEditor->setPlainText(node.attribute("value"));
  106. }
  107. }
  108. connect(deviceSettingsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
  109. this, SLOT(updateConfiguration()));
  110. connect(scriptEditor, SIGNAL(textChanged()), this, SLOT(saveScript()));
  111. setLayout(dummyLayout);
  112. }
  113. @ Device configuration data is entered through an ordinary |QTableView| with a
  114. |SaltModel| backing. The original use case for that model does not apply here,
  115. but that model does ensure that additional blank rows are added as needed so
  116. that arbitrarily many key value pairs can be entered. When data changes in the
  117. model we write the full content of the model out. Note that commas may not be
  118. used in keys or values. For keys in which lists make sense, a different
  119. delimiter must be chosen.
  120. @<UnsupportedSerialDeviceConfWidget implementation@>=
  121. void UnsupportedSerialDeviceConfWidget::updateConfiguration()
  122. {
  123. updateAttribute("keys", deviceSettingsModel->arrayLiteral(0, Qt::DisplayRole));
  124. updateAttribute("values", deviceSettingsModel->arrayLiteral(1, Qt::DisplayRole));
  125. }
  126. @ Every time the script text is changed, the new version of the script is
  127. saved. My expectation is that scripts will either be small or that they will be
  128. pasted in from outside of \pn so that this decision will not cause usability
  129. issues, however if I am wrong about this there may be a need to handle this
  130. more intelligently.
  131. @<UnsupportedSerialDeviceConfWidget implementation@>=
  132. void UnsupportedSerialDeviceConfWidget::saveScript()
  133. {
  134. updateAttribute("script", scriptEditor->toPlainText());
  135. }
  136. @ Typica requires channel nodes to simplify integration with other existing
  137. device code. Providing a new node type allows arbitrary attributes to be
  138. configured on a per-channel basis without resorting to strange conventions in
  139. the device configuration.
  140. @<UnsupportedSerialDeviceConfWidget implementation@>=
  141. void UnsupportedSerialDeviceConfWidget::addChannel()
  142. {
  143. insertChildNode(tr("Channel"), "unsupporteddevicechannel");
  144. }
  145. @ Channel configuration for unsupported devices is like unsupported device
  146. configuration in that arbitrary key value pairs may be entered for use by the
  147. device script. Conventions common to all other channel node types are also
  148. present here.
  149. @<Class declarations@>=
  150. class UnsupportedDeviceChannelConfWidget : public BasicDeviceConfigurationWidget
  151. {
  152. Q_OBJECT
  153. public:
  154. Q_INVOKABLE UnsupportedDeviceChannelConfWidget(DeviceTreeModel *model,
  155. const QModelIndex &index);
  156. private slots:
  157. void updateColumnName(const QString &value);
  158. void updateHidden(bool hidden);
  159. void updateConfiguration();
  160. private:
  161. SaltModel *channelSettingsModel;
  162. };
  163. @ The constructor is typical for for channel node configuraion widgets.
  164. @<UnsupportedSerialDeviceConfWidget implementation@>=
  165. UnsupportedDeviceChannelConfWidget::UnsupportedDeviceChannelConfWidget(DeviceTreeModel *model,
  166. const QModelIndex &index)
  167. : BasicDeviceConfigurationWidget(model, index),
  168. channelSettingsModel(new SaltModel(2))
  169. {
  170. QFormLayout *layout = new QFormLayout;
  171. QLineEdit *columnName = new QLineEdit;
  172. layout->addRow(tr("Column Name:"), columnName);
  173. QCheckBox *hideSeries = new QCheckBox("Hide this channel");
  174. layout->addRow(hideSeries);
  175. QTableView *channelSettings = new QTableView;
  176. channelSettingsModel->setHeaderData(0, Qt::Horizontal, "Key");
  177. channelSettingsModel->setHeaderData(1, Qt::Horizontal, "Value");
  178. channelSettings->setModel(channelSettingsModel);
  179. layout->addRow(channelSettings);
  180. setLayout(layout);
  181. @<Get device configuration data for current node@>@;
  182. for(int i = 0; i < configData.size(); i++)
  183. {
  184. node = configData.at(i).toElement();
  185. if(node.attribute("name") == "columnname")
  186. {
  187. columnName->setText(node.attribute("value"));
  188. }
  189. else if(node.attribute("name") == "hidden")
  190. {
  191. hideSeries->setChecked(node.attribute("value") == "true");
  192. }
  193. else if(node.attribute("name") == "keys" || node.attribute("name") == "values")
  194. {
  195. int column = 0;
  196. if(node.attribute("name") == "values")
  197. {
  198. column = 1;
  199. }
  200. QString data = node.attribute("value");
  201. if(data.length() > 3)
  202. {
  203. data.chop(2);
  204. data = data.remove(0, 2);
  205. }
  206. QStringList keyList = data.split(", ");
  207. for(int j = 0; j < keyList.size(); j++)
  208. {
  209. channelSettingsModel->setData(channelSettingsModel->index(j, column),
  210. QVariant(keyList.at(j)),
  211. Qt::EditRole);
  212. }
  213. }
  214. }
  215. updateColumnName(columnName->text());
  216. updateHidden(hideSeries->isChecked());
  217. updateConfiguration();
  218. connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  219. connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  220. connect(channelSettingsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
  221. this, SLOT(updateConfiguration()));
  222. }
  223. @ Arbitrary channel configuration data is handled in the same way as device
  224. level settings while the column name and hidden status is handled in the same
  225. way as they are in other channel nodes.
  226. @<UnsupportedSerialDeviceConfWidget implementation@>=
  227. void UnsupportedDeviceChannelConfWidget::updateColumnName(const QString &value)
  228. {
  229. updateAttribute("columnname", value);
  230. }
  231. void UnsupportedDeviceChannelConfWidget::updateHidden(bool hidden)
  232. {
  233. updateAttribute("hidden", hidden ? "true" : "false");
  234. }
  235. void UnsupportedDeviceChannelConfWidget::updateConfiguration()
  236. {
  237. updateAttribute("keys", channelSettingsModel->arrayLiteral(0, Qt::DisplayRole));
  238. updateAttribute("values", channelSettingsModel->arrayLiteral(1, Qt::DisplayRole));
  239. }
  240. @ The configuration widgets need to be registered so they can be instantiated
  241. as appropriate.
  242. @<Register device configuration widgets@>=
  243. app.registerDeviceConfigurationWidget("unsupporteddevicechannel",
  244. UnsupportedDeviceChannelConfWidget::staticMetaObject);
  245. app.registerDeviceConfigurationWidget("unsupporteddevice",
  246. UnsupportedSerialDeviceConfWidget::staticMetaObject);
  247. @ A |NodeInserter| for the device node is also provided.
  248. @<Register top level device configuration nodes@>=
  249. inserter = new NodeInserter(tr("Other Device"), tr("Other Device"),
  250. "unsupporteddevice", NULL);
  251. topLevelNodeInserters.append(inserter);
  252. @ A device abstraction is not strictly required for this feature, however
  253. having one greatly simplifies integrating this feature. At some point I would
  254. like to revise other device abstraction classes so that a huge amount of
  255. boilerplate associated with these can be removed from configuration documents.
  256. This device abstraction includes features in a few particular categories. First
  257. there are methods that are required for integrating the device with the logging
  258. view. The logging view instantiates the device abstraction, passing in the
  259. configuration data required to properly set up the device. It then is able to
  260. query information about the measurement channels that have been configured for
  261. this device and can set up all of the relevant indicators. Some device classes
  262. may be able to produce annotations, so this class can be treated exactly the
  263. same as any other annotation source. Another requested feature includes the
  264. ability of a device to trigger the start and end of batches, so signals are
  265. provided for this capability. Finally, there are methods associated with
  266. starting and stopping the device. The |start()| method will be called when the
  267. logging view has finished making all of the signal connections. The |stop()|
  268. method will be called when the logging view is closed, giving script code the
  269. chance to cleanly release any resources that must be held for device
  270. communications.
  271. @<Class declarations@>=
  272. class JavaScriptDevice : public QObject
  273. {
  274. Q_OBJECT
  275. public:
  276. Q_INVOKABLE JavaScriptDevice(const QModelIndex &deviceIndex,
  277. QScriptEngine *engine);
  278. Q_INVOKABLE int channelCount();
  279. Channel* getChannel(int channel);
  280. Q_INVOKABLE bool isChannelHidden(int channel);
  281. Q_INVOKABLE Units::Unit expectedChannelUnit(int channel);
  282. Q_INVOKABLE QString channelColumnName(int channel);
  283. Q_INVOKABLE QString channelIndicatorText(int channel);
  284. public slots:
  285. void setTemperatureColumn(int tcol);
  286. void setAnnotationColumn(int ncol);
  287. void start();
  288. void stop();
  289. signals:
  290. void annotation(QString note, int tcol, int ncol);
  291. void triggerStartBatch();
  292. void triggerStopBatch();
  293. void deviceStopRequested();
  294. private:
  295. QVariantMap deviceSettings;
  296. QString deviceScript;
  297. QList<Channel *> channelList;
  298. QList<bool> hiddenState;
  299. QList<Units::Unit> channelUnits;
  300. QList<QString> columnNames;
  301. QList<QString> indicatorTexts;
  302. QList<QVariantMap> channelSettings;
  303. int annotationTemperatureColumn;
  304. int annotationNoteColumn;
  305. QScriptEngine *scriptengine;
  306. };
  307. @ The |JavaScriptDevice| instance provides two interfaces. Its invokable
  308. methods provide the information needed to integrate script driven devices with
  309. a generic logging view. Additional information is also exposed through the host
  310. environment running the device script. This means that the class requires
  311. knowledge of the host environment, which it obtains through a script function
  312. similar to what is done for window creation.
  313. The name of the function is generic so this may be easily extended later to
  314. create all device abstraction instances.
  315. @<Function prototypes for scripting@>=
  316. QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine);
  317. @ That method is made available to the scripting engine.
  318. @<Set up the scripting engine@>=
  319. engine->globalObject().setProperty("createDevice",
  320. engine->newFunction(createDevice));
  321. @ This function currently creates a |JavaScriptDevice| from a device
  322. configuration node which must be passed through as an argument.
  323. @<Functions for scripting@>=
  324. QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine)@/
  325. {
  326. QModelIndex deviceIndex = argument<QModelIndex>(0, context);
  327. JavaScriptDevice *device = new JavaScriptDevice(deviceIndex, engine);
  328. QScriptValue object = engine->newQObject(device);
  329. setQObjectProperties(object, engine);
  330. object.setProperty("getChannel", engine->newFunction(JavaScriptDevice_getChannel));
  331. return object;
  332. }
  333. @ The |start()| method is responsible for preparing the host environment and
  334. executing the device script.
  335. @<JavaScriptDevice implementation@>=
  336. void JavaScriptDevice::start()
  337. {
  338. QScriptValue object = scriptengine->newQObject(this);
  339. @<Expose device settings as object property@>@;
  340. @<Expose channels and channel settings to device script@>@;
  341. QScriptContext *context = scriptengine->currentContext();
  342. QScriptValue oldThis = context->thisObject();
  343. context->setThisObject(object);
  344. QScriptValue result = scriptengine->evaluate(deviceScript);
  345. QScriptEngine *engine = scriptengine;
  346. @<Report scripting errors@>@;
  347. context->setThisObject(oldThis);
  348. }
  349. @ Device settings are only needed from the device script itself. As such, these
  350. are presented under a settings property available from the |this| object when
  351. the script is run.
  352. @<Expose device settings as object property@>=
  353. QScriptValue settingsObject = scriptengine->newObject();
  354. QVariantMap::const_iterator i = deviceSettings.constBegin();
  355. while(i != deviceSettings.constEnd())
  356. {
  357. settingsObject.setProperty(i.key(), i.value().toString());
  358. i++;
  359. }
  360. object.setProperty("settings", settingsObject);
  361. @ While channels are available to the device script through the same
  362. |getChannel()| interface used outside of the device script for integration
  363. purposes, it is more convenient to have an array of channels with channel
  364. specific settings as properties of the channel.
  365. @<Expose channels and channel settings to device script@>=
  366. QScriptValue channelsArray = scriptengine->newArray(channelCount());
  367. for(int i = 0; i < channelCount(); i++)
  368. {
  369. QScriptValue channelObject = scriptengine->newQObject(getChannel(i));
  370. QScriptValue channelSettingsObject = scriptengine->newObject();
  371. QVariantMap::const_iterator j = channelSettings.at(i).constBegin();
  372. while(j != channelSettings.at(i).constEnd())
  373. {
  374. channelSettingsObject.setProperty(j.key(), j.value().toString());
  375. j++;
  376. }
  377. channelObject.setProperty("settings", channelSettingsObject);
  378. channelsArray.setProperty(i, channelObject);
  379. }
  380. object.setProperty("channels", channelsArray);
  381. @ Currently we require wrapper functions to work with channels in the host
  382. environment.
  383. @<Function prototypes for scripting@>=
  384. QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine);
  385. @ The implementation is trivial.
  386. @<Functions for scripting@>=
  387. QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine)
  388. {
  389. JavaScriptDevice *self = getself<JavaScriptDevice *>(context);
  390. QScriptValue object;
  391. if(self)
  392. {
  393. object = engine->newQObject(self->getChannel(argument<int>(0, context)));
  394. setChannelProperties(object, engine);
  395. }
  396. return object;
  397. }
  398. @ The |stop()| method just fires off a signal that the script can hook into for
  399. any required cleanup.
  400. @<JavaScriptDevice implementation@>=
  401. void JavaScriptDevice::stop()
  402. {
  403. emit deviceStopRequested();
  404. }
  405. @ The constructor is responsible for all boilerplate initialization required
  406. for integrating script defined devices with the logging view.
  407. Note: At present expected units are assumed to be Fahrenheit. The configuration
  408. widget must be updated to allow at least for control measurements and
  409. eventually support for runtime defined units should also be added.
  410. @<JavaScriptDevice implementation@>=
  411. JavaScriptDevice::JavaScriptDevice(const QModelIndex &index,
  412. QScriptEngine *engine) :
  413. QObject(NULL), scriptengine(engine)
  414. {
  415. DeviceTreeModel *model = (DeviceTreeModel *)(index.model());
  416. QDomElement deviceReferenceElement =
  417. model->referenceElement(model->data(index, Qt::UserRole).toString());
  418. QDomNodeList deviceConfigData = deviceReferenceElement.elementsByTagName("attribute");
  419. QDomElement node;
  420. QStringList deviceKeys;
  421. QStringList deviceValues;
  422. for(int i = 0; i < deviceConfigData.size(); i++)
  423. {
  424. node = deviceConfigData.at(i).toElement();
  425. if(node.attribute("name") == "keys")
  426. {
  427. QString data = node.attribute("value");
  428. if(data.length() > 3)
  429. {
  430. data.chop(2);
  431. data = data.remove(0, 2);
  432. }
  433. deviceKeys = data.split(", ");
  434. }
  435. else if(node.attribute("name") == "values")
  436. {
  437. QString data = node.attribute("value");
  438. if(data.length() > 3)
  439. {
  440. data.chop(2);
  441. data = data.remove(0, 2);
  442. }
  443. deviceValues = data.split(", ");
  444. }
  445. else if(node.attribute("name") == "script")
  446. {
  447. deviceScript = node.attribute("value");
  448. }
  449. deviceSettings.insert(node.attribute("name"), node.attribute("value"));
  450. }
  451. for(int i = 0; i < qMin(deviceKeys.length(), deviceValues.length()); i++)
  452. {
  453. deviceSettings.insert(deviceKeys[i], deviceValues[i]);
  454. }
  455. if(model->hasChildren(index))
  456. {
  457. for(int i = 0; i < model->rowCount(index); i++)
  458. {
  459. QModelIndex channelIndex = model->index(i, 0, index);
  460. QDomElement channelReference = model->referenceElement(model->data(channelIndex, 32).toString());
  461. channelList.append(new Channel);
  462. QDomElement channelReferenceElement =
  463. model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
  464. QDomNodeList channelConfigData =
  465. channelReferenceElement.elementsByTagName("attribute");
  466. QStringList channelKeys;
  467. QStringList channelValues;
  468. for(int j = 0; j < channelConfigData.size(); j++)
  469. {
  470. node = channelConfigData.at(j).toElement();
  471. if(node.attribute("name") == "keys")
  472. {
  473. QString data = node.attribute("value");
  474. if(data.length() > 3)
  475. {
  476. data.chop(2);
  477. data = data.remove(0, 2);
  478. }
  479. channelKeys = data.split(", ");
  480. }
  481. else if(node.attribute("name") == "values")
  482. {
  483. QString data = node.attribute("value");
  484. if(data.length() > 3)
  485. {
  486. data.chop(2);
  487. data = data.remove(0, 2);
  488. }
  489. channelValues = data.split(", ");
  490. }
  491. else if(node.attribute("name") == "hidden")
  492. {
  493. hiddenState.append(node.attribute("value") == "true");
  494. }
  495. else if(node.attribute("name") == "columnname")
  496. {
  497. columnNames.append(node.attribute("value"));
  498. }
  499. }
  500. QVariantMap cs;
  501. for(int j = 0; j < qMin(channelKeys.length(), channelValues.length()); j++)
  502. {
  503. cs.insert(channelKeys[j], channelValues[j]);
  504. }
  505. channelSettings.append(cs);
  506. indicatorTexts.append(model->data(channelIndex, Qt::DisplayRole).toString());
  507. channelUnits.append(Units::Fahrenheit);
  508. }
  509. }
  510. }
  511. @ Several methods are available to query information about the configured
  512. channels.
  513. @<JavaScriptDevice implementation@>=
  514. int JavaScriptDevice::channelCount()
  515. {
  516. return channelList.length();
  517. }
  518. Channel* JavaScriptDevice::getChannel(int channel)
  519. {
  520. return channelList.at(channel);
  521. }
  522. bool JavaScriptDevice::isChannelHidden(int channel)
  523. {
  524. return hiddenState.at(channel);
  525. }
  526. Units::Unit JavaScriptDevice::expectedChannelUnit(int channel)
  527. {
  528. return channelUnits.at(channel);
  529. }
  530. QString JavaScriptDevice::channelColumnName(int channel)
  531. {
  532. if(channel >= 0 && channel < columnNames.length())
  533. {
  534. return columnNames.at(channel);
  535. }
  536. return QString();
  537. }
  538. QString JavaScriptDevice::channelIndicatorText(int channel)
  539. {
  540. return indicatorTexts.at(channel);
  541. }
  542. @ Two slots are provided for controlling the placement of annotations.
  543. @<JavaScriptDevice implementation@>=
  544. void JavaScriptDevice::setTemperatureColumn(int tcol)
  545. {
  546. annotationTemperatureColumn = tcol;
  547. }
  548. void JavaScriptDevice::setAnnotationColumn(int ncol)
  549. {
  550. annotationNoteColumn = ncol;
  551. }
  552. @ Device scripts must be able to produce measurements on a channel. To do this,
  553. a function is provided for obtaining a timestamp. The returned timestamp should
  554. not be examined as future changes may break assumptions about the content of
  555. the timestamp.
  556. @<Function prototypes for scripting@>=
  557. QScriptValue getMeasurementTimestamp(QScriptContext *context, QScriptEngine *engine);
  558. @ That method is made available to the scripting engine.
  559. @<Set up the scripting engine@>=
  560. engine->globalObject().setProperty("getMeasurementTimestamp",
  561. engine->newFunction(getMeasurementTimestamp));
  562. @ At present this simply obtains the current system time. It is planned to
  563. switch to a better quality clock in the future, but this should be done for
  564. everything that uses |Measurement| objects at once.
  565. @<Functions for scripting@>=
  566. QScriptValue getMeasurementTimestamp(QScriptContext *, QScriptEngine *engine)@/
  567. {
  568. return engine->toScriptValue<QTime>(QTime::currentTime());
  569. }
  570. @ At present, implementations are not broken out to a separate file. This
  571. should be changed at some point.
  572. @<Class implementations@>=
  573. @<UnsupportedSerialDeviceConfWidget implementation@>
  574. @<JavaScriptDevice implementation@>
  575. @* Serial Ports.
  576. \noindent The first use case for script driven devices was connecting to
  577. devices which present themselves as a serial port. This covers a broad range
  578. of data acquisition products. To provide this support, |QextSerialPort|, which
  579. was already used to support some other hardware options, is directly exposed to
  580. the host environment.
  581. @<Function prototypes for scripting@>=
  582. QScriptValue constructSerialPort(QScriptContext *context, QScriptEngine *engine);
  583. void setSerialPortProperties(QScriptValue value, QScriptEngine *engine);
  584. QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *engine);
  585. @ Our constructor is passed to the scripting engine.
  586. @<Set up the scripting engine@>=
  587. constructor = engine->newFunction(constructSerialPort);
  588. value = engine->newQMetaObject(&QextSerialPort::staticMetaObject, constructor);
  589. engine->globalObject().setProperty("SerialPort", value);
  590. @ At present we only support event driven communications and are not passing
  591. any port settings through the constructor. Such functionality may be added in
  592. the future, but it is not strictly necessary.
  593. @<Functions for scripting@>=
  594. QScriptValue constructSerialPort(QScriptContext *, QScriptEngine *engine)
  595. {
  596. QScriptValue object = engine->newQObject(new QextSerialPort());
  597. setSerialPortProperties(object, engine);
  598. return object;
  599. }
  600. @ Some properties of |QIODevice| are brought in as usual for similar subclasses
  601. but we also add a wrapper around the |flush()| method.
  602. @<Functions for scripting@>=
  603. void setSerialPortProperties(QScriptValue value, QScriptEngine *engine)
  604. {
  605. setQIODeviceProperties(value, engine);
  606. value.setProperty("flush", engine->newFunction(SerialPort_flush));
  607. }
  608. @ The wrapper around |flush()| is trivial.
  609. @<Functions for scripting@>=
  610. QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *)
  611. {
  612. QextSerialPort *self = getself<QextSerialPort *>(context);
  613. self->flush();
  614. return QScriptValue();
  615. }