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 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  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. connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  216. connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  217. connect(channelSettingsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
  218. this, SLOT(updateConfiguration()));
  219. }
  220. @ Arbitrary channel configuration data is handled in the same way as device
  221. level settings while the column name and hidden status is handled in the same
  222. way as they are in other channel nodes.
  223. @<UnsupportedSerialDeviceConfWidget implementation@>=
  224. void UnsupportedDeviceChannelConfWidget::updateColumnName(const QString &value)
  225. {
  226. updateAttribute("columnname", value);
  227. }
  228. void UnsupportedDeviceChannelConfWidget::updateHidden(bool hidden)
  229. {
  230. updateAttribute("hidden", hidden ? "true" : "false");
  231. }
  232. void UnsupportedDeviceChannelConfWidget::updateConfiguration()
  233. {
  234. updateAttribute("keys", channelSettingsModel->arrayLiteral(0, Qt::DisplayRole));
  235. updateAttribute("values", channelSettingsModel->arrayLiteral(1, Qt::DisplayRole));
  236. }
  237. @ The configuration widgets need to be registered so they can be instantiated
  238. as appropriate.
  239. @<Register device configuration widgets@>=
  240. app.registerDeviceConfigurationWidget("unsupporteddevicechannel",
  241. UnsupportedDeviceChannelConfWidget::staticMetaObject);
  242. app.registerDeviceConfigurationWidget("unsupporteddevice",
  243. UnsupportedSerialDeviceConfWidget::staticMetaObject);
  244. @ A |NodeInserter| for the device node is also provided.
  245. @<Register top level device configuration nodes@>=
  246. inserter = new NodeInserter(tr("Other Device"), tr("Other Device"),
  247. "unsupporteddevice", NULL);
  248. topLevelNodeInserters.append(inserter);
  249. @ A device abstraction is not strictly required for this feature, however
  250. having one greatly simplifies integrating this feature. At some point I would
  251. like to revise other device abstraction classes so that a huge amount of
  252. boilerplate associated with these can be removed from configuration documents.
  253. This device abstraction includes features in a few particular categories. First
  254. there are methods that are required for integrating the device with the logging
  255. view. The logging view instantiates the device abstraction, passing in the
  256. configuration data required to properly set up the device. It then is able to
  257. query information about the measurement channels that have been configured for
  258. this device and can set up all of the relevant indicators. Some device classes
  259. may be able to produce annotations, so this class can be treated exactly the
  260. same as any other annotation source. Another requested feature includes the
  261. ability of a device to trigger the start and end of batches, so signals are
  262. provided for this capability. Finally, there are methods associated with
  263. starting and stopping the device. The |start()| method will be called when the
  264. logging view has finished making all of the signal connections. The |stop()|
  265. method will be called when the logging view is closed, giving script code the
  266. chance to cleanly release any resources that must be held for device
  267. communications.
  268. @<Class declarations@>=
  269. class JavaScriptDevice : public QObject
  270. {
  271. Q_OBJECT
  272. public:
  273. Q_INVOKABLE JavaScriptDevice(const QModelIndex &deviceIndex,
  274. QScriptEngine *engine);
  275. Q_INVOKABLE int channelCount();
  276. Channel* getChannel(int channel);
  277. Q_INVOKABLE bool isChannelHidden(int channel);
  278. Q_INVOKABLE Units::Unit expectedChannelUnit(int channel);
  279. Q_INVOKABLE QString channelColumnName(int channel);
  280. Q_INVOKABLE QString channelIndicatorText(int channel);
  281. public slots:
  282. void setTemperatureColumn(int tcol);
  283. void setAnnotationColumn(int ncol);
  284. void start();
  285. void stop();
  286. signals:
  287. void annotation(QString note, int tcol, int ncol);
  288. void triggerStartBatch();
  289. void triggerStopBatch();
  290. void deviceStopRequested();
  291. private:
  292. QVariantMap deviceSettings;
  293. QString deviceScript;
  294. QList<Channel *> channelList;
  295. QList<bool> hiddenState;
  296. QList<Units::Unit> channelUnits;
  297. QList<QString> columnNames;
  298. QList<QString> indicatorTexts;
  299. QList<QVariantMap> channelSettings;
  300. int annotationTemperatureColumn;
  301. int annotationNoteColumn;
  302. QScriptEngine *scriptengine;
  303. };
  304. @ The |JavaScriptDevice| instance provides two interfaces. Its invokable
  305. methods provide the information needed to integrate script driven devices with
  306. a generic logging view. Additional information is also exposed through the host
  307. environment running the device script. This means that the class requires
  308. knowledge of the host environment, which it obtains through a script function
  309. similar to what is done for window creation.
  310. The name of the function is generic so this may be easily extended later to
  311. create all device abstraction instances.
  312. @<Function prototypes for scripting@>=
  313. QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine);
  314. @ That method is made available to the scripting engine.
  315. @<Set up the scripting engine@>=
  316. engine->globalObject().setProperty("createDevice",
  317. engine->newFunction(createDevice));
  318. @ This function currently creates a |JavaScriptDevice| from a device
  319. configuration node which must be passed through as an argument.
  320. @<Functions for scripting@>=
  321. QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine)@/
  322. {
  323. QModelIndex deviceIndex = argument<QModelIndex>(0, context);
  324. JavaScriptDevice *device = new JavaScriptDevice(deviceIndex, engine);
  325. QScriptValue object = engine->newQObject(device);
  326. setQObjectProperties(object, engine);
  327. object.setProperty("getChannel", engine->newFunction(JavaScriptDevice_getChannel));
  328. return object;
  329. }
  330. @ The |start()| method is responsible for preparing the host environment and
  331. executing the device script.
  332. @<JavaScriptDevice implementation@>=
  333. void JavaScriptDevice::start()
  334. {
  335. QScriptValue object = scriptengine->newQObject(this);
  336. @<Expose device settings as object property@>@;
  337. @<Expose channels and channel settings to device script@>@;
  338. QScriptContext *context = scriptengine->currentContext();
  339. QScriptValue oldThis = context->thisObject();
  340. context->setThisObject(object);
  341. QScriptValue result = scriptengine->evaluate(deviceScript);
  342. QScriptEngine *engine = scriptengine;
  343. @<Report scripting errors@>@;
  344. context->setThisObject(oldThis);
  345. }
  346. @ Device settings are only needed from the device script itself. As such, these
  347. are presented under a settings property available from the |this| object when
  348. the script is run.
  349. @<Expose device settings as object property@>=
  350. QScriptValue settingsObject = scriptengine->newObject();
  351. QVariantMap::const_iterator i = deviceSettings.constBegin();
  352. while(i != deviceSettings.constEnd())
  353. {
  354. settingsObject.setProperty(i.key(), i.value().toString());
  355. i++;
  356. }
  357. object.setProperty("settings", settingsObject);
  358. @ While channels are available to the device script through the same
  359. |getChannel()| interface used outside of the device script for integration
  360. purposes, it is more convenient to have an array of channels with channel
  361. specific settings as properties of the channel.
  362. @<Expose channels and channel settings to device script@>=
  363. QScriptValue channelsArray = scriptengine->newArray(channelCount());
  364. for(int i = 0; i < channelCount(); i++)
  365. {
  366. QScriptValue channelObject = scriptengine->newQObject(getChannel(i));
  367. QScriptValue channelSettingsObject = scriptengine->newObject();
  368. QVariantMap::const_iterator j = channelSettings.at(i).constBegin();
  369. while(j != channelSettings.at(i).constEnd())
  370. {
  371. channelSettingsObject.setProperty(j.key(), j.value().toString());
  372. j++;
  373. }
  374. channelObject.setProperty("settings", channelSettingsObject);
  375. channelsArray.setProperty(i, channelObject);
  376. }
  377. object.setProperty("channels", channelsArray);
  378. @ Currently we require wrapper functions to work with channels in the host
  379. environment.
  380. @<Function prototypes for scripting@>=
  381. QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine);
  382. @ The implementation is trivial.
  383. @<Functions for scripting@>=
  384. QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine)
  385. {
  386. JavaScriptDevice *self = getself<JavaScriptDevice *>(context);
  387. QScriptValue object;
  388. if(self)
  389. {
  390. object = engine->newQObject(self->getChannel(argument<int>(0, context)));
  391. setChannelProperties(object, engine);
  392. }
  393. return object;
  394. }
  395. @ The |stop()| method just fires off a signal that the script can hook into for
  396. any required cleanup.
  397. @<JavaScriptDevice implementation@>=
  398. void JavaScriptDevice::stop()
  399. {
  400. emit deviceStopRequested();
  401. }
  402. @ The constructor is responsible for all boilerplate initialization required
  403. for integrating script defined devices with the logging view.
  404. Note: At present expected units are assumed to be Fahrenheit. The configuration
  405. widget must be updated to allow at least for control measurements and
  406. eventually support for runtime defined units should also be added.
  407. @<JavaScriptDevice implementation@>=
  408. JavaScriptDevice::JavaScriptDevice(const QModelIndex &index,
  409. QScriptEngine *engine) :
  410. QObject(NULL), scriptengine(engine)
  411. {
  412. DeviceTreeModel *model = (DeviceTreeModel *)(index.model());
  413. QDomElement deviceReferenceElement =
  414. model->referenceElement(model->data(index, Qt::UserRole).toString());
  415. QDomNodeList deviceConfigData = deviceReferenceElement.elementsByTagName("attribute");
  416. QDomElement node;
  417. QStringList deviceKeys;
  418. QStringList deviceValues;
  419. for(int i = 0; i < deviceConfigData.size(); i++)
  420. {
  421. node = deviceConfigData.at(i).toElement();
  422. if(node.attribute("name") == "keys")
  423. {
  424. QString data = node.attribute("value");
  425. if(data.length() > 3)
  426. {
  427. data.chop(2);
  428. data = data.remove(0, 2);
  429. }
  430. deviceKeys = data.split(", ");
  431. }
  432. else if(node.attribute("name") == "values")
  433. {
  434. QString data = node.attribute("value");
  435. if(data.length() > 3)
  436. {
  437. data.chop(2);
  438. data = data.remove(0, 2);
  439. }
  440. deviceValues = data.split(", ");
  441. }
  442. else if(node.attribute("name") == "script")
  443. {
  444. deviceScript = node.attribute("value");
  445. }
  446. deviceSettings.insert(node.attribute("name"), node.attribute("value"));
  447. }
  448. for(int i = 0; i < qMin(deviceKeys.length(), deviceValues.length()); i++)
  449. {
  450. deviceSettings.insert(deviceKeys[i], deviceValues[i]);
  451. }
  452. if(model->hasChildren(index))
  453. {
  454. for(int i = 0; i < model->rowCount(index); i++)
  455. {
  456. QModelIndex channelIndex = model->index(i, 0, index);
  457. QDomElement channelReference = model->referenceElement(model->data(channelIndex, 32).toString());
  458. channelList.append(new Channel);
  459. QDomElement channelReferenceElement =
  460. model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
  461. QDomNodeList channelConfigData =
  462. channelReferenceElement.elementsByTagName("attribute");
  463. QStringList channelKeys;
  464. QStringList channelValues;
  465. for(int j = 0; j < channelConfigData.size(); j++)
  466. {
  467. node = channelConfigData.at(j).toElement();
  468. if(node.attribute("name") == "keys")
  469. {
  470. QString data = node.attribute("value");
  471. if(data.length() > 3)
  472. {
  473. data.chop(2);
  474. data = data.remove(0, 2);
  475. }
  476. channelKeys = data.split(", ");
  477. }
  478. else if(node.attribute("name") == "values")
  479. {
  480. QString data = node.attribute("value");
  481. if(data.length() > 3)
  482. {
  483. data.chop(2);
  484. data = data.remove(0, 2);
  485. }
  486. channelValues = data.split(", ");
  487. }
  488. else if(node.attribute("name") == "hidden")
  489. {
  490. hiddenState.append(node.attribute("value") == "true");
  491. }
  492. else if(node.attribute("name") == "columnname")
  493. {
  494. columnNames.append(node.attribute("value"));
  495. }
  496. }
  497. QVariantMap cs;
  498. for(int j = 0; j < qMin(channelKeys.length(), channelValues.length()); j++)
  499. {
  500. cs.insert(channelKeys[j], channelValues[j]);
  501. }
  502. channelSettings.append(cs);
  503. indicatorTexts.append(model->data(channelIndex, Qt::DisplayRole).toString());
  504. channelUnits.append(Units::Fahrenheit);
  505. }
  506. }
  507. }
  508. @ Several methods are available to query information about the configured
  509. channels.
  510. @<JavaScriptDevice implementation@>=
  511. int JavaScriptDevice::channelCount()
  512. {
  513. return channelList.length();
  514. }
  515. Channel* JavaScriptDevice::getChannel(int channel)
  516. {
  517. return channelList.at(channel);
  518. }
  519. bool JavaScriptDevice::isChannelHidden(int channel)
  520. {
  521. return hiddenState.at(channel);
  522. }
  523. Units::Unit JavaScriptDevice::expectedChannelUnit(int channel)
  524. {
  525. return channelUnits.at(channel);
  526. }
  527. QString JavaScriptDevice::channelColumnName(int channel)
  528. {
  529. if(channel >= 0 && channel < columnNames.length())
  530. {
  531. return columnNames.at(channel);
  532. }
  533. return QString();
  534. }
  535. QString JavaScriptDevice::channelIndicatorText(int channel)
  536. {
  537. return indicatorTexts.at(channel);
  538. }
  539. @ Two slots are provided for controlling the placement of annotations.
  540. @<JavaScriptDevice implementation@>=
  541. void JavaScriptDevice::setTemperatureColumn(int tcol)
  542. {
  543. annotationTemperatureColumn = tcol;
  544. }
  545. void JavaScriptDevice::setAnnotationColumn(int ncol)
  546. {
  547. annotationNoteColumn = ncol;
  548. }
  549. @ Device scripts must be able to produce measurements on a channel. To do this,
  550. a function is provided for obtaining a timestamp. The returned timestamp should
  551. not be examined as future changes may break assumptions about the content of
  552. the timestamp.
  553. @<Function prototypes for scripting@>=
  554. QScriptValue getMeasurementTimestamp(QScriptContext *context, QScriptEngine *engine);
  555. @ That method is made available to the scripting engine.
  556. @<Set up the scripting engine@>=
  557. engine->globalObject().setProperty("getMeasurementTimestamp",
  558. engine->newFunction(getMeasurementTimestamp));
  559. @ At present this simply obtains the current system time. It is planned to
  560. switch to a better quality clock in the future, but this should be done for
  561. everything that uses |Measurement| objects at once.
  562. @<Functions for scripting@>=
  563. QScriptValue getMeasurementTimestamp(QScriptContext *, QScriptEngine *engine)@/
  564. {
  565. return engine->toScriptValue<QTime>(QTime::currentTime());
  566. }
  567. @ At present, implementations are not broken out to a separate file. This
  568. should be changed at some point.
  569. @<Class implementations@>=
  570. @<UnsupportedSerialDeviceConfWidget implementation@>
  571. @<JavaScriptDevice implementation@>
  572. @* Serial Ports.
  573. \noindent The first use case for script driven devices was connecting to
  574. devices which present themselves as a serial port. This covers a broad range
  575. of data acquisition products. To provide this support, |QextSerialPort|, which
  576. was already used to support some other hardware options, is directly exposed to
  577. the host environment.
  578. @<Function prototypes for scripting@>=
  579. QScriptValue constructSerialPort(QScriptContext *context, QScriptEngine *engine);
  580. void setSerialPortProperties(QScriptValue value, QScriptEngine *engine);
  581. QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *engine);
  582. @ Our constructor is passed to the scripting engine.
  583. @<Set up the scripting engine@>=
  584. constructor = engine->newFunction(constructSerialPort);
  585. value = engine->newQMetaObject(&QextSerialPort::staticMetaObject, constructor);
  586. engine->globalObject().setProperty("SerialPort", value);
  587. @ At present we only support event driven communications and are not passing
  588. any port settings through the constructor. Such functionality may be added in
  589. the future, but it is not strictly necessary.
  590. @<Functions for scripting@>=
  591. QScriptValue constructSerialPort(QScriptContext *, QScriptEngine *engine)
  592. {
  593. QScriptValue object = engine->newQObject(new QextSerialPort());
  594. setSerialPortProperties(object, engine);
  595. return object;
  596. }
  597. @ Some properties of |QIODevice| are brought in as usual for similar subclasses
  598. but we also add a wrapper around the |flush()| method.
  599. @<Functions for scripting@>=
  600. void setSerialPortProperties(QScriptValue value, QScriptEngine *engine)
  601. {
  602. setQIODeviceProperties(value, engine);
  603. value.setProperty("flush", engine->newFunction(SerialPort_flush));
  604. }
  605. @ The wrapper around |flush()| is trivial.
  606. @<Functions for scripting@>=
  607. QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *)
  608. {
  609. QextSerialPort *self = getself<QextSerialPort *>(context);
  610. self->flush();
  611. return QScriptValue();
  612. }
  613. @* Timers.
  614. \noindent While some devices will output a steady stream of measurements which
  615. can be continuously read as they come in, other devices must be polled for
  616. their current state. One approach is to poll the device immediately after
  617. reading the response from the previous polling, but there are times when we may
  618. want to limit the rate at which we poll the device. There are also devices
  619. which specify a length of time during which data should not be sent. For these
  620. cases, we expose |QTimer| to the host environment which allows us to wait. This
  621. is also useful for producing simulations to test features without needing to be
  622. connected to real hardware.
  623. <@Function prototypes for scripting@>=
  624. void setQTimerProperties(QScriptValue value, QScriptEngine *engine);
  625. QScriptValue constructQTimer(QScriptContext *context, QScriptEngine *engine);
  626. @ The host environment is informed of the constructor.
  627. @<Set up the scripting engine@>=
  628. constructor = engine->newFunction(constructQTimer);
  629. value = engine->newQMetaObject(&QTimer::staticMetaObject, constructor);
  630. engine->globalObject().setProperty("Timer", value);
  631. @ Everything that we are interested in here is a signal, slot, or property so
  632. there is little else to do.
  633. @<Functions for scripting@>=
  634. void setQTimerProperties(QScriptValue value, QScriptEngine *engine)
  635. {
  636. setQObjectProperties(value, engine);
  637. }
  638. QScriptValue constructQTimer(QScriptContext *, QScriptEngine *engine)
  639. {
  640. QScriptValue object = engine->newQObject(new QTimer);
  641. setQTimerProperties(object, engine);
  642. return object;
  643. }