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.

phidgets.w 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. @** Phidgets 1048.
  2. \noindent Phidgets, Inc. has provided one of their four channel temperature
  3. sensor devices so that support could be added to \pn{}. This was originally
  4. planned for version 1.7, however early support was rushed in for the 1.6.3
  5. release. As a result, this support is not full featured, but it should still be
  6. adequate for the most common uses.
  7. Two configuration widgets are required. The first is for the device as a whole.
  8. @<Class declarations@>=
  9. class PhidgetsTemperatureSensorConfWidget : public BasicDeviceConfigurationWidget
  10. {
  11. Q_OBJECT
  12. public:
  13. Q_INVOKABLE PhidgetsTemperatureSensorConfWidget(DeviceTreeModel *model,
  14. const QModelIndex &index);
  15. private slots:
  16. void addChannel();
  17. void updateRate(int ms);
  18. };
  19. @ This widget allows specification of a device wide sample rate and allows
  20. adding channels for the device to monitor. The device specifications indicate
  21. temperature updates happen up to 25 times per second, but this is generally
  22. excessive for \pn{} so a default rate is set to a multiple of this close to
  23. 3 updates per second. There are other options for collecting measurements from
  24. this device and I have not yet had time to experiment with all of the options
  25. to determine the best approach suitable for coffee roasting applications.
  26. @<Phidgets implementation@>=
  27. PhidgetsTemperatureSensorConfWidget::PhidgetsTemperatureSensorConfWidget(DeviceTreeModel *model,
  28. const QModelIndex &index)
  29. : BasicDeviceConfigurationWidget(model, index)
  30. {
  31. QFormLayout *layout = new QFormLayout;
  32. QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
  33. QSpinBox *sampleRate = new QSpinBox;
  34. sampleRate->setMinimum(40);
  35. sampleRate->setMaximum(600);
  36. sampleRate->setSingleStep(40);
  37. sampleRate->setValue(360);
  38. @<Get device configuration data for current node@>@;
  39. for(int i = 0; i < configData.size(); i++)
  40. {
  41. node = configData.at(i).toElement();
  42. if(node.attribute("name") == "sampleRate")
  43. {
  44. sampleRate->setValue(node.attribute("value").toInt());
  45. }
  46. }
  47. updateRate(sampleRate->value());
  48. connect(sampleRate, SIGNAL(valueChanged(int)), this, SLOT(updateRate(int)));
  49. connect(addChannelButton, SIGNAL(clicked()), this, SLOT(addChannel()));
  50. layout->addRow(addChannelButton);
  51. layout->addRow(tr("Sample rate:"), sampleRate);
  52. setLayout(layout);
  53. }
  54. @ Adding another channel is handled in the usual way, with the channel
  55. configured in a separate widget.
  56. @<Phidgets implementation@>=
  57. void PhidgetsTemperatureSensorConfWidget::addChannel()
  58. {
  59. insertChildNode(tr("Channel"), "phidgets1048channel");
  60. }
  61. @ Changes to the sample rate are saved as an attribute of the node as usual.
  62. @<Phidgets implementation@>=
  63. void PhidgetsTemperatureSensorConfWidget::updateRate(int ms)
  64. {
  65. updateAttribute("sampleRate", QString("%1").arg(ms));
  66. }
  67. @ The other required configuration widget is for a single channel.
  68. @<Class declarations@>=
  69. class PhidgetTemperatureSensorChannelConfWidget : public BasicDeviceConfigurationWidget
  70. {
  71. Q_OBJECT
  72. public:
  73. Q_INVOKABLE PhidgetTemperatureSensorChannelConfWidget(DeviceTreeModel *model,
  74. const QModelIndex &index);
  75. private slots:
  76. void updateColumnName(const QString &value);
  77. void updateHidden(bool hidden);
  78. void updateTC(int index);
  79. void updateChannel(int channel);
  80. private:
  81. QComboBox *tcType;
  82. };
  83. @ For each channel it is necessary to specify which channel of the device
  84. measurements will come in on. The thermocouple type should be set to match the
  85. type of the thermocouple attached to that channel. The column name and if the
  86. channel is hidden has the same meaning as in channels on other devices.
  87. @<Phidgets implementation@>=
  88. PhidgetTemperatureSensorChannelConfWidget::PhidgetTemperatureSensorChannelConfWidget(
  89. DeviceTreeModel *model, const QModelIndex &index)
  90. : BasicDeviceConfigurationWidget(model, index),
  91. tcType(new QComboBox)
  92. {
  93. QFormLayout *layout = new QFormLayout;
  94. QLineEdit *columnName = new QLineEdit;
  95. layout->addRow(tr("Column Name:"), columnName);
  96. QCheckBox *hideSeries = new QCheckBox("Hide this channel");
  97. layout->addRow(hideSeries);
  98. layout->addRow(tr("Thermocouple Type:"), tcType);
  99. tcType->addItem("Type K", "1");
  100. tcType->addItem("Type J", "2");
  101. tcType->addItem("Type E", "3");
  102. tcType->addItem("Type T", "4");
  103. QSpinBox *channel = new QSpinBox;
  104. layout->addRow(tr("Channel:"), channel);
  105. channel->setMinimum(0);
  106. channel->setMaximum(3);
  107. setLayout(layout);
  108. @<Get device configuration data for current node@>@;
  109. for(int i = 0; i < configData.size(); i++)
  110. {
  111. node = configData.at(i).toElement();
  112. if(node.attribute("name") == "columnname")
  113. {
  114. columnName->setText(node.attribute("value"));
  115. }
  116. else if(node.attribute("name") == "hidden")
  117. {
  118. hideSeries->setChecked(node.attribute("value") == "true");
  119. }
  120. else if(node.attribute("name") == "tctype")
  121. {
  122. tcType->setCurrentIndex(tcType->findData(node.attribute("value")));
  123. }
  124. else if(node.attribute("name") == "channel")
  125. {
  126. channel->setValue(node.attribute("value").toInt());
  127. }
  128. }
  129. updateColumnName(columnName->text());
  130. updateHidden(hideSeries->isChecked());
  131. updateTC(tcType->currentIndex());
  132. updateChannel(channel->value());
  133. connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
  134. connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
  135. connect(tcType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTC(int)));
  136. connect(channel, SIGNAL(valueChanged(int)), this, SLOT(updateChannel(int)));
  137. }
  138. @ Channel configuration settings are persisted as they are made.
  139. @<Phidgets implementation@>=
  140. void PhidgetTemperatureSensorChannelConfWidget::updateColumnName(const QString &value)
  141. {
  142. updateAttribute("columnname", value);
  143. }
  144. void PhidgetTemperatureSensorChannelConfWidget::updateHidden(bool hidden)
  145. {
  146. updateAttribute("hidden", hidden ? "true" : "false");
  147. }
  148. void PhidgetTemperatureSensorChannelConfWidget::updateTC(int index)
  149. {
  150. updateAttribute("tctype", tcType->itemData(index).toString());
  151. }
  152. void PhidgetTemperatureSensorChannelConfWidget::updateChannel(int channel)
  153. {
  154. updateAttribute("channel", QString("%1").arg(channel));
  155. }
  156. @ The configuration widgets need to be registered so they can be instantiated as
  157. appropriate.
  158. @<Register device configuration widgets@>=
  159. app.registerDeviceConfigurationWidget("phidgets1048",
  160. PhidgetsTemperatureSensorConfWidget::staticMetaObject);
  161. app.registerDeviceConfigurationWidget("phidgets1048channel",
  162. PhidgetTemperatureSensorChannelConfWidget::staticMetaObject);
  163. @ A |NodeInserter| for the device node is also required, but this should only
  164. be provided if the required library is installed.
  165. @<Register top level device configuration nodes@>=
  166. QLibrary phidgetsCheck("phidget21");
  167. if(phidgetsCheck.load())
  168. {
  169. inserter = new NodeInserter(tr("Phidgets 1048"), tr("Phidgets 1048"),
  170. "phidgets1048", NULL);
  171. topLevelNodeInserters.append(inserter);
  172. phidgetsCheck.unload();
  173. }
  174. else
  175. {
  176. phidgetsCheck.setFileName("Phidget21.framework/Phidget21");
  177. if(phidgetsCheck.load())
  178. {
  179. inserter = new NodeInserter(tr("Phidgets 1048"), tr("Phidgets 1048"),
  180. "phidgets1048", NULL);
  181. topLevelNodeInserters.append(inserter);
  182. phidgetsCheck.unload();
  183. }
  184. }
  185. @ As usual, a class representing the device is provided.
  186. @<Class declarations@>=
  187. class PhidgetsTemperatureSensor : public QObject
  188. {
  189. Q_OBJECT
  190. public:
  191. Q_INVOKABLE PhidgetsTemperatureSensor(const QModelIndex &deviceIndex);
  192. Q_INVOKABLE int channelCount();
  193. Channel* getChannel(int channel);
  194. Q_INVOKABLE bool isChannelHidden(int channel);
  195. Q_INVOKABLE QString channelColumnName(int channel);
  196. Q_INVOKABLE QString channelIndicatorText(int channel);
  197. public slots:
  198. void start();
  199. void stop();
  200. private slots:
  201. void getMeasurements();
  202. private:
  203. QList<int> channelIndices;
  204. QList<int> tctypes;
  205. QList<Channel*> channelList;
  206. QMap<int, Channel*> channelMap;
  207. QList<bool> hiddenState;
  208. QList<QString> columnNames;
  209. QList<QString> indicatorTexts;
  210. QLibrary driver;
  211. QTimer sampleTimer;
  212. void *device;
  213. @<Phidgets 1048 function pointers@>@;
  214. };
  215. @ The constructor uses the configuration data to set up the interface used for
  216. integration with the logging view.
  217. @<Phidgets implementation@>=
  218. PhidgetsTemperatureSensor::PhidgetsTemperatureSensor(const QModelIndex &index)
  219. : QObject(NULL), driver("phidget21"), device(NULL)
  220. {
  221. DeviceTreeModel *model = (DeviceTreeModel *)(index.model());
  222. QDomElement deviceReferenceElement =
  223. model->referenceElement(model->data(index, Qt::UserRole).toString());
  224. QDomNodeList deviceConfigData = deviceReferenceElement.elementsByTagName("attribute");
  225. QDomElement node;
  226. for(int i = 0; i < deviceConfigData.size(); i++)
  227. {
  228. node = deviceConfigData.at(i).toElement();
  229. if(node.attribute("name") == "sampleRate")
  230. {
  231. sampleTimer.setInterval(node.attribute("value").toInt());
  232. }
  233. }
  234. if(model->hasChildren(index))
  235. {
  236. for(int i = 0; i < model->rowCount(index); i++)
  237. {
  238. QModelIndex channelIndex = model->index(i, 0, index);
  239. QDomElement channelReference = model->referenceElement(model->data(channelIndex, 32).toString());
  240. QDomElement channelReferenceElement = model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
  241. QDomNodeList channelConfigData = channelReferenceElement.elementsByTagName("attribute");
  242. for(int j = 0; j < channelConfigData.size(); j++)
  243. {
  244. node = channelConfigData.at(j).toElement();
  245. if(node.attribute("name") == "channel")
  246. {
  247. int channelID = node.attribute("value").toInt();
  248. channelIndices.append(channelID);
  249. Channel* channel = new Channel;
  250. channelList.append(channel);
  251. channelMap.insert(channelID, channel);
  252. }
  253. else if(node.attribute("name") == "hidden")
  254. {
  255. hiddenState.append(node.attribute("value") == "true");
  256. }
  257. else if(node.attribute("name") == "columnname")
  258. {
  259. columnNames.append(node.attribute("value"));
  260. }
  261. else if(node.attribute("name") == "tctype")
  262. {
  263. tctypes.append(node.attribute("value").toInt());
  264. }
  265. }
  266. indicatorTexts.append(model->data(channelIndex, Qt::DisplayRole).toString());
  267. }
  268. }
  269. }
  270. @ There is a distinction between logical and physical channels. Physical
  271. channels are specified as a configuration attribute and are used for
  272. communication with hardware. Logical channels are determined by the order of
  273. nodes in the configuration and are used for integrating device support with the
  274. rest of the program.
  275. @<Phidgets implementation@>=
  276. int PhidgetsTemperatureSensor::channelCount()
  277. {
  278. return channelList.length();
  279. }
  280. Channel* PhidgetsTemperatureSensor::getChannel(int channel)
  281. {
  282. return channelList.at(channel);
  283. }
  284. @ Some information is available about each channel.
  285. @<Phidgets implementation@>=
  286. bool PhidgetsTemperatureSensor::isChannelHidden(int channel)
  287. {
  288. return hiddenState.at(channel);
  289. }
  290. QString PhidgetsTemperatureSensor::channelColumnName(int channel)
  291. {
  292. if(channel >= 0 && channel < columnNames.length())
  293. {
  294. return columnNames.at(channel);
  295. }
  296. return QString();
  297. }
  298. QString PhidgetsTemperatureSensor::channelIndicatorText(int channel)
  299. {
  300. if(channel >= 0 && channel < indicatorTexts.length())
  301. {
  302. return indicatorTexts.at(channel);
  303. }
  304. return QString();
  305. }
  306. @ To avoid introducing dependencies on a library that is only needed for
  307. hardware that may not exist, the phidget21 library is only loaded at runtime
  308. if it is needed. Some function pointers and associated types are, therefore,
  309. required. This approach also means the associated header does not need to
  310. exist at compile time.
  311. @<Phidgets 1048 function pointers@>=
  312. #ifdef _WIN32
  313. typedef int (__stdcall *PhidgetHandleOnly)(void *);
  314. typedef int (__stdcall *PhidgetHandleInt)(void *, int);
  315. typedef int (__stdcall *PhidgetHandleIntInt)(void *, int, int);
  316. typedef int (__stdcall *PhidgetHandleIntDoubleOut)(void *, int, double*);
  317. #else
  318. typedef int (*PhidgetHandleOnly)(void *);
  319. typedef int (*PhidgetHandleInt)(void *, int);
  320. typedef int (*PhidgetHandleIntInt)(void *, int, int);
  321. typedef int (*PhidgetHandleIntDoubleOut)(void *, int, double*);
  322. #endif
  323. PhidgetHandleOnly createDevice;
  324. PhidgetHandleInt openDevice;
  325. PhidgetHandleInt waitForOpen;
  326. PhidgetHandleIntInt setTCType;
  327. PhidgetHandleIntDoubleOut getTemperature;
  328. PhidgetHandleOnly closeDevice;
  329. PhidgetHandleOnly deleteDevice;
  330. @ Library loading is deferred until we are ready to open a device.
  331. @<Phidgets implementation@>=
  332. void PhidgetsTemperatureSensor::start()
  333. {
  334. if(!driver.load())
  335. {
  336. driver.setFileName("Phidget21.framework/Phidget21");
  337. if(!driver.load())
  338. {
  339. QMessageBox::critical(NULL, tr("Typica: Driver not found"),
  340. tr("Failed to find phidget21. Please install it."));
  341. return;
  342. }
  343. }
  344. if((createDevice = (PhidgetHandleOnly) driver.resolve("CPhidgetTemperatureSensor_create")) == 0 || @|
  345. (openDevice = (PhidgetHandleInt) driver.resolve("CPhidget_open")) == 0 || @|
  346. (waitForOpen = (PhidgetHandleInt) driver.resolve("CPhidget_waitForAttachment")) == 0 || @|
  347. (setTCType = (PhidgetHandleIntInt) driver.resolve("CPhidgetTemperatureSensor_setThermocoupleType")) == 0 || @|
  348. (getTemperature = (PhidgetHandleIntDoubleOut) driver.resolve("CPhidgetTemperatureSensor_getTemperature")) == 0 || @|
  349. (closeDevice = (PhidgetHandleOnly) driver.resolve("CPhidget_close")) == 0 || @|
  350. (deleteDevice = (PhidgetHandleOnly) driver.resolve("CPhidget_delete")) == 0)
  351. {
  352. QMessageBox::critical(NULL, tr("Typica: Link error"),
  353. tr("Failed to link a required symbol in phidget21."));
  354. return;
  355. }
  356. createDevice(&device);
  357. openDevice(device, -1);
  358. int error;
  359. if((error = waitForOpen(device, 10000)))
  360. {
  361. closeDevice(device);
  362. deleteDevice(device);
  363. QMessageBox::critical(NULL, tr("Typica: Failed to Open Device"),
  364. tr("CPhidget_waitForAttachment returns error %n", 0, error));
  365. return;
  366. }
  367. for(int i = 0; i < channelIndices.length(); i++)
  368. {
  369. setTCType(device, channelIndices.at(i), tctypes.at(i));
  370. }
  371. connect(&sampleTimer, SIGNAL(timeout()), this, SLOT(getMeasurements()));
  372. sampleTimer.start();
  373. }
  374. @ Once the device is started, we periodically request measurements and pass
  375. them to the appropriate |Channel|.
  376. @<Phidgets implementation@>=
  377. void PhidgetsTemperatureSensor::getMeasurements()
  378. {
  379. double value = 0.0;
  380. QTime time = QTime::currentTime();
  381. foreach(int i, channelIndices)
  382. {
  383. getTemperature(device, i, &value);
  384. Measurement measure(value * 9.0 / 5.0 + 32.0, time);
  385. channelMap[i]->input(measure);
  386. }
  387. }
  388. @ Some clean up is needed in the |stop()| method.
  389. @<Phidgets implementation@>=
  390. void PhidgetsTemperatureSensor::stop()
  391. {
  392. sampleTimer.stop();
  393. closeDevice(device);
  394. deleteDevice(device);
  395. driver.unload();
  396. }
  397. @ The implementation currently goes into typica.cpp.
  398. @<Class implementations@>=
  399. @<Phidgets implementation@>@;
  400. @ The |PhidgetsTemperatureSensor| needs to be available from the host
  401. environment. This detail is likely to change in the future.
  402. @<Set up the scripting engine@>=
  403. constructor = engine->newFunction(constructPhidgetsTemperatureSensor);
  404. value = engine->newQMetaObject(&PhidgetsTemperatureSensor::staticMetaObject, constructor);
  405. engine->globalObject().setProperty("PhidgetsTemperatureSensor", value);
  406. @ Two function prototypes are needed.
  407. @<Function prototypes for scripting@>=
  408. QScriptValue constructPhidgetsTemperatureSensor(QScriptContext *context, QScriptEngine *engine);
  409. QScriptValue Phidgets_getChannel(QScriptContext *context, QScriptEngine *engine);
  410. @ The script constructor is trivial.
  411. @<Functions for scripting@>=
  412. QScriptValue constructPhidgetsTemperatureSensor(QScriptContext *context, QScriptEngine *engine)
  413. {
  414. if(context->argumentCount() != 1)
  415. {
  416. context->throwError("Incorrect number of arguments passed to "@|
  417. "PhidgetsTemperatureSensor constructor. This takes "@|
  418. "a QModelIndex.");
  419. }
  420. QScriptValue object = engine->newQObject(new PhidgetsTemperatureSensor(argument<QModelIndex>(0, context)), QScriptEngine::ScriptOwnership);
  421. setQObjectProperties(object, engine);
  422. object.setProperty("getChannel", engine->newFunction(Phidgets_getChannel));
  423. return object;
  424. }
  425. @ As usual, a wrapper is needed for getting channels.
  426. @<Functions for scripting@>=
  427. QScriptValue Phidgets_getChannel(QScriptContext *context, QScriptEngine *engine)
  428. {
  429. PhidgetsTemperatureSensor *self = getself<PhidgetsTemperatureSensor *>(context);
  430. QScriptValue object;
  431. if(self)
  432. {
  433. object = engine->newQObject(self->getChannel(argument<int>(0, context)));
  434. setChannelProperties(object, engine);
  435. }
  436. return object;
  437. }