Bläddra i källkod

Initial support for DATAQ DI-145. Fixes #43

Neal Wilson 11 år sedan
förälder
incheckning
31824dbd0a
3 ändrade filer med 623 tillägg och 10 borttagningar
  1. 606
    0
      src/dataqsdk.w
  2. 15
    10
      src/typica.w
  3. 2
    0
      src/units.w

+ 606
- 0
src/dataqsdk.w Visa fil

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

+ 15
- 10
src/typica.w Visa fil

@@ -435,7 +435,7 @@ such. Code that will be interpreted by the ECMA-262 host environment has no
435 435
 pretty printing.
436 436
 
437 437
 \danger With apologies to prof.~Knuth\nfnote{This symbol was introduced in
438
-{\underbar{Computers~\char'046~Typesetting}} (Knuth, 1984) to point out material
438
+{\underbar{Computers~\char'046~Typesetting}}@q'@> (Knuth, 1984) to point out material
439 439
 that is for ``wizards only.'' It seems to be as appropriate a symbol as any to
440 440
 point out the darker corners of this program.}, code that is known to be
441 441
 potentially buggy is flagged with a dangerous bend sign. Some of this code is
@@ -771,6 +771,9 @@ generated file empty.
771 771
 @<NodeInserter implementation@>@/
772 772
 @<Measurement implementation@>@/
773 773
 @<DAQ Implementation@>@/
774
+#ifdef Q_OS_WIN32
775
+@<DataqSdkDevice implementation@>@/
776
+#endif
774 777
 @<FakeDAQ Implementation@>@/
775 778
 @<Channel Implementation@>@/
776 779
 @<TemperatureDisplay Implementation@>@/
@@ -1364,7 +1367,7 @@ QScriptValue QMainWindow_setCentralWidget(QScriptContext *context,
1364 1367
 }
1365 1368
 
1366 1369
 @ The |"menuBar"| property requires that we expose |QMenuBar| to the scripting
1367
-environment in a limited fashion. We don't need to allow scripts to create a
1370
+environment in a limited fashion. We don'@q'@>t need to allow scripts to create a
1368 1371
 new menu bar as it can be obtained from the window, however to add the menus to
1369 1372
 the menu bar, we need to add a property to the |QMenuBar| object before passing
1370 1373
 it back.
@@ -2111,7 +2114,7 @@ QScriptValue QFile_remove(QScriptContext *context, QScriptEngine *engine)
2111 2114
 	return QScriptValue(engine, retval);
2112 2115
 }
2113 2116
 
2114
-@ Although we aren't going to create any instances of |QIODevice| directly,
2117
+@ Although we aren'@q'@>t going to create any instances of |QIODevice| directly,
2115 2118
 subclasses such as |QFile| and |QBuffer| get two additional properties for
2116 2119
 opening and closing the device.
2117 2120
 
@@ -2738,7 +2741,7 @@ QScriptValue QSettings_setValue(QScriptContext *context, QScriptEngine *)
2738 2741
 
2739 2742
 @* Scripting QLCDNumber.
2740 2743
 
2741
-\noindent |QLCDNumber| is used as a base class for \pn{}'s |TemperatureDisplay|
2744
+\noindent |QLCDNumber| is used as a base class for \pn{}'@q'@>s |TemperatureDisplay|
2742 2745
 and |TimerDisplay| classes, but it can also be used on its own for the display
2743 2746
 of mainly numeric information.
2744 2747
 
@@ -3849,7 +3852,7 @@ any {\tt <program>} elements that are immediate children of the
3849 3852
 {\tt <window>} element into a window displayed on screen, the script in the
3850 3853
 {\tt <program>} elements must call a function to display a specified window.
3851 3854
 
3852
-\danger This design works, but it's not particularly good design. It was written
3855
+\danger This design works, but it'@q'@>s not particularly good design. It was written
3853 3856
 under severe time constraints and should be redesigned or at least cleaned up
3854 3857
 and reorganized.\endanger
3855 3858
 
@@ -3942,7 +3945,7 @@ QScriptValue createWindow(QScriptContext *context, QScriptEngine *engine)@/
3942 3945
 @ First we must locate the {\tt <window>} element. The most sensible way to do
3943 3946
 this would require that each {\tt <window>} element has an ID attribute and
3944 3947
 search the DOM tree for that ID. Unfortunately, as of this writing,
3945
-|QDomDocument::elementByID()| always returns a null element, so that won't work.
3948
+|QDomDocument::elementByID()| always returns a null element, so that won'@q'@>t work.
3946 3949
 Instead, we search the tree for all {\tt <window>} elements and then examine
3947 3950
 the resulting list to find the element with the appropriate ID.
3948 3951
 
@@ -4868,7 +4871,7 @@ void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *,@|
4868 4871
 script code would need to alter the column set. While this works fine on a Mac,
4869 4872
 this did not work very well under Windows. For the current version, I would like
4870 4873
 to remove the need to deal with table columns from the host environment. The
4871
-first step for this is allowing column descriptions in XML. After this, I'd like
4874
+first step for this is allowing column descriptions in XML. After this, I'@q'@>d like
4872 4875
 to remove the default column set from the widget code and provide some better
4873 4876
 functionality for dealing with additional data sets.
4874 4877
 
@@ -5772,12 +5775,12 @@ Measurement times are represented as instances of |QTime|.
5772 5775
 
5773 5776
 \noindent Measurements are sent through \pn{} in a way similar to liquid moving
5774 5777
 through a series of connected pipes. \pn{} is not something that you just dump
5775
-measurements on. It's not a big truck\nfnote{Senator Ted Stevens (R-Alaska) on
5778
+measurements on. It'@q'@>s not a big truck\nfnote{Senator Ted Stevens (R-Alaska) on
5776 5779
 network neutrality, June 28, 2006\par
5777 5780
 \hbox{\indent\pdfURL{http://media.publicknowledge.org/stevens-on-nn.mp3}%
5778 5781
 {http://media.publicknowledge.org/stevens-on-nn.mp3}}}. In most cases the
5779 5782
 connections between classes (represented by arrows in Figure \secno) are made
5780
-with Qt's signals and slots mechanism\nfnote{Qt 4.4.3: Signals and Slots\par
5783
+with Qt'@q'@>s signals and slots mechanism\nfnote{Qt 4.4.3: Signals and Slots\par
5781 5784
 \hbox{\indent\pdfURL{http://doc.trolltech.com/4.4/signalsandslots.html}%
5782 5785
 {http://doc.trolltech.com/4.4/signalsandslots.html}}}, but
5783 5786
 these
@@ -6023,7 +6026,7 @@ this program.
6023 6026
 There are two calls to |DAQmxBaseGetExtendedErrorInfo()| to obtain the error
6024 6027
 messages. The first is used just to obtain the length of the error string. That
6025 6028
 length is then used to allocate space for the error message. The second call
6026
-fills that string. This isn't allowed by ISO \CPLUSPLUS/\nfnote{%
6029
+fills that string. This isn'@q'@>t allowed by ISO \CPLUSPLUS/\nfnote{%
6027 6030
 \CPLUSPLUS/Dynamic Arrays (Crowl and Austern, May 16, 2008)\par
6028 6031
 \hbox{\indent\pdfURL{%
6029 6032
 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2648.html}{%
@@ -6399,6 +6402,8 @@ QScriptValue DAQ_newChannel(QScriptContext *context, QScriptEngine *engine)
6399 6402
 	return object;
6400 6403
 }
6401 6404
 
6405
+@i dataqsdk.w
6406
+
6402 6407
 @ Sometimes it can be useful to test other parts of the program (for example,
6403 6408
 when developing new scripts) when the DAQ hardware is not available. In these
6404 6409
 cases, it is possible to temporarily use the |FakeDAQ| class. This class mimics

+ 2
- 0
src/units.w Visa fil

@@ -34,7 +34,9 @@ class Units: public QObject
34 34
 
35 35
 @(units.cpp@>=
36 36
 #include "units.h"
37
+#ifndef Q_OS_WIN32
37 38
 #include "moc_units.cpp"
39
+#endif
38 40
 
39 41
 @ The |isTemperatureUnit()| method may seem counter-intuitive while the enum
40 42
 only contains represenations of temperature measurements, but there are plans

Laddar…
Avbryt
Spara