Browse Source

Initial script device support. WIP

Neal Wilson 10 years ago
parent
commit
68b4dcb416
2 changed files with 336 additions and 2 deletions
  1. 37
    0
      config/Windows/productionroaster.xml
  2. 299
    2
      src/unsupportedserial.w

+ 37
- 0
config/Windows/productionroaster.xml View File

@@ -68,6 +68,7 @@
68 68
 		var annotationButtons = new Array();
69 69
 		var nidevices = new Array();
70 70
 		var dataqsdkdevices = new Array();
71
+		var jsdevices = new Array();
71 72
 		var temperatureDisplays = new Array();
72 73
 		var columnNames = new Array();
73 74
 		var modbusdevices = new Array();
@@ -356,6 +357,36 @@
356 357
 						tabControls.push(outputControl);
357 358
 					}
358 359
 				}
360
+				else if(driverReference.driver == "unsupporteddevice")
361
+				{
362
+					var device = createDevice(driverIndex);
363
+					for(var j = 0; j < device.channelCount(); j++) {
364
+						channels.push(device.getChannel(j));
365
+						columnNames.push(device.channelColumnName(j));
366
+						var unit = device.expectedChannelUnit(j);
367
+						if(unit == Units.Fahrenheit) {
368
+							channelType.push("T");
369
+						} else {
370
+							channelType.push("C");
371
+						}
372
+						if(device.isChannelHidden(j)) {
373
+							channelVisibility.push(false);
374
+						} else {
375
+							channelVisibility.push(true);
376
+							var indicator = new TemperatureDisplay;
377
+							if(unit == Units.Unitless) {
378
+								indicator.setDisplayUnits(Units.Unitless);
379
+								indicator.digitCount = 6;
380
+							}
381
+							temperatureDisplays.push(indicator);
382
+							var decorator = new WidgetDecorator(indicator, device.channelIndicatorText(j), 2);
383
+							device.getChannel(j).newData.connect(indicator.setValue);
384
+							indicatorPanel.addWidget(decorator);
385
+						}
386
+					}
387
+					device.start();
388
+					jsdevices.push(device);
389
+				}
359 390
 				else if(driverReference.driver == "annotationbutton")
360 391
 				{
361 392
 					var button = new AnnotationButton(driverReference.buttontext);
@@ -598,9 +629,15 @@
598 629
 			{
599 630
 				dataqsdkdevices[i].deleteLater();
600 631
 			}
632
+			for(var i = 0; i < jsdevices.length; i++)
633
+			{
634
+				jsdevices[i].stop();
635
+				jsdevices[i].deleteLater();
636
+			}
601 637
 			delete nidevices;
602 638
 			delete modbusdevices;
603 639
 			delete dataqsdkdevices;
640
+			delete jsdevices;
604 641
             window.saveSizeAndPosition("window");
605 642
             vsplit.saveState("script/mainSplitter");
606 643
             isplit.saveState("script/instrumentSplitter");

+ 299
- 2
src/unsupportedserial.w View File

@@ -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@>

Loading…
Cancel
Save