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.

dataqsdk.w 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. @** Support for Devices Using the DATAQ SDK.
  2. \noindent Support for hardware from DATAQ Instruments is currently provided
  3. through the DATAQ SDK. This means that this support is currently only available
  4. on Microsoft Windows. The first planned supported device is the DI-145 with
  5. support for the DI-148U planned later. The devices are sufficiently similar
  6. that adding support for other devices from this manufacturer should be easy,
  7. but I do not have hardware samples to use for testing with other devices. The
  8. DI-145 additionally has a documented serial protocol which should make the
  9. hardware usable without the DATAQ SDK both on Microsoft Windows and on other
  10. platforms for which there is a suitable serial driver.
  11. Originally the classes were surrounded with conditional compilation directives
  12. but moc failed to generate the appropriate meta-objects on Windows when this
  13. was done. The |DataqSdkDevice| and |DataqSdkDeviceImplementation| classes will
  14. truly only work on Microsoft Windows at this time. Attempts to use it elsewhere
  15. will not end well.
  16. @<Class declarations@>=
  17. class DataqSdkDeviceImplementation;
  18. class DataqSdkDevice : public QObject
  19. {
  20. Q_OBJECT
  21. DataqSdkDeviceImplementation *imp;
  22. private slots:
  23. void threadFinished();
  24. public:
  25. DataqSdkDevice(QString device);
  26. ~DataqSdkDevice();
  27. Channel* newChannel(Units::Unit scale);
  28. Q_INVOKABLE void setClockRate(double Hz);
  29. Q_INVOKABLE void start();
  30. static QStringList detectPorts();
  31. static QStringList detectHardware(); // Friendly names
  32. };
  33. @ The |DataqSdkDevice| class has as a private member an instance of a class
  34. called |DataqSdkDeviceImplementation|. The two classes together create and run
  35. a new thread of execution. This thread spends most of its time blocking while
  36. waiting for a new measurement to become available. When a new measurement is
  37. available, that measurement is passed to the appropriate channel which in turn
  38. passes it to any interested object.
  39. Note that subclassing |QThread| in this way is no longer considered best
  40. practice. This particular code architecture is based on code written when this
  41. was considered the right thing to do, but it would be good to rewrite this to
  42. not subclass |QThread| now that this is no longer required.
  43. @<Class declarations@>=
  44. class DataqSdkDeviceImplementation : public QThread
  45. {
  46. Q_OBJECT
  47. public:
  48. DataqSdkDeviceImplementation();
  49. ~DataqSdkDeviceImplementation();
  50. void run();
  51. @<DATAQ SDK library function pointers@>@;
  52. @<DataqSdkDeviceImplementation member data@>@;
  53. public slots:
  54. void measure();
  55. private:
  56. qint16 *buffer;
  57. };
  58. @ While the |DAQ| class for communicating with National Instruments devices
  59. uses a single function pointer type, increased variety of function signatures
  60. in the DATAQ SDK makes using several types a better option. This also
  61. eliminates the need for explicit casts on the arguments.
  62. @<DATAQ SDK library function pointers@>=
  63. typedef struct di_inlist_struct {
  64. unsigned short chan;
  65. unsigned short diff;
  66. unsigned short gain;
  67. unsigned short unipolar;
  68. unsigned short dig_out_enable;
  69. unsigned short dig_out;
  70. unsigned short ave;
  71. unsigned short counter;
  72. } DI_INLIST_STRUCT;
  73. typedef int (PASCAL *FPDIOPEN)(unsigned);
  74. typedef int (PASCAL *FPDICLOSE)(void);
  75. typedef double (PASCAL *FPDISAMPLERATE)(double, long*, long*);
  76. typedef double (PASCAL *FPDIMAXIMUMRATE)(double);
  77. typedef int (PASCAL *FPDILISTLENGTH)(unsigned, unsigned);
  78. typedef int (PASCAL *FPDIINLIST)(di_inlist_struct*);
  79. typedef int* (PASCAL *FPDIBUFFERALLOC)(unsigned, unsigned);
  80. typedef int (PASCAL *FPDISTARTSCAN)(void);
  81. typedef unsigned (PASCAL *FPDISTATUSREAD)(short*, unsigned);
  82. typedef unsigned (PASCAL *FPDIBUFFERSTATUS)(unsigned);
  83. typedef int (PASCAL *FPDIBURSTRATE)(unsigned);
  84. typedef int (PASCAL *FPDISTOPSCAN)(void);
  85. FPDIOPEN di_open;
  86. FPDICLOSE di_close;
  87. FPDISAMPLERATE di_sample_rate;
  88. FPDIMAXIMUMRATE di_maximum_rate;
  89. FPDILISTLENGTH di_list_length;
  90. FPDIINLIST di_inlist;
  91. FPDIBUFFERALLOC di_buffer_alloc;
  92. FPDISTARTSCAN di_start_scan;
  93. FPDISTATUSREAD di_status_read;
  94. FPDIBUFFERSTATUS di_buffer_status;
  95. FPDIBURSTRATE di_burst_rate;
  96. FPDISTOPSCAN di_stop_scan;
  97. @ The |PASCAL| macro is defined in the {\tt windef.h} header file which will
  98. need to be included. This modifies the mechanics of the function call. A
  99. feature of the C language which C++ inherits is the ability to create variadic
  100. functions. To facilitate this, when one function calls another, the function
  101. making that call is responsible for cleaning up the stack. The function being
  102. called has no reliable way of knowing how many and what type of arguments have
  103. been passed if it is a variadic function, but this can be determined in the
  104. calling function at compile time. This is effectively a compiler implementation
  105. detail which is unimportant to the vast majority of application code. Use of
  106. the |PASCAL| macro informs the compiler that the function being called will
  107. clean up the stack itself. This precludes the use of variadic functions, but
  108. results in a smaller executable. The choice of name for that macro is
  109. unfortunate as arguments are placed on the stack in the order opposite of
  110. calling conventions of the Pascal programming language, but these are
  111. unimportant details so long as the resulting program works.
  112. @<Header files to include@>=
  113. #ifdef Q_OS_WIN32
  114. #include <windef.h>
  115. #else
  116. #define PASCAL
  117. #endif
  118. @ |DataqSdkDeviceImplementation| maintains information about the device and the
  119. channels the measurements are sent to.
  120. @<DataqSdkDeviceImplementation member data@>=
  121. bool isOpen;
  122. double sampleRate;
  123. long oversample;
  124. long burstDivisor;
  125. QString device;
  126. unsigned deviceNumber;
  127. QVector<Channel*> channelMap;
  128. int error;
  129. int channels;
  130. bool ready;
  131. QLibrary *driver;
  132. QVector<Units::Unit> unitMap;
  133. int *input_buffer;
  134. QTimer *eventClock;
  135. QMultiMap<int, double> smoother;
  136. @ Most of the interesting work associated with the |DataqSdkDevice| class is
  137. handled in the |measure()| method of |DataqSdkDeviceImplementation|. This
  138. method will block until a measurement is available. Once |buffer| is filled by
  139. |di_status_read()| that function returns and new |Measurement| objects are
  140. created based on the information in the buffer. These measurements are sent to
  141. |Channel| objects tracked by |channelMap|.
  142. The buffered values are presented in terms of ADC counts. Before using these
  143. values to convert to a voltage measurement, the two least significant binary
  144. digits of the count are set to 0 to improve measurement accuracy as recommended
  145. in the DATAQ SDK reference documentation.
  146. One of the use cases for this class is using the data port provided on some
  147. roasters from Diedrich Manufacturing. In this case there are three channels
  148. that are used: one provides a 0-10V signal that maps to temperature
  149. measurements of 32 to 1832 degrees F, one provides a signal in the same range
  150. requiring distinguishing among three values for air flow settings, and one is
  151. intended to show a percentage for the fuel setting. After experimenting with
  152. the most direct approach, there are limitations of the hardware that complicate
  153. matters for the channel representing bean temperature. The hardware is
  154. providing a 14 bit value representing a signal in the range of +/-10V so as a
  155. practical matter we only have 13 bits for temperature values. There is a desire
  156. to present measurements with at least one digit after the decimal point,
  157. meaning that we require 18,000 distinct values despite likely only ever seeing
  158. values in the lower third of that range. A 13 bit value only allows 8,192
  159. distinct values to be represented. The result of this is that stable signals
  160. between representable values are coded in an inconsistent fashion which can be
  161. seen as displayed measurements varying erratically. The usual solution to this
  162. problem is to collect many measurements quickly and average them, which is a
  163. reasonable thing to do with the sample rates available on DATAQ hardware.
  164. Examining measurements at a higher sample rate unfortunately reveals a periodic
  165. structure to the measurement error which averaging alone is not adequate to
  166. solve. The quality of the measurements can be improved somewhat by removing the
  167. extreme values from each set of measurements prior to averaging, however this
  168. does not fully address the lower frequency error sources. Further improvements
  169. can be made by maintaining a multimap of recent ADC count values to averaged
  170. voltage values and producing results that take this slightly longer term data
  171. into account. This is essential for obtaining a sufficiently stable low
  172. temperature calibration value and introduces minimal additional measurement
  173. latency during a roast.
  174. At present smoothing is applied to the first data channel and no others. It
  175. should be possible to enable or disable adaptive smoothing for all channels
  176. independently to better handle different hardware configurations.
  177. @<DataqSdkDevice implementation@>=
  178. void DataqSdkDeviceImplementation::measure()
  179. {
  180. unsigned count = channels * 40;
  181. di_status_read(buffer, count);
  182. QTime time = QTime::currentTime();
  183. for(unsigned int i = 0; i < count; i++)
  184. {
  185. buffer[i] = buffer[i] & 0xFFFC;
  186. }
  187. QList<int> countList;
  188. for(unsigned int i = 0; i < (unsigned)channels; i++)
  189. {
  190. QList<double> channelBuffer;
  191. for(unsigned int j = 0; j < 40; j++)
  192. {
  193. channelBuffer << ((double)buffer[i+(channels*j)] * 10.0) / 32768.0;
  194. if(i == 0)
  195. {
  196. countList << buffer[i+(channels*j)];
  197. }
  198. }
  199. double value = 0.0;
  200. for(unsigned int j = 0; j < 40; j++)
  201. {
  202. value += channelBuffer[j];
  203. }
  204. value /= 40.0;
  205. if(i == 0)
  206. {
  207. QList<double> smoothingList;
  208. smoothingList << value;
  209. QList<int> smoothingKeys = smoother.uniqueKeys();
  210. for(int j = 0; j < smoothingKeys.size(); j++)
  211. {
  212. if(countList.contains(smoothingKeys[j]))
  213. {
  214. QList<double> keyValues = smoother.values(smoothingKeys[j]);
  215. for(int k = 0; k < keyValues.size(); k++)
  216. {
  217. smoothingList << keyValues[k];
  218. }
  219. }
  220. else
  221. {
  222. smoother.remove(smoothingKeys[j]);
  223. }
  224. }
  225. qSort(countList);
  226. int lastCount = 0;
  227. for(int j = 0; j < countList.size(); j++)
  228. {
  229. if(j == 0 || countList[j] != lastCount)
  230. {
  231. smoother.insert(countList[j], value);
  232. lastCount = countList[j];
  233. }
  234. }
  235. value = 0.0;
  236. for(int j = 0; j < smoothingList.size(); j++)
  237. {
  238. value += smoothingList[j];
  239. }
  240. value /= smoothingList.size();
  241. }
  242. Measurement measure(value, time, unitMap[i]);
  243. channelMap[i]->input(measure);
  244. }
  245. }
  246. @ It was noted that |di_status_read()| blocks until it is able to fill the
  247. |buffer| passed to it. To prevent this behavior from having adverse effects on
  248. the rest of the program, |measure()| is called from a loop running in its own
  249. thread of execution. When the thread is started, it begins its execution from
  250. the |run()| method of |DataqSdkDeviceImplementation| which overrides the
  251. |run()| method of |QThread|.
  252. The while loop is controlled by |ready| which is set to |false| when there is
  253. an error in collecting a measurement or when there is a desire to stop logging.
  254. It could also be set to |false| for reconfiguration events.
  255. All device initialization happens in this method.
  256. Note that while the equivalent method when communicating with National
  257. Instruments hardware sets a time critical thread priority in an attempt to cut
  258. down on the variation in time between recorded measurements, that is a really
  259. bad idea when using the DATAQ SDK. The result was that the main thread never
  260. got enough time to report measurements and responsiveness throughout the entire
  261. system became barely usable to the point that it was difficult to kill the
  262. process. If anybody reading this can provide some insight into why setting the
  263. thread priority is fine with interacting with either DAQmx or DAQmxBase but not
  264. when interacting with the DATAQ SDK, I would like to read such an explanation.
  265. @<DataqSdkDevice implementation@>=
  266. void DataqSdkDeviceImplementation::run()
  267. {
  268. if(!ready)
  269. {
  270. error = 9; // Device data not available
  271. return;
  272. }
  273. driver = new QLibrary(device);
  274. if(!driver->load())
  275. {
  276. error = 1; // Failed to load driver.
  277. qDebug() << "Failed to load driver: " << device;
  278. return;
  279. }
  280. di_open = (FPDIOPEN)driver->resolve("di_open");
  281. di_close = (FPDICLOSE)driver->resolve("di_close");
  282. di_sample_rate = (FPDISAMPLERATE)driver->resolve("di_sample_rate");
  283. di_maximum_rate = (FPDIMAXIMUMRATE)driver->resolve("di_maximum_rate");
  284. di_list_length = (FPDILISTLENGTH)driver->resolve("di_list_length");
  285. di_inlist = (FPDIINLIST)driver->resolve("di_inlist");
  286. di_buffer_alloc = (FPDIBUFFERALLOC)driver->resolve("di_buffer_alloc");
  287. di_start_scan = (FPDISTARTSCAN)driver->resolve("di_start_scan");
  288. di_status_read = (FPDISTATUSREAD)driver->resolve("di_status_read");
  289. di_buffer_status = (FPDIBUFFERSTATUS)driver->resolve("di_buffer_status");
  290. di_burst_rate = (FPDIBURSTRATE)driver->resolve("di_burst_rate");
  291. di_stop_scan = (FPDISTOPSCAN)driver->resolve("di_stop_scan");
  292. if((!di_open) || (!di_close) || (!di_sample_rate) || (!di_maximum_rate) ||
  293. (!di_list_length) || (!di_inlist) || (!di_buffer_alloc) ||
  294. (!di_start_scan) || (!di_status_read) || (!di_buffer_status) ||
  295. (!di_burst_rate) || (!di_stop_scan))
  296. {
  297. error = 2; // Failed to link required symbol
  298. return;
  299. }
  300. error = di_open(deviceNumber);
  301. if(error)
  302. {
  303. di_close();
  304. error = di_open(deviceNumber);
  305. if(error)
  306. {
  307. error = 3; // Failed to open device
  308. di_close();
  309. return;
  310. }
  311. }
  312. isOpen = true;
  313. di_maximum_rate(240.0);
  314. sampleRate = di_sample_rate(sampleRate * channels * 40, &oversample,
  315. &burstDivisor);
  316. buffer = new qint16[(int)sampleRate];
  317. di_inlist_struct inlist[16] = {{0}};
  318. for(unsigned short i = 0; i < channels; i++)
  319. {
  320. inlist[i].chan = i;
  321. inlist[i].gain = 0;
  322. inlist[i].ave = 1;
  323. inlist[i].counter = (oversample - 1);
  324. }
  325. error = di_list_length(channels, 0);
  326. if(error)
  327. {
  328. error = 4; // List length error
  329. return;
  330. }
  331. error = di_inlist(inlist);
  332. if(error)
  333. {
  334. error = 5; // Inlist error
  335. return;
  336. }
  337. input_buffer = di_buffer_alloc(0, 4096);
  338. if(input_buffer == NULL)
  339. {
  340. error = 6; // Failed to allocate buffer
  341. return;
  342. }
  343. error = di_start_scan();
  344. if(error)
  345. {
  346. error = 7; // Failed to start scanning
  347. return;
  348. }
  349. while(ready)
  350. {
  351. measure();
  352. }
  353. }
  354. @ When the loop exits, |DataqSdkDeviceImplementation| emits a finished signal
  355. to indicate that the thread is no longer running. This could be due to normal
  356. conditions or there could be a problem that should be reported. That signal is
  357. connected to a function that checks for error conditions and reports them if
  358. needed.
  359. @<DataqSdkDevice implementation@>=
  360. void DataqSdkDevice::threadFinished()
  361. {
  362. if(imp->error)
  363. {
  364. @<Display DATAQ SDK Error@>@;
  365. }
  366. }
  367. @ The DATAQ SDK does not have a single method for reporting errors. Instead,
  368. any method that can return an error code has its return value checked and
  369. |error| is set to a value that allows the source of the problem to be
  370. determined. At present, error handling is very poor.
  371. @<Display DATAQ SDK Error@>=
  372. imp->ready = false;
  373. QMessageBox warning;
  374. warning.setStandardButtons(QMessageBox::Cancel);
  375. warning.setIcon(QMessageBox::Warning);
  376. warning.setText(QString(tr("Error: %1")).arg(imp->error));
  377. warning.setInformativeText(tr("An error occurred"));
  378. warning.setWindowTitle(QString(PROGRAM_NAME));
  379. warning.exec();
  380. @ Starting the thread is very simple. Device initialization happens in the new
  381. thread which then begins taking measurements. The call to |imp->start()| starts
  382. the new thread and passes control of that thread to |imp->run()|. The main
  383. thread of execution returns without waiting for the new thread to do anything.
  384. When the thread is finished, the |finished()| signal is emitted which we have
  385. connected to |threadFinished()|.
  386. @<DataqSdkDevice implementation@>=
  387. void DataqSdkDevice::start()
  388. {
  389. connect(imp, SIGNAL(finished()), this, SLOT(threadFinished()));
  390. imp->start();
  391. }
  392. @ When configuring Typica to use a device supported through the DATAQ SDK it is
  393. useful to have a way to report the ports where supported hardware has been
  394. detected. This is also used for automatic detection.
  395. @<DataqSdkDevice implementation@>=
  396. QStringList DataqSdkDevice::detectHardware()
  397. {
  398. QSettings deviceLookup("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\usbser\\Enum",
  399. QSettings::NativeFormat);
  400. QStringList keys = deviceLookup.childKeys();
  401. QStringList devices;
  402. for(int i = 0; i < keys.size(); i++)
  403. {
  404. QString value = deviceLookup.value(keys.at(i)).toString();
  405. if(value.startsWith("USB\\VID_0683&PID_1450\\"))
  406. {
  407. devices.append(value.split("\\").at(2));
  408. }
  409. }
  410. QStringList portList;
  411. foreach(QString device, devices)
  412. {
  413. QString deviceKey = QString("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_0683&PID_1450\\%1").arg(device);
  414. QSettings deviceEntry(deviceKey, QSettings::NativeFormat);
  415. portList.append(deviceEntry.value("FriendlyName").toString());
  416. }
  417. return portList;
  418. }
  419. QStringList DataqSdkDevice::detectPorts()
  420. {
  421. QStringList friendlyNames = detectHardware();
  422. QStringList portList;
  423. foreach(QString name, friendlyNames)
  424. {
  425. name.remove(0, name.indexOf("COM"));
  426. name.chop(1);
  427. portList.append(name);
  428. }
  429. return portList;
  430. }
  431. @ Setting up the device begins by constructing a new |DataqSdkDevice| object.
  432. The constructor takes as its argument a string which identifies the device. For
  433. legacy reasons this currently accepts device names such as |"Dev1"| and looks
  434. up currently connected devices to determine which serial port should be used.
  435. Now that it is preferred to configure devices graphically this is not a good
  436. way to do this. This should be changed before release.
  437. @<DataqSdkDevice implementation@>=
  438. DataqSdkDevice::DataqSdkDevice(QString device) : imp(new DataqSdkDeviceImplementation)
  439. {
  440. bool usesAuto = false;
  441. int autoIndex = device.toInt(&usesAuto);
  442. QString finalizedPort;
  443. if(usesAuto)
  444. {
  445. QStringList portList = detectPorts();
  446. if(autoIndex > 0 && autoIndex <= portList.size())
  447. {
  448. finalizedPort = portList.at(autoIndex - 1);
  449. }
  450. else
  451. {
  452. imp->error = 8; // Failed to find device.
  453. qDebug() << "Failed to detect port.";
  454. }
  455. }
  456. else
  457. {
  458. finalizedPort = device;
  459. }
  460. int rstart = finalizedPort.indexOf("COM");
  461. finalizedPort.remove(0, rstart + 3);
  462. if(finalizedPort.toInt() < 10)
  463. {
  464. imp->device = QString("DI10%1NT.DLL").arg(finalizedPort);
  465. }
  466. else
  467. {
  468. imp->device = QString("DI1%1NT.DLL").arg(finalizedPort);
  469. }
  470. imp->deviceNumber = 0x12C02D00;
  471. imp->deviceNumber += finalizedPort.toInt();
  472. imp->ready = true;
  473. }
  474. @ Once the |DataqSdkDevice| is created, one or more channels can be added.
  475. @<DataqSdkDevice implementation@>=
  476. Channel* DataqSdkDevice::newChannel(Units::Unit scale)
  477. {
  478. Channel *retval = NULL;
  479. if(imp->ready)
  480. {
  481. retval = new Channel();
  482. imp->channelMap[imp->channels] = retval;
  483. imp->unitMap[imp->channels] = scale;
  484. imp->channels++;
  485. }
  486. return retval;
  487. }
  488. @ Once the channels are created, it is necessary to set the clock rate of the
  489. device. The DATAQ SDK will set the clock rate to be whichever value is closest
  490. to the specified value that is supported by the hardware. Note that when
  491. measuring multiple channels the device clock rate should be the desired sample
  492. rate per channel multiplied by the number of channels.
  493. The amount of time between measurements may vary slightly. Tests have shown
  494. that while most measurements come within 1ms of the expected time, some
  495. measurements do not come in within 100ms of the expected time.
  496. @<DataqSdkDevice implementation@>=
  497. void DataqSdkDevice::setClockRate(double Hz)
  498. {
  499. imp->sampleRate = Hz;
  500. }
  501. @ The destructor instructs the measurement thread to stop, waits for it to
  502. finish, and resets the device. If this is not done, an error would be issued
  503. the next time a program attempted to use the device.
  504. @<DataqSdkDevice implementation@>=
  505. DataqSdkDevice::~DataqSdkDevice()
  506. {
  507. if(imp->ready)
  508. {
  509. imp->ready = false;
  510. }
  511. imp->wait(ULONG_MAX);
  512. delete imp;
  513. }
  514. @ The constructor and destructor in |DataqSdkDeviceImplementation| currently
  515. limit the number of channels to 4. As additional devices are supported this
  516. restriction should be lifted.
  517. Very little is needed from the constructor. The destructor is responsible for
  518. closing the device and unloading the device driver.
  519. @<DataqSdkDevice implementation@>=
  520. DataqSdkDeviceImplementation::DataqSdkDeviceImplementation() : QThread(NULL),
  521. channelMap(4), error(0), channels(0), ready(false), unitMap(4)
  522. {
  523. /* Nothing needs to be done here. */
  524. }
  525. DataqSdkDeviceImplementation::~DataqSdkDeviceImplementation()
  526. {
  527. if(isOpen)
  528. {
  529. di_stop_scan();
  530. di_close();
  531. }
  532. if(driver->isLoaded())
  533. {
  534. driver->unload();
  535. }
  536. }
  537. @ This is exposed to the scripting engine in the usual way.
  538. @<Function prototypes for scripting@>=
  539. QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine);
  540. QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine);
  541. void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine);
  542. @ These functions are made known to the scripting engine.
  543. @<Set up the scripting engine@>=
  544. constructor = engine->newFunction(constructDataqSdkDevice);
  545. value = engine->newQMetaObject(&DataqSdkDevice::staticMetaObject, constructor);
  546. engine->globalObject().setProperty("DataqSdkDevice", value);
  547. @ When creating a new device we make sure that it is owned by the script
  548. engine. This is necessary to ensure that the destructor is called before \pn{}
  549. exits. Just as the constructor requires an argument that specifies the device
  550. name, the constructor available from a script also requires this argument.
  551. @<Functions for scripting@>=
  552. QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine)
  553. {
  554. QScriptValue object;
  555. if(context->argumentCount() == 1)
  556. {
  557. object = engine->newQObject(new DataqSdkDevice(argument<QString>(0, context)),
  558. QScriptEngine::ScriptOwnership);
  559. setDataqSdkDeviceProperties(object, engine);
  560. }
  561. else
  562. {
  563. context->throwError("Incorrect number of arguments passed to "
  564. "DataqSdkDevice. The constructor takes one string "
  565. "as an argument specifying a device name. "
  566. "Example: Dev1");
  567. }
  568. return object;
  569. }
  570. @ As |DataqSdkDevice| inherits |QObject| we add the |newChannel()| property
  571. after adding any |QObject| properties.
  572. @<Functions for scripting@>=
  573. void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine)
  574. {
  575. setQObjectProperties(value, engine);
  576. value.setProperty("newChannel", engine->newFunction(DataqSdkDevice_newChannel));
  577. }
  578. @ The |newChannel()| wrapper requires two arguments.
  579. @<Functions for scripting@>=
  580. QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine)
  581. {
  582. DataqSdkDevice *self = getself<DataqSdkDevice *>(context);
  583. QScriptValue object;
  584. if(self)
  585. {
  586. object = engine->newQObject(self->newChannel((Units::Unit)argument<int>(0, context)));
  587. setChannelProperties(object, engine);
  588. }
  589. return object;
  590. }