|
@@ -1,4 +1,4 @@
|
1
|
|
-@** Unsupported Serial Port Devices
|
|
1
|
+@** Unsupported Serial Port Devices.
|
2
|
2
|
|
3
|
3
|
\noindent There are many data acquisition products which connect over or
|
4
|
4
|
present themselves as a serial port and it has become relatively easy for
|
|
@@ -274,9 +274,306 @@ inserter = new NodeInserter(tr("Other Device"), tr("Other Device"),
|
274
|
274
|
"unsupporteddevice", NULL);
|
275
|
275
|
topLevelNodeInserters.append(inserter);
|
276
|
276
|
|
|
277
|
+@ A device abstraction is not strictly required for this feature, however
|
|
278
|
+having one greatly simplifies integrating this feature. At some point I would
|
|
279
|
+like to revise other device abstraction classes so that a huge amount of
|
|
280
|
+boilerplate associated with these can be removed from configuration documents.
|
|
281
|
+
|
|
282
|
+This device abstraction includes features in a few particular categories. First
|
|
283
|
+there are methods that are required for integrating the device with the logging
|
|
284
|
+view. The logging view instantiates the device abstraction, passing in the
|
|
285
|
+configuration data required to properly set up the device. It then is able to
|
|
286
|
+query information about the measurement channels that have been configured for
|
|
287
|
+this device and can set up all of the relevant indicators. Some device classes
|
|
288
|
+may be able to produce annotations, so this class can be treated exactly the
|
|
289
|
+same as any other annotation source. Another requested feature includes the
|
|
290
|
+ability of a device to trigger the start and end of batches, so signals are
|
|
291
|
+provided for this capability. Finally, there are methods associated with
|
|
292
|
+starting and stopping the device. The |start()| method will be called when the
|
|
293
|
+logging view has finished making all of the signal connections. The |stop()|
|
|
294
|
+method will be called when the logging view is closed, giving script code the
|
|
295
|
+chance to cleanly release any resources that must be held for device
|
|
296
|
+communications.
|
|
297
|
+
|
|
298
|
+@<Class declarations@>=
|
|
299
|
+class JavaScriptDevice : public QObject
|
|
300
|
+{
|
|
301
|
+ Q_OBJECT
|
|
302
|
+ public:
|
|
303
|
+ Q_INVOKABLE JavaScriptDevice(const QModelIndex &deviceIndex,
|
|
304
|
+ QScriptEngine *engine);
|
|
305
|
+ Q_INVOKABLE int channelCount();
|
|
306
|
+ Channel* getChannel(int channel);
|
|
307
|
+ Q_INVOKABLE bool isChannelHidden(int channel);
|
|
308
|
+ Q_INVOKABLE Units::Unit expectedChannelUnit(int channel);
|
|
309
|
+ Q_INVOKABLE QString channelColumnName(int channel);
|
|
310
|
+ Q_INVOKABLE QString channelIndicatorText(int channel);
|
|
311
|
+ public slots:
|
|
312
|
+ void setTemperatureColumn(int tcol);
|
|
313
|
+ void setAnnotationColumn(int ncol);
|
|
314
|
+ void start();
|
|
315
|
+ void stop();
|
|
316
|
+ signals:
|
|
317
|
+ void annotation(QString note, int tcol, int ncol);
|
|
318
|
+ void triggerStartBatch();
|
|
319
|
+ void triggerStopBatch();
|
|
320
|
+ void deviceStopRequested();
|
|
321
|
+ private:
|
|
322
|
+ QVariantMap deviceSettings;
|
|
323
|
+ QString deviceScript;
|
|
324
|
+ QList<Channel *> channelList;
|
|
325
|
+ QList<bool> hiddenState;
|
|
326
|
+ QList<Units::Unit> channelUnits;
|
|
327
|
+ QList<QString> columnNames;
|
|
328
|
+ QList<QString> indicatorTexts;
|
|
329
|
+ QList<QVariantMap> channelSettings;
|
|
330
|
+ int annotationTemperatureColumn;
|
|
331
|
+ int annotationNoteColumn;
|
|
332
|
+ QScriptEngine *scriptengine;
|
|
333
|
+};
|
|
334
|
+
|
|
335
|
+@ The |JavaScriptDevice| instance provides two interfaces. Its invokable
|
|
336
|
+methods provide the information needed to integrate script driven devices with
|
|
337
|
+a generic logging view. Additional information is also exposed through the host
|
|
338
|
+environment running the device script. This means that the class requires
|
|
339
|
+knowledge of the host environment, which it obtains through a script function
|
|
340
|
+similar to what is done for window creation.
|
|
341
|
+
|
|
342
|
+The name of the function is generic so this may be easily extended later to
|
|
343
|
+create all device abstraction instances.
|
|
344
|
+
|
|
345
|
+@<Function prototypes for scripting@>=
|
|
346
|
+QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine);
|
|
347
|
+
|
|
348
|
+@ That method is made available to the scripting engine.
|
|
349
|
+
|
|
350
|
+@<Set up the scripting engine@>=
|
|
351
|
+engine->globalObject().setProperty("createDevice",
|
|
352
|
+ engine->newFunction(createDevice));
|
|
353
|
+
|
|
354
|
+@ This function currently creates a |JavaScriptDevice| from a device
|
|
355
|
+configuration node which must be passed through as an argument.
|
|
356
|
+
|
|
357
|
+@<Functions for scripting@>=
|
|
358
|
+QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine)@/
|
|
359
|
+{
|
|
360
|
+ QModelIndex deviceIndex = argument<QModelIndex>(0, context);
|
|
361
|
+ JavaScriptDevice *device = new JavaScriptDevice(deviceIndex, engine);
|
|
362
|
+ QScriptValue object = engine->newQObject(device);
|
|
363
|
+ setQObjectProperties(object, engine);
|
|
364
|
+ object.setProperty("getChannel", engine->newFunction(JavaScriptDevice_getChannel));
|
|
365
|
+ return object;
|
|
366
|
+}
|
|
367
|
+
|
|
368
|
+@ The |start()| method is responsible for preparing the host environment and
|
|
369
|
+executing the device script.
|
|
370
|
+
|
|
371
|
+@<JavaScriptDevice implementation@>=
|
|
372
|
+void JavaScriptDevice::start()
|
|
373
|
+{
|
|
374
|
+ QScriptValue object = scriptengine->newQObject(this);
|
|
375
|
+ QScriptContext *context = scriptengine->currentContext();
|
|
376
|
+ QScriptValue oldThis = context->thisObject();
|
|
377
|
+ context->setThisObject(object);
|
|
378
|
+ QScriptValue result = scriptengine->evaluate(deviceScript);
|
|
379
|
+ QScriptEngine *engine = scriptengine;
|
|
380
|
+ @<Report scripting errors@>@;
|
|
381
|
+ context->setThisObject(oldThis);
|
|
382
|
+}
|
|
383
|
+
|
|
384
|
+@ Currently we require wrapper functions to work with channels in the host
|
|
385
|
+environment.
|
|
386
|
+
|
|
387
|
+@<Function prototypes for scripting@>=
|
|
388
|
+QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine);
|
|
389
|
+
|
|
390
|
+@ The implementation is trivial.
|
|
391
|
+
|
|
392
|
+@<Functions for scripting@>=
|
|
393
|
+QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine)
|
|
394
|
+{
|
|
395
|
+ JavaScriptDevice *self = getself<JavaScriptDevice *>(context);
|
|
396
|
+ QScriptValue object;
|
|
397
|
+ if(self)
|
|
398
|
+ {
|
|
399
|
+ object = engine->newQObject(self->getChannel(argument<int>(0, context)));
|
|
400
|
+ setChannelProperties(object, engine);
|
|
401
|
+ }
|
|
402
|
+ return object;
|
|
403
|
+}
|
|
404
|
+
|
|
405
|
+@ The |stop()| method just fires off a signal that the script can hook into for
|
|
406
|
+any required cleanup.
|
|
407
|
+
|
|
408
|
+@<JavaScriptDevice implementation@>=
|
|
409
|
+void JavaScriptDevice::stop()
|
|
410
|
+{
|
|
411
|
+ emit deviceStopRequested();
|
|
412
|
+}
|
|
413
|
+
|
|
414
|
+@ The constructor is responsible for all boilerplate initialization required
|
|
415
|
+for integrating script defined devices with the logging view.
|
|
416
|
+
|
|
417
|
+Note: At present expected units are assumed to be Fahrenheit. The configuration
|
|
418
|
+widget must be updated to allow at least for control measurements and
|
|
419
|
+eventually support for runtime defined units should also be added.
|
|
420
|
+
|
|
421
|
+@<JavaScriptDevice implementation@>=
|
|
422
|
+JavaScriptDevice::JavaScriptDevice(const QModelIndex &index,
|
|
423
|
+ QScriptEngine *engine) :
|
|
424
|
+ QObject(NULL), scriptengine(engine)
|
|
425
|
+{
|
|
426
|
+ DeviceTreeModel *model = (DeviceTreeModel *)(index.model());
|
|
427
|
+ QDomElement deviceReferenceElement =
|
|
428
|
+ model->referenceElement(model->data(index, Qt::UserRole).toString());
|
|
429
|
+ QDomNodeList deviceConfigData = deviceReferenceElement.elementsByTagName("attribute");
|
|
430
|
+ QDomElement node;
|
|
431
|
+ QStringList deviceKeys;
|
|
432
|
+ QStringList deviceValues;
|
|
433
|
+ for(int i = 0; i < deviceConfigData.size(); i++)
|
|
434
|
+ {
|
|
435
|
+ node = deviceConfigData.at(i).toElement();
|
|
436
|
+ if(node.attribute("name") == "keys")
|
|
437
|
+ {
|
|
438
|
+ QString data = node.attribute("value");
|
|
439
|
+ if(data.length() > 3)
|
|
440
|
+ {
|
|
441
|
+ data.chop(2);
|
|
442
|
+ data = data.remove(0, 2);
|
|
443
|
+ }
|
|
444
|
+ deviceKeys = data.split(", ");
|
|
445
|
+ }
|
|
446
|
+ else if(node.attribute("name") == "values")
|
|
447
|
+ {
|
|
448
|
+ QString data = node.attribute("value");
|
|
449
|
+ if(data.length() > 3)
|
|
450
|
+ {
|
|
451
|
+ data.chop(2);
|
|
452
|
+ data = data.remove(0, 2);
|
|
453
|
+ }
|
|
454
|
+ deviceValues = data.split(", ");
|
|
455
|
+ }
|
|
456
|
+ else if(node.attribute("name") == "script")
|
|
457
|
+ {
|
|
458
|
+ deviceScript = node.attribute("value");
|
|
459
|
+ }
|
|
460
|
+ deviceSettings.insert(node.attribute("name"), node.attribute("value"));
|
|
461
|
+ }
|
|
462
|
+ for(int i = 0; i < qMin(deviceKeys.length(), deviceValues.length()); i++)
|
|
463
|
+ {
|
|
464
|
+ deviceSettings.insert(deviceKeys[i], deviceValues[i]);
|
|
465
|
+ }
|
|
466
|
+ if(model->hasChildren(index))
|
|
467
|
+ {
|
|
468
|
+ for(int i = 0; i < model->rowCount(index); i++)
|
|
469
|
+ {
|
|
470
|
+ QModelIndex channelIndex = model->index(i, 0, index);
|
|
471
|
+ QDomElement channelReference = model->referenceElement(model->data(channelIndex, 32).toString());
|
|
472
|
+ channelList.append(new Channel);
|
|
473
|
+ QDomElement channelReferenceElement =
|
|
474
|
+ model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
|
|
475
|
+ QDomNodeList channelConfigData =
|
|
476
|
+ channelReferenceElement.elementsByTagName("attribute");
|
|
477
|
+ QStringList channelKeys;
|
|
478
|
+ QStringList channelValues;
|
|
479
|
+ for(int j = 0; j < channelConfigData.size(); j++)
|
|
480
|
+ {
|
|
481
|
+ node = channelConfigData.at(i).toElement();
|
|
482
|
+ if(node.attribute("name") == "keys")
|
|
483
|
+ {
|
|
484
|
+ QString data = node.attribute("value");
|
|
485
|
+ if(data.length() > 3)
|
|
486
|
+ {
|
|
487
|
+ data.chop(2);
|
|
488
|
+ data = data.remove(0, 2);
|
|
489
|
+ }
|
|
490
|
+ channelKeys = data.split(", ");
|
|
491
|
+ }
|
|
492
|
+ else if(node.attribute("name") == "values")
|
|
493
|
+ {
|
|
494
|
+ QString data = node.attribute("value");
|
|
495
|
+ if(data.length() > 3)
|
|
496
|
+ {
|
|
497
|
+ data.chop(2);
|
|
498
|
+ data = data.remove(0, 2);
|
|
499
|
+ }
|
|
500
|
+ channelValues = data.split(", ");
|
|
501
|
+ }
|
|
502
|
+ else if(node.attribute("nane") == "hidden")
|
|
503
|
+ {
|
|
504
|
+ hiddenState.append(node.attribute("value") == "true");
|
|
505
|
+ }
|
|
506
|
+ else if(node.attribute("name") == "columnname")
|
|
507
|
+ {
|
|
508
|
+ columnNames.append(node.attribute("value"));
|
|
509
|
+ }
|
|
510
|
+ }
|
|
511
|
+ QVariantMap cs;
|
|
512
|
+ for(int j = 0; j < qMin(channelKeys.length(), channelValues.length()); j++)
|
|
513
|
+ {
|
|
514
|
+ cs.insert(channelKeys[j], channelValues[j]);
|
|
515
|
+ }
|
|
516
|
+ channelSettings.append(cs);
|
|
517
|
+ indicatorTexts.append(model->data(channelIndex, Qt::DisplayRole).toString());
|
|
518
|
+ channelUnits.append(Units::Fahrenheit);
|
|
519
|
+ }
|
|
520
|
+ }
|
|
521
|
+}
|
|
522
|
+
|
|
523
|
+@ Several methods are available to query information about the configured
|
|
524
|
+channels.
|
|
525
|
+
|
|
526
|
+@<JavaScriptDevice implementation@>=
|
|
527
|
+int JavaScriptDevice::channelCount()
|
|
528
|
+{
|
|
529
|
+ return channelList.length();
|
|
530
|
+}
|
|
531
|
+
|
|
532
|
+Channel* JavaScriptDevice::getChannel(int channel)
|
|
533
|
+{
|
|
534
|
+ return channelList.at(channel);
|
|
535
|
+}
|
|
536
|
+
|
|
537
|
+bool JavaScriptDevice::isChannelHidden(int channel)
|
|
538
|
+{
|
|
539
|
+ return hiddenState.at(channel);
|
|
540
|
+}
|
|
541
|
+
|
|
542
|
+Units::Unit JavaScriptDevice::expectedChannelUnit(int channel)
|
|
543
|
+{
|
|
544
|
+ return channelUnits.at(channel);
|
|
545
|
+}
|
|
546
|
+
|
|
547
|
+QString JavaScriptDevice::channelColumnName(int channel)
|
|
548
|
+{
|
|
549
|
+ if(channel >= 0 && channel < columnNames.length())
|
|
550
|
+ {
|
|
551
|
+ return columnNames.at(channel);
|
|
552
|
+ }
|
|
553
|
+ return QString();
|
|
554
|
+}
|
|
555
|
+
|
|
556
|
+QString JavaScriptDevice::channelIndicatorText(int channel)
|
|
557
|
+{
|
|
558
|
+ return indicatorTexts.at(channel);
|
|
559
|
+}
|
|
560
|
+
|
|
561
|
+@ Two slots are provided for controlling the placement of annotations.
|
|
562
|
+
|
|
563
|
+@<JavaScriptDevice implementation@>=
|
|
564
|
+void JavaScriptDevice::setTemperatureColumn(int tcol)
|
|
565
|
+{
|
|
566
|
+ annotationTemperatureColumn = tcol;
|
|
567
|
+}
|
|
568
|
+
|
|
569
|
+void JavaScriptDevice::setAnnotationColumn(int ncol)
|
|
570
|
+{
|
|
571
|
+ annotationNoteColumn = ncol;
|
|
572
|
+}
|
|
573
|
+
|
277
|
574
|
@ At present, implementations are not broken out to a separate file. This
|
278
|
575
|
should be changed at some point.
|
279
|
576
|
|
280
|
577
|
@<Class implementations@>=
|
281
|
578
|
@<UnsupportedSerialDeviceConfWidget implementation@>
|
282
|
|
-
|
|
579
|
+@<JavaScriptDevice implementation@>
|