Browse Source

Merge branch 'development' into sample-roasting

Neal Wilson 11 years ago
parent
commit
579dd2ad8e
9 changed files with 1512 additions and 91 deletions
  1. 1
    1
      config/Windows/navigation.xml
  2. 41
    0
      config/Windows/newbatch.xml
  3. 152
    53
      config/Windows/productionroaster.xml
  4. 6
    2
      src/Typica.pro
  5. 205
    7
      src/dataqsdk.w
  6. 498
    0
      src/scales.w
  7. 104
    26
      src/typica.w
  8. 216
    2
      src/units.w
  9. 289
    0
      src/valueannotation.w

+ 1
- 1
config/Windows/navigation.xml View File

160
             if(typeof(window.loggingWindow) == "undefined")
160
             if(typeof(window.loggingWindow) == "undefined")
161
             {
161
             {
162
                 window.loggingWindow = createWindow("basicWindow");
162
                 window.loggingWindow = createWindow("basicWindow");
163
-                window.loggingWindow.windowTitle = "Typica";
163
+                window.loggingWindow.windowTitle = "Typica [*]";
164
 				window.loggingWindow.navigationWindow = window;
164
 				window.loggingWindow.navigationWindow = window;
165
             }
165
             }
166
             else
166
             else

+ 41
- 0
config/Windows/newbatch.xml View File

2
     <menu name="Batch">
2
     <menu name="Batch">
3
         <item id="new" shortcut="Ctrl+N">New Batch…</item>
3
         <item id="new" shortcut="Ctrl+N">New Batch…</item>
4
     </menu>
4
     </menu>
5
+	<layout type="horizontal">
5
     <layout type="vertical">
6
     <layout type="vertical">
6
         <layout type="horizontal">
7
         <layout type="horizontal">
7
 			<label>Machine:</label>
8
 			<label>Machine:</label>
53
             <button name="Save log as target profile" type="check" id="target" />
54
             <button name="Save log as target profile" type="check" id="target" />
54
         </layout>
55
         </layout>
55
     </layout>
56
     </layout>
57
+	<layout type="vertical">
58
+		<label>Connected Scales</label>
59
+		<layout type="vertical" id="scales" />
60
+		<stretch />
61
+	</layout>
62
+	</layout>
56
     <program>
63
     <program>
57
         <![CDATA[
64
         <![CDATA[
58
 			var unitBox = findChildObject(this, 'unit');
65
 			var unitBox = findChildObject(this, 'unit');
77
             var roasted = findChildObject(this, 'roasted');
84
             var roasted = findChildObject(this, 'roasted');
78
             var roastwt = findChildObject(this, 'roast');
85
             var roastwt = findChildObject(this, 'roast');
79
             roastwt.maximumWidth = 80;
86
             roastwt.maximumWidth = 80;
87
+			var scalesLayout = findChildObject(this, 'scales');
88
+			scalesLayout.spacing = 10;
89
+			if(navigationwindow.loggingWindow.scales.length > 0) {
90
+				for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
91
+					var scale = navigationwindow.loggingWindow.scales[i];
92
+					var label = new DragLabel();
93
+					var weighButton = new QPushButton();
94
+					weighButton.text = "Weigh";
95
+					weighButton.clicked.connect(scale.weigh);
96
+					label.updateMeasurement = function(m, u) {
97
+						switch(unitBox.currentIndex) {
98
+							case 0:
99
+								this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
100
+								break;
101
+							case 1:
102
+								this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
103
+								break;
104
+							case 2:
105
+								this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
106
+								break;
107
+							case 3:
108
+								this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
109
+								break;
110
+						}
111
+					};
112
+					scalesLayout.addWidget(label);
113
+					scalesLayout.addWidget(weighButton);
114
+					scale.newMeasurement.connect(function(m, u) {
115
+						label.updateMeasurement(m, u);
116
+					});
117
+					scale.weigh();
118
+					unitBox['currentIndexChanged(int)'].connect(scale.weigh);
119
+				}
120
+			}
80
 			model.dataChanged.connect(function() {
121
 			model.dataChanged.connect(function() {
81
 				green.text = table.columnSum(1, 0);
122
 				green.text = table.columnSum(1, 0);
82
 				table.resizeColumnToContents(0);
123
 				table.resizeColumnToContents(0);

+ 152
- 53
config/Windows/productionroaster.xml View File

75
 		var rateoffsets = new Array();
75
 		var rateoffsets = new Array();
76
 		var ratezeros = new Array();
76
 		var ratezeros = new Array();
77
 		var channelType = new Array();
77
 		var channelType = new Array();
78
+		window.scales = new Array();
79
+		var channelVisibility = new Array();
78
 		var indicatorPanel = findChildObject(this, 'indicators');
80
 		var indicatorPanel = findChildObject(this, 'indicators');
79
 		var annotationPanel = findChildObject(this, 'controlpanel');
81
 		var annotationPanel = findChildObject(this, 'controlpanel');
80
 		var log = findChildObject(this, 'log');
82
 		var log = findChildObject(this, 'log');
153
 											channelType.push("T");
155
 											channelType.push("T");
154
 											columnNames.push(channelReference.columnname);
156
 											columnNames.push(channelReference.columnname);
155
 											DAQChannels++;
157
 											DAQChannels++;
156
-											var indicator = new TemperatureDisplay;
157
-											temperatureDisplays.push(indicator);
158
-											var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
159
-											channel.newData.connect(indicator.setValue);
160
-											indicatorPanel.addWidget(decorator);
158
+											if(channelReference.hidden == "true") {
159
+												channelVisibility.push(false);
160
+											} else {
161
+												channelVisibility.push(true);
162
+												var indicator = new TemperatureDisplay;
163
+												temperatureDisplays.push(indicator);
164
+												var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
165
+												channel.newData.connect(indicator.setValue);
166
+												indicatorPanel.addWidget(decorator);
167
+											}
161
 										}
168
 										}
162
 									}
169
 									}
163
 									switch(DAQChannels) {
170
 									switch(DAQChannels) {
216
 								channels.push(channel);
223
 								channels.push(channel);
217
 								channelType.push("T");
224
 								channelType.push("T");
218
 								columnNames.push(deviceReference.columnname);
225
 								columnNames.push(deviceReference.columnname);
219
-								var indicator = new TemperatureDisplay;
220
-								temperatureDisplays.push(indicator);
221
-								var decorator = new WidgetDecorator(indicator, configModel.data(deviceIndex, 0), 2);
222
-								channel.newData.connect(indicator.setValue);
223
-								indicatorPanel.addWidget(decorator);
226
+								if(deviceReference.hidden == "true") {
227
+									channelVisibility.push(false);
228
+								} else {
229
+									channelVisibility.push(true);
230
+									var indicator = new TemperatureDisplay;
231
+									temperatureDisplays.push(indicator);
232
+									var decorator = new WidgetDecorator(indicator, configModel.data(deviceIndex, 0), 2);
233
+									channel.newData.connect(indicator.setValue);
234
+									indicatorPanel.addWidget(decorator);
235
+								}
224
 								device.start();
236
 								device.start();
225
 								nidevices.push(device);
237
 								nidevices.push(device);
226
 							}
238
 							}
267
 							channel.newData.connect(calibrator.newMeasurement)
279
 							channel.newData.connect(calibrator.newMeasurement)
268
 							channels.push(calibrator);
280
 							channels.push(calibrator);
269
 							columnNames.push(channelReference.column);
281
 							columnNames.push(channelReference.column);
270
-							var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
271
-							calibrator.newData.connect(indicator.setValue);
272
-							indicatorPanel.addWidget(decorator);
282
+							if(channelReference.hidden == "true") {
283
+								channelVisibility.push(false);
284
+							} else {
285
+								channelVisibility.push(true);
286
+								var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
287
+								calibrator.newData.connect(indicator.setValue);
288
+								indicatorPanel.addWidget(decorator);
289
+							}
273
 						}
290
 						}
274
 						sampleRate /= configModel.rowCount(driverIndex);
291
 						sampleRate /= configModel.rowCount(driverIndex);
275
 					}
292
 					}
285
 					channels.push(pvchannel);
302
 					channels.push(pvchannel);
286
 					channelType.push("T");
303
 					channelType.push("T");
287
 					columnNames.push(driverReference.pvcolname);
304
 					columnNames.push(driverReference.pvcolname);
288
-					var indicator = new TemperatureDisplay;
289
-					temperatureDisplays.push(indicator);
290
-					var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " PV", 2);
291
-					pvchannel.newData.connect(indicator.setValue);
292
-					indicatorPanel.addWidget(decorator);
305
+					if(driverReference.pvhidden == "true") {
306
+						channelVisibility.push(false);
307
+					} else {
308
+						channelVisibility.push(true);
309
+						var indicator = new TemperatureDisplay;
310
+						temperatureDisplays.push(indicator);
311
+						var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " PV", 2);
312
+						pvchannel.newData.connect(indicator.setValue);
313
+						indicatorPanel.addWidget(decorator);
314
+					}
293
 					if(driverReference.sVEnabled == "true")
315
 					if(driverReference.sVEnabled == "true")
294
 					{
316
 					{
295
 						var svchannel = device.sVChannel();
317
 						var svchannel = device.sVChannel();
296
 						channels.push(svchannel);
318
 						channels.push(svchannel);
297
 						channelType.push("C");
319
 						channelType.push("C");
298
 						columnNames.push(driverReference.svcolname);
320
 						columnNames.push(driverReference.svcolname);
299
-						var indicator = new TemperatureDisplay;
300
-						temperatureDisplays.push(indicator);
301
-						var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " SV", 2);
302
-						svchannel.newData.connect(indicator.setValue);
303
-						indicatorPanel.addWidget(decorator);
321
+						if(driverReference.svhidden == "true") {
322
+							channelVisibility.push(false);
323
+						} else {
324
+							channelVisibility.push(true);
325
+							var indicator = new TemperatureDisplay;
326
+							temperatureDisplays.push(indicator);
327
+							var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " SV", 2);
328
+							svchannel.newData.connect(indicator.setValue);
329
+							indicatorPanel.addWidget(decorator);
330
+						}
304
 					}
331
 					}
305
 					if(driverReference.sVWritable == "true")
332
 					if(driverReference.sVWritable == "true")
306
 					{
333
 					{
336
 					annotationPanel.addWidget(button);
363
 					annotationPanel.addWidget(button);
337
 					tabControls.push(button);
364
 					tabControls.push(button);
338
 				}
365
 				}
366
+				else if(driverReference.driver == "valueannotation")
367
+				{
368
+					var checker = new ValueAnnotation;
369
+					var valuesSetting = driverReference.measuredValues;
370
+					var notesSetting = driverReference.annotations;
371
+					var valuesList = valuesSetting.slice(2, valuesSetting.length-2).split(",");
372
+					var notesList = notesSetting.slice(2, notesSetting.length-2).split(",");
373
+					if(valuesList.length > 1 && notesList.length == valuesList.length) {
374
+						for(var j = 0; j < valuesList.length; j++) {
375
+							checker.setAnnotation(Number(valuesList[j]), notesList[j]);
376
+						}
377
+					}
378
+					if(driverReference.emitOnStart == "true") {
379
+						start.clicked.connect(checker.annotate);
380
+					}
381
+					var colname = driverReference.source;
382
+					for(var j = 0; j < columnNames.length; j++) {
383
+						if(columnNames[j] == colname) {
384
+							channels[j].newData.connect(checker.newMeasurement);
385
+							break;
386
+						}
387
+					}
388
+					annotationButtons.push(checker);
389
+				}
339
 				else if(driverReference.driver == "reconfigurablebutton")
390
 				else if(driverReference.driver == "reconfigurablebutton")
340
 				{
391
 				{
341
 					var button = new AnnotationButton(driverReference.buttontext);
392
 					var button = new AnnotationButton(driverReference.buttontext);
405
 								channels[j].newData.connect(calibrator.newMeasurement);
456
 								channels[j].newData.connect(calibrator.newMeasurement);
406
 								calibrator.newData.connect(indicator.setValue);
457
 								calibrator.newData.connect(indicator.setValue);
407
 								channels.push(calibrator);
458
 								channels.push(calibrator);
459
+								// Channel hiding is not yet configurable for this.
460
+								// I'm not sure if there's any need to allow this
461
+								// to be hidden at present.
462
+								channelVisibility.push(true);
408
 								channelType.push(channelType[j]);
463
 								channelType.push(channelType[j]);
409
 								columnNames.push(driverReference.destination);
464
 								columnNames.push(driverReference.destination);
410
 								indicatorPanel.addWidget(decorator);
465
 								indicatorPanel.addWidget(decorator);
483
 							rate.newData.connect(indicator.setValue);
538
 							rate.newData.connect(indicator.setValue);
484
 							temperatureDisplays.push(indicator);
539
 							temperatureDisplays.push(indicator);
485
 							channels.push(rate);
540
 							channels.push(rate);
541
+							// This cannot at present be configured to be
542
+							// hidden.
543
+							channelVisibility.push(true);
486
 							channelType.push("T");
544
 							channelType.push("T");
487
 							columnNames.push(configModel.data(driverIndex, 0));
545
 							columnNames.push(configModel.data(driverIndex, 0));
488
 						}
546
 						}
489
 					}
547
 					}
490
 				}
548
 				}
549
+				else if(driverReference.driver == "scale")
550
+				{
551
+					var scale = new SerialScale(driverReference.port);
552
+					scale.setDataBits(8);
553
+					scale.setBaudRate(driverReference.baudrate);
554
+					scale.setParity(driverReference.parity);
555
+					scale.setStopBits(driverReference.stopbits);
556
+					scale.setFlowControl(driverReference.flowcontrol);
557
+					scale.open(3);
558
+					window.scales.push(scale);
559
+				}
491
 			}
560
 			}
492
 		}
561
 		}
493
 		for(var i = 1; i < tabControls.length; i++)
562
 		for(var i = 1; i < tabControls.length; i++)
495
 			setTabOrder(tabControls[i-1], tabControls[i]);
564
 			setTabOrder(tabControls[i-1], tabControls[i]);
496
 		}
565
 		}
497
 		log.setHeaderData(0, "Time");
566
 		log.setHeaderData(0, "Time");
567
+		var channelSkip = 0;
498
 		for(var i = 0; i < columnNames.length; i++) {
568
 		for(var i = 0; i < columnNames.length; i++) {
499
-			log.setHeaderData(i + 1, columnNames[i]);
500
-		}
501
-		log.setHeaderData(columnNames.length + 1, "Note");
502
-		for(var i = 0; i < channels.length; i++) {
503
-			log.addToCurrentColumnSet(i + 1);
569
+			if(channelVisibility[i]) {
570
+				log.setHeaderData(i + 1 - channelSkip, columnNames[i]);
571
+				log.addToCurrentColumnSet(i + 1 - channelSkip);
572
+			}
573
+			else {
574
+				channelSkip++;
575
+			}
504
 		}
576
 		}
577
+		log.setHeaderData(columnNames.length + 1 - channelSkip, "Note");
505
 		var timer = new TimerDisplay;
578
 		var timer = new TimerDisplay;
506
 		timer.displayFormat = "mm:ss";
579
 		timer.displayFormat = "mm:ss";
507
 		timer.autoReset = true;
580
 		timer.autoReset = true;
542
 		var offsets = new Array();
615
 		var offsets = new Array();
543
 		var zeroemitters = new Array();
616
 		var zeroemitters = new Array();
544
 		var adapters = new Array();
617
 		var adapters = new Array();
618
+		channelSkip = 0;
545
 		for(var i = 0; i < channels.length; i++) {
619
 		for(var i = 0; i < channels.length; i++) {
546
-			var offset = new MeasurementTimeOffset(epoch);
547
-			offsets.push(offset);
548
-			channels[i].newData.connect(offset.newMeasurement);
549
-			var adapter = new MeasurementAdapter(i + 1);
550
-			adapters.push(adapter);
551
-			offset.measurement.connect(adapter.newMeasurement);
552
-			var emitter = new ZeroEmitter(i + 1);
553
-			zeroemitters.push(emitter);
554
-			channels[i].newData.connect(emitter.newMeasurement);
555
-			emitter.measurement.connect(log.newMeasurement);
556
-			emitter.measurement.connect(graph.newMeasurement);
620
+			if(channelVisibility[i]) {
621
+				var offset = new MeasurementTimeOffset(epoch);
622
+				offsets.push(offset);
623
+				channels[i].newData.connect(offset.newMeasurement);
624
+				var adapter = new MeasurementAdapter(i + 1 - channelSkip);
625
+				adapters.push(adapter);
626
+				offset.measurement.connect(adapter.newMeasurement);
627
+				var emitter = new ZeroEmitter(i + 1);
628
+				zeroemitters.push(emitter);
629
+				channels[i].newData.connect(emitter.newMeasurement);
630
+				emitter.measurement.connect(log.newMeasurement);
631
+				emitter.measurement.connect(graph.newMeasurement);
632
+			} else {
633
+				channelSkip++;
634
+			}
557
 		}
635
 		}
636
+		var offsetForChannel = function(c) {
637
+			var retval = -1;
638
+			for(var i = 0; i <= c; i++) {
639
+				if(channelVisibility[i]) {
640
+					retval++;
641
+				}
642
+			}
643
+			return retval;
644
+		};
558
         start.clicked.connect(function() {
645
         start.clicked.connect(function() {
559
 			start.enabled = false;
646
 			start.enabled = false;
560
 			hasTranslated = false;
647
 			hasTranslated = false;
585
             }
672
             }
586
 			if(translationChannel >= 0)
673
 			if(translationChannel >= 0)
587
 			{
674
 			{
588
-				offsets[translationChannel].measurement.connect(currentDetector.newMeasurement);
675
+				offsets[offsetForChannel(translationChannel)].measurement.connect(currentDetector.newMeasurement);
589
 			}
676
 			}
590
 			if(typeof(externtrans) != 'undefined') {
677
 			if(typeof(externtrans) != 'undefined') {
591
 				externtrans.display(0);
678
 				externtrans.display(0);
599
 			{
686
 			{
600
 				annotationButtons[i].annotation.connect(log.newAnnotation);
687
 				annotationButtons[i].annotation.connect(log.newAnnotation);
601
 				annotationButtons[i].setTemperatureColumn(1);
688
 				annotationButtons[i].setTemperatureColumn(1);
602
-				annotationButtons[i].setAnnotationColumn(channels.length + 1);
689
+				annotationButtons[i].setAnnotationColumn(channels.length - channelSkip + 1);
603
 				annotationButtons[i].annotation.connect(function(note, tcol, ncol) {
690
 				annotationButtons[i].annotation.connect(function(note, tcol, ncol) {
604
 					for(var i = tcol; i < ncol; i++) {
691
 					for(var i = tcol; i < ncol; i++) {
605
 						log.newAnnotation(note, i, ncol);
692
 						log.newAnnotation(note, i, ncol);
611
         stop.annotation.connect(log.newAnnotation);
698
         stop.annotation.connect(log.newAnnotation);
612
         stop.clicked.connect(timer.stopTimer);
699
         stop.clicked.connect(timer.stopTimer);
613
 		stop.setTemperatureColumn(1);
700
 		stop.setTemperatureColumn(1);
614
-		stop.setAnnotationColumn(channels.length + 1);
701
+		stop.setAnnotationColumn(channels.length - channelSkip + 1);
615
 		QSettings.setValue("liveColumn", 1);
702
 		QSettings.setValue("liveColumn", 1);
616
         var lc = 1;
703
         var lc = 1;
617
         stop.clicked.connect(function() {
704
         stop.clicked.connect(function() {
647
             }
734
             }
648
 			if(translationChannel >= 0)
735
 			if(translationChannel >= 0)
649
 			{
736
 			{
650
-				offsets[translationChannel].measurement.disconnect(currentDetector.newMeasurement);
737
+				offsets[offsetForChannel(translationChannel)].measurement.disconnect(currentDetector.newMeasurement);
651
 			}
738
 			}
652
 			start.enabled = true;
739
 			start.enabled = true;
653
 			window.windowModified = false;
740
 			window.windowModified = false;
764
 				log.newAnnotation("End", 1, lc);
851
 				log.newAnnotation("End", 1, lc);
765
             }
852
             }
766
         });
853
         });
854
+		var columnName = function(c) {
855
+			var visibleColumns = 0;
856
+			for(var i = 0; i < channels.length; i++) {
857
+				if(channelVisibility[i]) {
858
+					visibleColumns++;
859
+				}
860
+				if(c == visibleColumns - 1) {
861
+					return columnNames[i];
862
+				}
863
+			}
864
+			return "";
865
+		};
767
 		window.postLoadColumnSetup = function(c) {
866
 		window.postLoadColumnSetup = function(c) {
768
 			for(var i = 0; i < adapters.length; i++)
867
 			for(var i = 0; i < adapters.length; i++)
769
 			{
868
 			{
770
 				adapters[i].setColumn(c + i + 1);
869
 				adapters[i].setColumn(c + i + 1);
771
 				zeroemitters[i].setColumn(c + i + 1);
870
 				zeroemitters[i].setColumn(c + i + 1);
772
-				log.setHeaderData(c + i + 1, columnNames[i]);
871
+				log.setHeaderData(c + i + 1, columnName(i));
773
 			}
872
 			}
774
-			log.setHeaderData(c + columnNames.length + 1, "Note");
873
+			log.setHeaderData(c + columnNames.length + 1 - channelSkip, "Note");
775
             stop.setTemperatureColumn(c + 1);
874
             stop.setTemperatureColumn(c + 1);
776
-			stop.setAnnotationColumn(c + columnNames.length + 1);
875
+			stop.setAnnotationColumn(c + columnNames.length + 1 - channelSkip);
777
 			for(var i = 0; i < annotationButtons.length; i++)
876
 			for(var i = 0; i < annotationButtons.length; i++)
778
 			{
877
 			{
779
 				annotationButtons[i].setTemperatureColumn(c + 1);
878
 				annotationButtons[i].setTemperatureColumn(c + 1);
780
-				annotationButtons[i].setAnnotationColumn(c + columnNames.length + 1);
879
+				annotationButtons[i].setAnnotationColumn(c + columnNames.length + 1 - channelSkip);
781
 			}
880
 			}
782
 			log.clearCurrentColumnSet();
881
 			log.clearCurrentColumnSet();
783
-			for(var i = 0; i < channels.length; i++) {
882
+			for(var i = 0; i < channels.length - channelSkip; i++) {
784
 				log.addToCurrentColumnSet(c + i + 1);
883
 				log.addToCurrentColumnSet(c + i + 1);
785
 			}
884
 			}
786
 			window.firstTempColumn = c + 1;
885
 			window.firstTempColumn = c + 1;
787
-			window.annotationColumn = c + columnNames.length + 1;
886
+			window.annotationColumn = c + columnNames.length - channelSkip + 1;
788
 		}
887
 		}
789
 		var saveMenu = findChildObject(this, 'save');
888
 		var saveMenu = findChildObject(this, 'save');
790
         saveMenu.triggered.connect(function() {
889
         saveMenu.triggered.connect(function() {
793
                 var lc = Number(QSettings.value("liveColumn"));
892
                 var lc = Number(QSettings.value("liveColumn"));
794
                 var file = new QFile(filename);
893
                 var file = new QFile(filename);
795
                 log.clearOutputColumns();
894
                 log.clearOutputColumns();
796
-				for(var i = 0; i < columnNames.length; i++)
895
+				for(var i = 0; i < columnNames.length - channelSkip; i++)
797
 				{
896
 				{
798
 					if(channelType[i] == "T") {
897
 					if(channelType[i] == "T") {
799
 						log.addOutputTemperatureColumn(lc + i);
898
 						log.addOutputTemperatureColumn(lc + i);
802
 						log.addOutputControlColumn(lc + i);
901
 						log.addOutputControlColumn(lc + i);
803
 					}
902
 					}
804
 				}
903
 				}
805
-				log.addOutputAnnotationColumn(lc + columnNames.length);
904
+				log.addOutputAnnotationColumn(lc + columnNames.length - channelSkip);
806
                 log.saveXML(file);
905
                 log.saveXML(file);
807
                 QSettings.setValue("script/lastDir", dir(filename));
906
                 QSettings.setValue("script/lastDir", dir(filename));
808
             }
907
             }
814
                 var lc = Number(QSettings.value("liveColumn"));
913
                 var lc = Number(QSettings.value("liveColumn"));
815
                 var file = new QFile(filename);
914
                 var file = new QFile(filename);
816
                 log.clearOutputColumns();
915
                 log.clearOutputColumns();
817
-				for(var i = 0; i < columnNames.length; i++)
916
+				for(var i = 0; i < columnNames.length - channelSkip; i++)
818
 				{
917
 				{
819
 					log.addOutputTemperatureColumn(lc + i);
918
 					log.addOutputTemperatureColumn(lc + i);
820
 				}
919
 				}
821
-				log.addOutputAnnotationColumn(lc + columnNames.length);
920
+				log.addOutputAnnotationColumn(lc + columnNames.length - channelSkip);
822
                 log.saveCSV(file);
921
                 log.saveCSV(file);
823
                 QSettings.setValue("script/lastDir", dir(filename));
922
                 QSettings.setValue("script/lastDir", dir(filename));
824
             }
923
             }

+ 6
- 2
src/Typica.pro View File

21
     abouttypica.h \
21
     abouttypica.h \
22
     units.h \
22
     units.h \
23
     webview.h \
23
     webview.h \
24
-    webelement.h
24
+    webelement.h \
25
+    scale.h \
26
+    draglabel.h
25
 SOURCES += typica.cpp \
27
 SOURCES += typica.cpp \
26
     helpmenu.cpp \
28
     helpmenu.cpp \
27
     abouttypica.cpp \
29
     abouttypica.cpp \
28
     units.cpp \
30
     units.cpp \
29
     webview.cpp \
31
     webview.cpp \
30
-    webelement.cpp
32
+    webelement.cpp \
33
+    scale.cpp \
34
+    draglabel.cpp
31
 
35
 
32
 RESOURCES += \
36
 RESOURCES += \
33
     resources.qrc
37
     resources.qrc

+ 205
- 7
src/dataqsdk.w View File

825
 		void startCalibration();
825
 		void startCalibration();
826
 		void stopCalibration();
826
 		void stopCalibration();
827
 		void resetCalibration();
827
 		void resetCalibration();
828
+		void updateInput(Measurement measure);
829
+		void updateOutput(Measurement measure);
830
+		void updateHidden(bool hidden);
831
+	private:
832
+		QPushButton *startButton;
833
+		QPushButton *resetButton;
834
+		QPushButton *stopButton;
835
+		@<DATAQ SDK device settings@>@;
836
+		DataqSdkDevice *calibrationDevice;
837
+		LinearCalibrator *calibrator;
838
+		QLineEdit *currentMeasurement;
839
+		QLineEdit *minimumMeasurement;
840
+		QLineEdit *maximumMeasurement;
841
+		QLineEdit *averageMeasurement;
842
+		QLineEdit *currentMapped;
843
+		QLineEdit *minimumMapped;
844
+		QLineEdit *maximumMapped;
845
+		QLineEdit *averageMapped;
846
+		int rmCount;
847
+		int cmCount;
848
+		double rmin;
849
+		double rmax;
850
+		double rmean;
851
+		double cmin;
852
+		double cmax;
853
+		double cmean;
828
 };
854
 };
829
 
855
 
856
+@ Private members that hold minimum and maximum aggregate data for channel
857
+calibration will be initialized to the maximum and minimum values available for
858
+the |double| type respectively. This guarantees that the first measurement will
859
+overwrite these values. This is done with |std::numeric_limits| so we require a
860
+header to be included to gain access to this.
861
+
862
+@<Header files to include@>=
863
+#include <limits>
864
+
830
 @ The constructor sets up the interface. Calibration settings line edits need
865
 @ The constructor sets up the interface. Calibration settings line edits need
831
 to have numeric validators added.
866
 to have numeric validators added.
832
 
867
 
833
 @<DataqSdkDeviceConfWidget implementation@>=
868
 @<DataqSdkDeviceConfWidget implementation@>=
834
 DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
869
 DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
835
                                                      const QModelIndex &index)
870
                                                      const QModelIndex &index)
836
-	: BasicDeviceConfigurationWidget(model, index)
871
+	: BasicDeviceConfigurationWidget(model, index),
872
+	startButton(new QPushButton(tr("Start"))),
873
+	resetButton(new QPushButton(tr("Reset"))),
874
+	stopButton(new QPushButton(tr("Stop"))),
875
+	calibrator(new LinearCalibrator),
876
+	currentMeasurement(new QLineEdit), minimumMeasurement(new QLineEdit),
877
+	maximumMeasurement(new QLineEdit), averageMeasurement(new QLineEdit),
878
+	currentMapped(new QLineEdit), minimumMapped(new QLineEdit),
879
+	maximumMapped(new QLineEdit), averageMapped(new QLineEdit),
880
+	rmCount(0), cmCount(0),
881
+	rmin(std::numeric_limits<double>::max()),
882
+	rmax(std::numeric_limits<double>::min()), rmean(0),
883
+	cmin(std::numeric_limits<double>::max()),
884
+	cmax(std::numeric_limits<double>::min()), cmean(0)
837
 {
885
 {
886
+	@<Find DATAQ SDK device settings from parent node@>@;
887
+	resetButton->setEnabled(false);
888
+	stopButton->setEnabled(false);
889
+	connect(startButton, SIGNAL(clicked()), this, SLOT(startCalibration()));
890
+	connect(resetButton, SIGNAL(clicked()), this, SLOT(resetCalibration()));
891
+	connect(stopButton, SIGNAL(clicked()), this, SLOT(stopCalibration()));
838
 	QVBoxLayout *layout = new QVBoxLayout;
892
 	QVBoxLayout *layout = new QVBoxLayout;
839
 	QFormLayout *topLayout = new QFormLayout;
893
 	QFormLayout *topLayout = new QFormLayout;
840
 	QLineEdit *columnEdit = new QLineEdit;
894
 	QLineEdit *columnEdit = new QLineEdit;
846
 	QCheckBox *smoothingBox = new QCheckBox(tr("Enable smoothing"));
900
 	QCheckBox *smoothingBox = new QCheckBox(tr("Enable smoothing"));
847
 	topLayout->addRow(smoothingBox);
901
 	topLayout->addRow(smoothingBox);
848
 	layout->addLayout(topLayout);
902
 	layout->addLayout(topLayout);
903
+	QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
904
+	topLayout->addRow(hideSeries);
849
 	QLabel *calibrationLabel = new QLabel(tr("Calibration settings"));
905
 	QLabel *calibrationLabel = new QLabel(tr("Calibration settings"));
850
 	layout->addWidget(calibrationLabel);
906
 	layout->addWidget(calibrationLabel);
907
+	QHBoxLayout *calibrationLayout = new QHBoxLayout;
851
 	QFormLayout *calibrationControlsLayout = new QFormLayout;
908
 	QFormLayout *calibrationControlsLayout = new QFormLayout;
852
 	QLineEdit *measuredLowerEdit = new QLineEdit;
909
 	QLineEdit *measuredLowerEdit = new QLineEdit;
853
 	measuredLowerEdit->setText("0");
910
 	measuredLowerEdit->setText("0");
866
 	QLineEdit *sensitivityEdit = new QLineEdit;
923
 	QLineEdit *sensitivityEdit = new QLineEdit;
867
 	sensitivityEdit->setText("0");
924
 	sensitivityEdit->setText("0");
868
 	calibrationControlsLayout->addRow(tr("Discrete interval skip"), sensitivityEdit);
925
 	calibrationControlsLayout->addRow(tr("Discrete interval skip"), sensitivityEdit);
869
-	layout->addLayout(calibrationControlsLayout);
870
-	
871
-	// Insert another panel to assist in determining proper calibration values.
872
-	
926
+	QVBoxLayout *calibrationTestLayout = new QVBoxLayout;
927
+	QHBoxLayout *deviceControlLayout = new QHBoxLayout;
928
+	deviceControlLayout->addWidget(startButton);
929
+	deviceControlLayout->addWidget(resetButton);
930
+	deviceControlLayout->addWidget(stopButton);
931
+	QFormLayout *indicatorLayout = new QFormLayout;	
932
+	currentMeasurement->setReadOnly(true);
933
+	minimumMeasurement->setReadOnly(true);
934
+	maximumMeasurement->setReadOnly(true);
935
+	averageMeasurement->setReadOnly(true);
936
+	currentMapped->setReadOnly(true);
937
+	minimumMapped->setReadOnly(true);
938
+	maximumMapped->setReadOnly(true);
939
+	averageMapped->setReadOnly(true);
940
+	indicatorLayout->addRow(tr("Measured Values"), new QWidget);
941
+	indicatorLayout->addRow(tr("Current"), currentMeasurement);
942
+	indicatorLayout->addRow(tr("Minimum"), minimumMeasurement);
943
+	indicatorLayout->addRow(tr("Maximum"), maximumMeasurement);
944
+	indicatorLayout->addRow(tr("Mean"), averageMeasurement);
945
+	indicatorLayout->addRow(tr("Mapped Values"), new QWidget);
946
+	indicatorLayout->addRow(tr("Current Mapped"), currentMapped);
947
+	indicatorLayout->addRow(tr("Minimum Mapped"), minimumMapped);
948
+	indicatorLayout->addRow(tr("Maximum Mapped"), maximumMapped);
949
+	indicatorLayout->addRow(tr("Mean Mapped"), averageMapped);
950
+	calibrationTestLayout->addLayout(deviceControlLayout);
951
+	calibrationTestLayout->addLayout(indicatorLayout);
952
+	calibrationLayout->addLayout(calibrationControlsLayout);
953
+	calibrationLayout->addLayout(calibrationTestLayout);
954
+	layout->addLayout(calibrationLayout);
873
 	@<Get device configuration data for current node@>@;
955
 	@<Get device configuration data for current node@>@;
874
 	for(int i = 0; i < configData.size(); i++)
956
 	for(int i = 0; i < configData.size(); i++)
875
 	{
957
 	{
910
 		{
992
 		{
911
 			sensitivityEdit->setText(node.attribute("value"));
993
 			sensitivityEdit->setText(node.attribute("value"));
912
 		}
994
 		}
995
+		else if(node.attribute("name") == "hidden")
996
+		{
997
+			hideSeries->setChecked(node.attribute("value") == "true");
998
+		}
913
 	}
999
 	}
914
 	updateColumnName(columnEdit->text());
1000
 	updateColumnName(columnEdit->text());
915
 	updateUnits(unitSelector->currentText());
1001
 	updateUnits(unitSelector->currentText());
920
 	updateMappedUpper(mappedUpperEdit->text());
1006
 	updateMappedUpper(mappedUpperEdit->text());
921
 	updateClosedInterval(closedBox->isChecked());
1007
 	updateClosedInterval(closedBox->isChecked());
922
 	updateSensitivity(sensitivityEdit->text());
1008
 	updateSensitivity(sensitivityEdit->text());
1009
+	updateHidden(hideSeries->isChecked());
923
 	connect(columnEdit, SIGNAL(textChanged(QString)),
1010
 	connect(columnEdit, SIGNAL(textChanged(QString)),
924
 	        this, SLOT(updateColumnName(QString)));
1011
 	        this, SLOT(updateColumnName(QString)));
925
 	connect(unitSelector, SIGNAL(currentIndexChanged(QString)),
1012
 	connect(unitSelector, SIGNAL(currentIndexChanged(QString)),
938
 	        this, SLOT(updateClosedInterval(bool)));
1025
 	        this, SLOT(updateClosedInterval(bool)));
939
 	connect(sensitivityEdit, SIGNAL(textChanged(QString)),
1026
 	connect(sensitivityEdit, SIGNAL(textChanged(QString)),
940
 	        this, SLOT(updateSensitivity(QString)));
1027
 	        this, SLOT(updateSensitivity(QString)));
1028
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
941
 	setLayout(layout);
1029
 	setLayout(layout);
942
 }
1030
 }
943
 
1031
 
960
 void DataqSdkChannelConfWidget::updateMeasuredLower(const QString &value)
1048
 void DataqSdkChannelConfWidget::updateMeasuredLower(const QString &value)
961
 {
1049
 {
962
 	updateAttribute("calibrationMeasuredLower", value);
1050
 	updateAttribute("calibrationMeasuredLower", value);
1051
+	calibrator->setMeasuredLower(value.toDouble());
963
 }
1052
 }
964
 
1053
 
965
 void DataqSdkChannelConfWidget::updateMeasuredUpper(const QString &value)
1054
 void DataqSdkChannelConfWidget::updateMeasuredUpper(const QString &value)
966
 {
1055
 {
967
 	updateAttribute("calibrationMeasuredUpper", value);
1056
 	updateAttribute("calibrationMeasuredUpper", value);
1057
+	calibrator->setMeasuredUpper(value.toDouble());
968
 }
1058
 }
969
 
1059
 
970
 void DataqSdkChannelConfWidget::updateMappedLower(const QString &value)
1060
 void DataqSdkChannelConfWidget::updateMappedLower(const QString &value)
971
 {
1061
 {
972
 	updateAttribute("calibrationMappedLower", value);
1062
 	updateAttribute("calibrationMappedLower", value);
1063
+	calibrator->setMappedLower(value.toDouble());
973
 }
1064
 }
974
 
1065
 
975
 void DataqSdkChannelConfWidget::updateMappedUpper(const QString &value)
1066
 void DataqSdkChannelConfWidget::updateMappedUpper(const QString &value)
976
 {
1067
 {
977
 	updateAttribute("calibrationMappedUpper", value);
1068
 	updateAttribute("calibrationMappedUpper", value);
1069
+	calibrator->setMappedUpper(value.toDouble());
978
 }
1070
 }
979
 
1071
 
980
 void DataqSdkChannelConfWidget::updateClosedInterval(bool closed)
1072
 void DataqSdkChannelConfWidget::updateClosedInterval(bool closed)
981
 {
1073
 {
982
 	updateAttribute("calibrationClosedInterval", closed ? "true" : "false");
1074
 	updateAttribute("calibrationClosedInterval", closed ? "true" : "false");
1075
+	calibrator->setClosedRange(closed);
983
 }
1076
 }
984
 
1077
 
985
 void DataqSdkChannelConfWidget::updateSmoothingEnabled(bool enabled)
1078
 void DataqSdkChannelConfWidget::updateSmoothingEnabled(bool enabled)
990
 void DataqSdkChannelConfWidget::updateSensitivity(const QString &value)
1083
 void DataqSdkChannelConfWidget::updateSensitivity(const QString &value)
991
 {
1084
 {
992
 	updateAttribute("calibrationSensitivity", value);
1085
 	updateAttribute("calibrationSensitivity", value);
1086
+	calibrator->setSensitivity(value.toDouble());
1087
+}
1088
+
1089
+void DataqSdkChannelConfWidget::updateHidden(bool hidden)
1090
+{
1091
+	updateAttribute("hidden", hidden ? "true" : "false");
1092
+}
1093
+
1094
+@ When calibrating a device, we must know certain information to open a
1095
+connection to the appropriate hardware and know which channel we are interested
1096
+in.
1097
+
1098
+@<DATAQ SDK device settings@>=
1099
+bool autoSelect;
1100
+QString deviceID;
1101
+int channelOfInterest;
1102
+
1103
+@ This information is accessed through the reference element associated with
1104
+the parent node of the current configuration and from the row number of the
1105
+current node.
1106
+
1107
+@<Find DATAQ SDK device settings from parent node@>=
1108
+QDomElement parentReference = model->referenceElement(model->data(index.parent(), Qt::UserRole).toString());
1109
+QDomNodeList deviceConfigData = parentReference.elementsByTagName("attribute");
1110
+QDomElement deviceNode;
1111
+QString configPort;
1112
+QString configAuto;
1113
+for(int i = 0; i < deviceConfigData.size(); i++)
1114
+{
1115
+	deviceNode = deviceConfigData.at(i).toElement();
1116
+	if(deviceNode.attribute("name") == "autoSelect")
1117
+	{
1118
+		autoSelect = (deviceNode.attribute("value") == "true");
1119
+	}
1120
+	else if(deviceNode.attribute("name") == "deviceNumber")
1121
+	{
1122
+		configAuto = deviceNode.attribute("value");
1123
+	}
1124
+	else if(deviceNode.attribute("name") == "port")
1125
+	{
1126
+		configPort = deviceNode.attribute("value");
1127
+	}
993
 }
1128
 }
1129
+deviceID = autoSelect ? configAuto : configPort;
1130
+channelOfInterest = index.row();
994
 
1131
 
995
 @ It must be possible to perform calibration operations with the hardware not
1132
 @ It must be possible to perform calibration operations with the hardware not
996
 connected. As such, the device should only be opened on request. Methods for
1133
 connected. As such, the device should only be opened on request. Methods for
999
 @<DataqSdkDeviceConfWidget implementation@>=
1136
 @<DataqSdkDeviceConfWidget implementation@>=
1000
 void DataqSdkChannelConfWidget::startCalibration()
1137
 void DataqSdkChannelConfWidget::startCalibration()
1001
 {
1138
 {
1002
-
1139
+	startButton->setEnabled(false);
1140
+	stopButton->setEnabled(true);
1141
+	resetButton->setEnabled(true);
1142
+	calibrationDevice = new DataqSdkDevice(deviceID);
1143
+	Channel *channel;
1144
+	for(int i = 0; i <= channelOfInterest; i++)
1145
+	{
1146
+		channel = calibrationDevice->newChannel(Units::Unitless);
1147
+	}
1148
+	connect(channel, SIGNAL(newData(Measurement)), this, SLOT(updateInput(Measurement)));
1149
+	connect(channel, SIGNAL(newData(Measurement)), calibrator, SLOT(newMeasurement(Measurement)));
1150
+	connect(calibrator, SIGNAL(newData(Measurement)), this, SLOT(updateOutput(Measurement)));
1151
+	calibrationDevice->setClockRate(6.0 / (1.0 + channelOfInterest));
1152
+	calibrationDevice->start();
1003
 }
1153
 }
1004
 
1154
 
1005
 void DataqSdkChannelConfWidget::stopCalibration()
1155
 void DataqSdkChannelConfWidget::stopCalibration()
1006
 {
1156
 {
1007
-
1157
+	startButton->setEnabled(true);
1158
+	stopButton->setEnabled(false);
1159
+	resetButton->setEnabled(false);
1160
+	calibrationDevice->deleteLater();
1161
+	@<Reset DATAQ SDK channel calibration aggregates@>@;
1008
 }
1162
 }
1009
 
1163
 
1010
 @ When collecting calibration data it is useful to have a few types of
1164
 @ When collecting calibration data it is useful to have a few types of
1018
 @<DataqSdkDeviceConfWidget implementation@>=
1172
 @<DataqSdkDeviceConfWidget implementation@>=
1019
 void DataqSdkChannelConfWidget::resetCalibration()
1173
 void DataqSdkChannelConfWidget::resetCalibration()
1020
 {
1174
 {
1175
+	@<Reset DATAQ SDK channel calibration aggregates@>@;
1176
+}
1177
+
1178
+@ When calibration is stopped or reset, aggregate statistics are set to
1179
+their initial values;
1180
+
1181
+@<Reset DATAQ SDK channel calibration aggregates@>=
1182
+rmCount = 0;
1183
+cmCount = 0;
1184
+rmin = std::numeric_limits<double>::max();
1185
+rmax = std::numeric_limits<double>::min();
1186
+rmean = 0;
1187
+cmin = std::numeric_limits<double>::max();
1188
+cmax = std::numeric_limits<double>::min();
1189
+cmean = 0;
1190
+
1191
+@ Two methods are responsible for updating line edits with current and
1192
+aggregate data when calibrating a channel. One handles raw measurements from
1193
+the channel and the other handles output from the |LinearCalibrator|.
1194
+
1195
+@<DataqSdkDeviceConfWidget implementation@>=
1196
+void DataqSdkChannelConfWidget::updateInput(Measurement measure)
1197
+{
1198
+	double nv = measure.temperature();
1199
+	currentMeasurement->setText(QString("%1").arg(nv));
1200
+	rmin = qMin(nv, rmin);
1201
+	minimumMeasurement->setText(QString("%1").arg(rmin));
1202
+	rmax = qMax(nv, rmax);
1203
+	maximumMeasurement->setText(QString("%1").arg(rmax));
1204
+	rmean = ((rmean * rmCount) + nv) / (rmCount + 1);
1205
+	rmCount++;
1206
+	averageMeasurement->setText(QString("%1").arg(rmean));
1207
+}
1021
 
1208
 
1209
+void DataqSdkChannelConfWidget::updateOutput(Measurement measure)
1210
+{
1211
+	double nv = measure.temperature();
1212
+	currentMapped->setText(QString("%1").arg(nv));
1213
+	cmin = qMin(nv, cmin);
1214
+	minimumMapped->setText(QString("%1").arg(cmin));
1215
+	cmax = qMax(nv, cmax);
1216
+	maximumMapped->setText(QString("%1").arg(cmax));
1217
+	cmean = ((cmean * cmCount) + nv) / (cmCount + 1);
1218
+	cmCount++;
1219
+	averageMapped->setText(QString("%1").arg(cmean));
1022
 }
1220
 }
1023
 
1221
 
1024
 @ Column name is handled as usual.
1222
 @ Column name is handled as usual.

+ 498
- 0
src/scales.w View File

1
+@** Collecting Measurements from Scales.
2
+
3
+\noindent When a computer connected scale is available, it can be useful to
4
+eliminate manual transcription from the data entry for batches. In general it
5
+is difficult to determine where a measurement should go automatically, but
6
+there are a number of situations where the ability to drag a measurement from a
7
+label and drop it to whatever input widget is appropriate would be a useful
8
+operation to support.
9
+
10
+To support this, we need to subclass |QLabel| to allow it to initiate a drag
11
+and drop operation.
12
+
13
+@(draglabel.h@>=
14
+#ifndef TypicaDragLabelInclude
15
+#define TypicaDragLabelInclude
16
+
17
+#include <QLabel>
18
+
19
+class DragLabel : public QLabel
20
+{
21
+	Q_OBJECT
22
+	public:
23
+		explicit DragLabel(const QString &labelText, QWidget *parent = NULL);
24
+	protected:
25
+		void mousePressEvent(QMouseEvent *event);
26
+};
27
+
28
+#endif
29
+
30
+@ The font size of the label is increased by default to make it easier to
31
+manipulate on a touch screen. Otherwise, there is little to do in this class.
32
+
33
+@(draglabel.cpp@>=
34
+#include "draglabel.h"
35
+
36
+#include <QDrag>
37
+#include <QMouseEvent>
38
+
39
+DragLabel::DragLabel(const QString &labelText, QWidget *parent) :
40
+	QLabel(labelText, parent)
41
+{
42
+	QFont labelFont = font();
43
+	labelFont.setPointSize(14);
44
+	setFont(labelFont);
45
+}
46
+
47
+void DragLabel::mousePressEvent(QMouseEvent *event)
48
+{
49
+	if(event->button() == Qt::LeftButton)
50
+	{
51
+		QDrag *drag = new QDrag(this);
52
+		QMimeData *mimeData = new QMimeData;
53
+		mimeData->setText(text());
54
+		drag->setMimeData(mimeData);
55
+		drag->exec();
56
+	}
57
+}
58
+
59
+@ We require the ability to create these labels from the host environment.
60
+First we include the appropriate header.
61
+
62
+@<Header files to include@>=
63
+#include "draglabel.h"
64
+
65
+@ Next, a pair of function prototypes.
66
+
67
+@<Function prototypes for scripting@>=
68
+QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine);
69
+void setDragLabelProperties(QScriptValue value, QScriptEngine *engine);
70
+
71
+@ These are made known to the host environment as usual.
72
+
73
+@<Set up the scripting engine@>=
74
+constructor = engine->newFunction(constructDragLabel);
75
+value = engine->newQMetaObject(&DragLabel::staticMetaObject, constructor);
76
+engine->globalObject().setProperty("DragLabel", value);
77
+
78
+@ The implementation is trivial.
79
+
80
+@<Functions for scripting@>=
81
+QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine)
82
+{
83
+	QScriptValue object;
84
+	QString labelText = "";
85
+	if(context->argumentCount() == 1)
86
+	{
87
+		labelText = argument<QString>(0, context);
88
+	}
89
+	object = engine->newQObject(new DragLabel(labelText));
90
+	setDragLabelProperties(object, engine);
91
+	return object;
92
+}
93
+
94
+void setDragLabelProperties(QScriptValue value, QScriptEngine *engine)
95
+{
96
+	setQLabelProperties(value, engine);
97
+}
98
+
99
+@ An object is also required to communicate with a scale. This is responsible
100
+for setting up a connection over a serial port, sending commands out to the
101
+scale, buffering and interpreting the response, and signaling new measurements.
102
+
103
+@(scale.h@>=
104
+#ifndef TypicaScaleInclude
105
+#define TypicaScaleInclude
106
+
107
+#include "3rdparty/qextserialport/src/qextserialport.h"
108
+#include "units.h"
109
+
110
+class SerialScale : public QextSerialPort
111
+{
112
+	Q_OBJECT
113
+	public:
114
+		SerialScale(const QString &port);
115
+	public slots:
116
+		void tare();
117
+		void weigh();
118
+	signals:
119
+		void newMeasurement(double weight, Units::Unit unit);
120
+	private slots:
121
+		void dataAvailable();
122
+	private:
123
+		QByteArray responseBuffer;
124
+};
125
+
126
+#endif
127
+
128
+@ The constructor tells the port that this should be event driven and connects
129
+a signal to buffer data..
130
+
131
+@(scale.cpp@>=
132
+#include "scale.h"
133
+#include <QStringList>
134
+
135
+SerialScale::SerialScale(const QString &port) :
136
+	QextSerialPort(port, QextSerialPort::EventDriven)
137
+{
138
+	connect(this, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
139
+}
140
+
141
+@ The |dataAvailable| method handles buffering incoming data and processing
142
+responses when they have come in. Serial port communications are likely to be
143
+very slow in comparison to everything else so it is likely that only one
144
+character will come in at a time.
145
+
146
+Note that this currently only understands single line output and a limited
147
+selection of units.
148
+
149
+@(scale.cpp@>=
150
+void SerialScale::dataAvailable()
151
+{
152
+	responseBuffer.append(readAll());
153
+	if(responseBuffer.contains("\x0D"))
154
+	{
155
+		if(responseBuffer.contains("!"))
156
+		{
157
+			responseBuffer.clear();
158
+		}
159
+		else
160
+		{
161
+			@<Process weight measurement@>@;
162
+			responseBuffer.clear();
163
+		}
164
+	}
165
+}
166
+
167
+@ Each line of data consists of an optional sign character possibly followed
168
+by a space followed by characters representing a number followed by a
169
+space followed by characters indicating a unit. This may be preceeded and
170
+followed by a variable amount of white space. To process a new measurement, we
171
+must remove the excess white space, split the number from the unit, prepend the
172
+sign to the number if it is present, convert the string representing the number
173
+to a numeric type, and determine which unit the measurement is in.
174
+
175
+\medskip
176
+
177
+\settabs 8 \columns
178
+\+&&&{\tt |"lb"|}&|Units::Pound|\cr
179
+\+&&&{\tt |"kg"|}&|Units::Kilogram|\cr
180
+\+&&&{\tt |"g"|}&|Units::Gram|\cr
181
+\+&&&{\tt |"oz"|}&|Units::Ounce|\cr
182
+
183
+\smallskip
184
+
185
+\centerline{Table \secno: Unit Strings and Representative Unit Enumeration}
186
+
187
+\medskip
188
+
189
+@<Process weight measurement@>=
190
+QStringList responseParts = QString(responseBuffer.simplified()).split(' ');
191
+if(responseParts.size() > 2)
192
+{
193
+	responseParts.removeFirst();
194
+	responseParts.replace(0, QString("-%1").arg(responseParts[0]));
195
+}
196
+double weight = responseParts[0].toDouble();
197
+Units::Unit unit = Units::Unitless;
198
+if(responseParts[1] == "lb")
199
+{
200
+	unit = Units::Pound;
201
+}
202
+else if(responseParts[1] == "kg")
203
+{
204
+	unit = Units::Kilogram;
205
+}
206
+else if(responseParts[1] == "g")
207
+{
208
+	unit = Units::Gram;
209
+}
210
+else if(responseParts[1] == "oz")
211
+{
212
+	unit = Units::Ounce;
213
+}
214
+emit newMeasurement(weight, unit);
215
+
216
+@ Two methods are used to send commands to the scale. I am unsure of how well
217
+standardized remote key operation of scales are. The class may need to be
218
+extended to support more devices.
219
+
220
+@(scale.cpp@>=
221
+void SerialScale::tare()
222
+{
223
+	write("!KT\x0D");
224
+}
225
+
226
+void SerialScale::weigh()
227
+{
228
+	write("!KP\x0D");
229
+}
230
+
231
+@ This must be available to the host environment.
232
+
233
+@<Function prototypes for scripting@>=
234
+QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine);
235
+void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine);
236
+
237
+@ These functions are made known to the scripting engine in the usual way.
238
+
239
+@<Set up the scripting engine@>=
240
+constructor = engine->newFunction(constructSerialScale);
241
+value = engine->newQMetaObject(&SerialScale::staticMetaObject, constructor);
242
+engine->globalObject().setProperty("SerialScale", value);
243
+
244
+@ If we are to set up the serial ports from the host environment, a few
245
+enumerated types must be made known to the meta-object system.
246
+
247
+@<Class declarations@>=
248
+Q_DECLARE_METATYPE(BaudRateType)
249
+Q_DECLARE_METATYPE(DataBitsType)
250
+Q_DECLARE_METATYPE(ParityType)
251
+Q_DECLARE_METATYPE(StopBitsType)
252
+Q_DECLARE_METATYPE(FlowType)
253
+
254
+@ For each of these, a pair of functions converts values to script values and
255
+back. This is a very annoying aspect of the version of QextSerialPort currently
256
+used by \pn{}.
257
+
258
+@<Function prototypes for scripting@>=
259
+QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value);
260
+void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value);
261
+QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value);
262
+void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value);
263
+QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value);
264
+void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value);
265
+QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value);
266
+void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value);
267
+QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value);
268
+void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value);
269
+
270
+@ These are implemented thusly.
271
+
272
+@<Functions for scripting@>=
273
+QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value)
274
+{
275
+	return engine->newVariant(QVariant((int)(value)));
276
+}
277
+
278
+void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value)
279
+{
280
+	value = (BaudRateType)(sv.toVariant().toInt());
281
+}
282
+
283
+QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value)
284
+{
285
+	return engine->newVariant(QVariant((int)(value)));
286
+}
287
+
288
+void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value)
289
+{
290
+	value = (DataBitsType)(sv.toVariant().toInt());
291
+}
292
+
293
+QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value)
294
+{
295
+	return engine->newVariant(QVariant((int)(value)));
296
+}
297
+
298
+void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value)
299
+{
300
+	value = (ParityType)(sv.toVariant().toInt());
301
+}
302
+
303
+QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value)
304
+{
305
+	return engine->newVariant(QVariant((int)(value)));
306
+}
307
+
308
+void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value)
309
+{
310
+	value = (StopBitsType)(sv.toVariant().toInt());
311
+}
312
+
313
+QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value)
314
+{
315
+	return engine->newVariant(QVariant((int)(value)));
316
+}
317
+
318
+void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value)
319
+{
320
+	value = (FlowType)(sv.toVariant().toInt());
321
+}
322
+
323
+@ These conversion functions are then registered.
324
+
325
+@<Set up the scripting engine@>=
326
+qScriptRegisterMetaType(engine, BaudRateType_toScriptValue, BaudRateType_fromScriptValue);
327
+qScriptRegisterMetaType(engine, DataBitsType_toScriptValue, DataBitsType_fromScriptValue);
328
+qScriptRegisterMetaType(engine, ParityType_toScriptValue, ParityType_fromScriptValue);
329
+qScriptRegisterMetaType(engine, StopBitsType_toScriptValue, StopBitsType_fromScriptValue);
330
+qScriptRegisterMetaType(engine, FlowType_toScriptValue, FlowType_fromScriptValue);
331
+
332
+@ In order to make this class available to the host environment, we must also
333
+include the appropriate header file.
334
+
335
+@<Header files to include@>=
336
+#include "scale.h"
337
+
338
+@ Most of the properties of interest should be added automatically, however
339
+there are non-slot methods in |QIODevice| that we require.
340
+
341
+@<Functions for scripting@>=
342
+void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine)
343
+{
344
+	setQIODeviceProperties(value, engine);
345
+}
346
+
347
+@ The script constructor should seem familiar.
348
+
349
+@<Functions for scripting@>=
350
+QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine)
351
+{
352
+	QScriptValue object;
353
+	if(context->argumentCount() == 1)
354
+	{
355
+		object = engine->newQObject(new SerialScale(argument<QString>(0, context)));
356
+		setSerialScaleProperties(object, engine);
357
+	}
358
+	else
359
+	{
360
+		context->throwError("Incorrect number of arguments passed to "
361
+		                    "SerialScale. The constructor takes one string "
362
+		                    "as an argument specifying a port name.");
363
+	}
364
+	return object;
365
+}
366
+
367
+@ In order to allow configuration of scales from within \pn{}, a configuration
368
+widget must be provided.
369
+
370
+@<Class declarations@>=
371
+class SerialScaleConfWidget : public BasicDeviceConfigurationWidget
372
+{
373
+	Q_OBJECT
374
+	public:
375
+		Q_INVOKABLE SerialScaleConfWidget(DeviceTreeModel *model,
376
+		                                  const QModelIndex &index);
377
+	private slots:
378
+		void updatePort(const QString &newPort);
379
+		void updateBaudRate(const QString &rate);
380
+		void updateParity(int index);
381
+		void updateFlowControl(int index);
382
+		void updateStopBits(int index);
383
+	private:
384
+		PortSelector *port;
385
+		BaudSelector *baud;
386
+		ParitySelector *parity;
387
+		FlowSelector *flow;
388
+		StopSelector *stop;
389
+};
390
+
391
+@ This is very similar to other configuration widgets.
392
+
393
+@<SerialScaleConfWidget implementation@>=
394
+SerialScaleConfWidget::SerialScaleConfWidget(DeviceTreeModel *model,
395
+                                             const QModelIndex &index)
396
+: BasicDeviceConfigurationWidget(model, index),
397
+  port(new PortSelector), baud(new BaudSelector), parity(new ParitySelector),
398
+  flow(new FlowSelector), stop(new StopSelector)
399
+{
400
+	QFormLayout *layout = new QFormLayout;
401
+	layout->addRow(tr("Port:"), port);
402
+	connect(port, SIGNAL(currentIndexChanged(QString)),
403
+	        this, SLOT(updatePort(QString)));
404
+	connect(port, SIGNAL(editTextChanged(QString)),
405
+	        this, SLOT(updatePort(QString)));
406
+	layout->addRow(tr("Baud:"), baud);
407
+	connect(baud, SIGNAL(currentIndexChanged(QString)),
408
+	        this, SLOT(updateBaudRate(QString)));
409
+	layout->addRow(tr("Parity:"), parity);
410
+	connect(parity, SIGNAL(currentIndexChanged(int)),
411
+	        this, SLOT(updateParity(int)));
412
+	layout->addRow(tr("Flow Control:"), flow);
413
+	connect(flow, SIGNAL(currentIndexChanged(int)),
414
+	        this, SLOT(updateFlowControl(int)));
415
+	layout->addRow(tr("Stop Bits:"), stop);
416
+	connect(stop, SIGNAL(currentIndexChanged(int)),
417
+	        this, SLOT(updateStopBits(int)));
418
+	@<Get device configuration data for current node@>@;
419
+	for(int i = 0; i < configData.size(); i++)
420
+	{
421
+		node = configData.at(i).toElement();
422
+		if(node.attribute("name") == "port")
423
+		{
424
+			int j = port->findText(node.attribute("value"));
425
+			if(j >= 0)
426
+			{
427
+				port->setCurrentIndex(j);
428
+			}
429
+			else
430
+			{
431
+				port->insertItem(0, node.attribute("value"));
432
+				port->setCurrentIndex(0);
433
+			}
434
+		}
435
+		else if(node.attribute("name") == "baudrate")
436
+		{
437
+			baud->setCurrentIndex(baud->findText(node.attribute("value")));
438
+		}
439
+		else if(node.attribute("name") == "parity")
440
+		{
441
+			parity->setCurrentIndex(parity->findData(node.attribute("value")));
442
+		}
443
+		else if(node.attribute("name") == "flowcontrol")
444
+		{
445
+			flow->setCurrentIndex(flow->findData(node.attribute("value")));
446
+		}
447
+		else if(node.attribute("name") == "stopbits")
448
+		{
449
+			stop->setCurrentIndex(stop->findData(node.attribute("value")));
450
+		}
451
+	}
452
+	updatePort(port->currentText());
453
+	updateBaudRate(baud->currentText());
454
+	updateParity(parity->currentIndex());
455
+	updateFlowControl(flow->currentIndex());
456
+	updateStopBits(stop->currentIndex());
457
+	setLayout(layout);
458
+}
459
+
460
+@ Update methods are the same as were used in |ModbusRtuPortConfWidget|.
461
+
462
+@<SerialScaleConfWidget implementation@>=
463
+void SerialScaleConfWidget::updatePort(const QString &newPort)
464
+{
465
+	updateAttribute("port", newPort);
466
+}
467
+
468
+void SerialScaleConfWidget::updateBaudRate(const QString &rate)
469
+{
470
+	updateAttribute("baudrate", rate);
471
+}
472
+
473
+void SerialScaleConfWidget::updateParity(int index)
474
+{
475
+	updateAttribute("parity", parity->itemData(index).toString());
476
+}
477
+
478
+void SerialScaleConfWidget::updateFlowControl(int index)
479
+{
480
+	updateAttribute("flowcontrol", flow->itemData(index).toString());
481
+}
482
+
483
+void SerialScaleConfWidget::updateStopBits(int index)
484
+{
485
+	updateAttribute("stopbits", stop->itemData(index).toString());
486
+}
487
+
488
+@ The configuration widget is registered with the configuration system.
489
+
490
+@<Register device configuration widgets@>=
491
+app.registerDeviceConfigurationWidget("scale", SerialScaleConfWidget::staticMetaObject);
492
+
493
+@ A |NodeInserter| is also added.
494
+
495
+@<Register top level device configuration nodes@>=
496
+inserter = new NodeInserter(tr("Serial Scale"), tr("Scale"), "scale", NULL);
497
+topLevelNodeInserters.append(inserter);
498
+

+ 104
- 26
src/typica.w View File

839
 @<SettingsWindow implementation@>@/
839
 @<SettingsWindow implementation@>@/
840
 @<GraphSettingsWidget implementation@>@/
840
 @<GraphSettingsWidget implementation@>@/
841
 @<DataqSdkDeviceConfWidget implementation@>@/
841
 @<DataqSdkDeviceConfWidget implementation@>@/
842
+@<SerialScaleConfWidget implementation@>@/
843
+@<ValueAnnotation implementation@>@/
844
+@<ValueAnnotationConfWidget implementation@>@/
842
 
845
 
843
 @ A few headers are required for various parts of \pn{}. These allow the use of
846
 @ A few headers are required for various parts of \pn{}. These allow the use of
844
 various Qt modules.
847
 various Qt modules.
976
 	return qscriptvalue_cast<QModelIndex>(context->argument(arg));
979
 	return qscriptvalue_cast<QModelIndex>(context->argument(arg));
977
 }
980
 }
978
 
981
 
982
+template<> double argument(int arg, QScriptContext *context)
983
+{
984
+	return (double)(context->argument(arg).toNumber());
985
+}
986
+
987
+template<> Units::Unit argument(int arg, QScriptContext *context)
988
+{
989
+	return (Units::Unit)(context->argument(arg).toInt32());
990
+}
991
+
979
 @ The scripting engine is informed of a number of classes defined elsewhere in
992
 @ The scripting engine is informed of a number of classes defined elsewhere in
980
 the program. Code related to scripting these classes is grouped with the code
993
 the program. Code related to scripting these classes is grouped with the code
981
 implementing the classes. Additionally, there are several classes from Qt which
994
 implementing the classes. Additionally, there are several classes from Qt which
10006
 r = cseconds - oseconds;
10019
 r = cseconds - oseconds;
10007
 
10020
 
10008
 @ The logic for a count down timer is very similar to the logic for a count up
10021
 @ The logic for a count down timer is very similar to the logic for a count up
10009
-timer. A key difference is that we don't want to continue counting down if the
10022
+timer. A key difference is that we don'@q'@>t want to continue counting down if the
10010
 timer has already reached 0.
10023
 timer has already reached 0.
10011
 
10024
 
10012
 @<Check for Timer Decrement@>=
10025
 @<Check for Timer Decrement@>=
10066
 }
10079
 }
10067
 
10080
 
10068
 @ Stopping the timer is a little simpler. Remember to stop the clock so we
10081
 @ Stopping the timer is a little simpler. Remember to stop the clock so we
10069
-aren't updating senselessly.
10082
+aren'@q'@>t updating senselessly.
10070
 
10083
 
10071
 @<TimerDisplay Implementation@>=
10084
 @<TimerDisplay Implementation@>=
10072
 void TimerDisplay::stopTimer()@t\2\2@>@/
10085
 void TimerDisplay::stopTimer()@t\2\2@>@/
11971
 
11984
 
11972
 The end of this function may seem a little strange. Why not simply look up the
11985
 The end of this function may seem a little strange. Why not simply look up the
11973
 map and insert information directly into the model data? Well, as of this
11986
 map and insert information directly into the model data? Well, as of this
11974
-writing, that doesn't work. There are two ways around that problem. One is to
11987
+writing, that doesn'@q'@>t work. There are two ways around that problem. One is to
11975
 have the lists store references and dereference the real data. The other option
11988
 have the lists store references and dereference the real data. The other option
11976
 is to obtain a copy of the row, then a copy of the cell, update the cell, then
11989
 is to obtain a copy of the row, then a copy of the cell, update the cell, then
11977
 replace the old value of the cell in the copy of the row, then replace the old
11990
 replace the old value of the cell in the copy of the row, then replace the old
12046
 	@<Expand the SaltModel@>@;
12059
 	@<Expand the SaltModel@>@;
12047
 }
12060
 }
12048
 
12061
 
12049
-@ The destructor doesn't need to do anything.
12062
+@ The destructor doesn'@q'@>t need to do anything.
12050
 
12063
 
12051
 @<SaltModel Implementation@>=
12064
 @<SaltModel Implementation@>=
12052
 SaltModel::~SaltModel()
12065
 SaltModel::~SaltModel()
12119
 	@<Check that the SaltModel index is valid@>@;
12132
 	@<Check that the SaltModel index is valid@>@;
12120
 	if(valid)
12133
 	if(valid)
12121
 	{
12134
 	{
12122
-		return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
12135
+		return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
12123
 	}
12136
 	}
12124
 	return 0;
12137
 	return 0;
12125
 }
12138
 }
12465
 @ In order to connect to the database, we need five pieces of information: the
12478
 @ In order to connect to the database, we need five pieces of information: the
12466
 name of a database driver (PostgreSQL is recommended for now), the host name of
12479
 name of a database driver (PostgreSQL is recommended for now), the host name of
12467
 the computer running the database, the name of the database, the name of the
12480
 the computer running the database, the name of the database, the name of the
12468
-user connecting to the database, and that user's password. This information will
12481
+user connecting to the database, and that user'@q'@>s password. This information will
12469
 be stored in the user settings for the application so that the database
12482
 be stored in the user settings for the application so that the database
12470
 connection can be established without prompting the user next time. A class is
12483
 connection can be established without prompting the user next time. A class is
12471
 provided to gather this information.
12484
 provided to gather this information.
12658
 				  QVariant(newsize));
12671
 				  QVariant(newsize));
12659
 
12672
 
12660
 @ To determine which window a given table is in, we just follow
12673
 @ To determine which window a given table is in, we just follow
12661
-|parentWidget()| until there isn't one. It is possible that the table view
12674
+|parentWidget()| until there isn'@q'@>t one. It is possible that the table view
12662
 will also be the window, however this is not advised as it is easier for the
12675
 will also be the window, however this is not advised as it is easier for the
12663
 settings key to be non-unique in such a case.
12676
 settings key to be non-unique in such a case.
12664
 
12677
 
12960
 in Qt. This brings several benefits, including making it easy to print reports
12973
 in Qt. This brings several benefits, including making it easy to print reports
12961
 or save reports as plain text or HTML.
12974
 or save reports as plain text or HTML.
12962
 
12975
 
12963
-Reports are specified in the \pn{}'s configuration document and can include both
12976
+Reports are specified in the \pn{}'@q'@>s configuration document and can include both
12964
 static elements and elements that are populated by external data such as the
12977
 static elements and elements that are populated by external data such as the
12965
 result of a SQL query.
12978
 result of a SQL query.
12966
 
12979
 
13212
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13225
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13213
 This is done with the {\tt <row>} element.
13226
 This is done with the {\tt <row>} element.
13214
 
13227
 
13215
-Technically, this isn't needed. The same results can be produced by using a
13228
+Technically, this isn'@q'@>t needed. The same results can be produced by using a
13216
 {\tt <query>} element to select constant data, but this approach saves a trip to
13229
 {\tt <query>} element to select constant data, but this approach saves a trip to
13217
 the database.
13230
 the database.
13218
 
13231
 
14199
 	saveDeviceConfiguration();
14212
 	saveDeviceConfiguration();
14200
 }
14213
 }
14201
 
14214
 
14202
-@ There isn't really anything that can be done if the device configuration data
14215
+@ There isn'@q'@>t really anything that can be done if the device configuration data
14203
 is corrupt, but an error message can be produced if the program happens to have
14216
 is corrupt, but an error message can be produced if the program happens to have
14204
 access to a debugging console.
14217
 access to a debugging console.
14205
 
14218
 
14589
 	return QDomElement();
14602
 	return QDomElement();
14590
 }
14603
 }
14591
 
14604
 
14592
-@ We don't want any headers, so |headerData()| is very simple.
14605
+@ We don'@q'@>t want any headers, so |headerData()| is very simple.
14593
 
14606
 
14594
 @<DeviceTreeModel implementation@>=
14607
 @<DeviceTreeModel implementation@>=
14595
 QVariant DeviceTreeModel::headerData(int, Qt::Orientation, int) const
14608
 QVariant DeviceTreeModel::headerData(int, Qt::Orientation, int) const
15217
 	        this, SLOT(insertChildNode(QString, QString)));
15230
 	        this, SLOT(insertChildNode(QString, QString)));
15218
 	connect(freeAnnotationInserter, SIGNAL(triggered(QString, QString)),
15231
 	connect(freeAnnotationInserter, SIGNAL(triggered(QString, QString)),
15219
 	        this, SLOT(insertChildNode(QString, QString)));
15232
 	        this, SLOT(insertChildNode(QString, QString)));
15233
+	@<Add annotation control node inserters@>@;
15220
 	addAnnotationControlButton->setMenu(annotationMenu);
15234
 	addAnnotationControlButton->setMenu(annotationMenu);
15221
 	layout->addWidget(addAnnotationControlButton);
15235
 	layout->addWidget(addAnnotationControlButton);
15222
 	QPushButton *advancedButton = new QPushButton(tr("Advanced Features"));
15236
 	QPushButton *advancedButton = new QPushButton(tr("Advanced Features"));
15441
 	@[private slots@]:@/
15455
 	@[private slots@]:@/
15442
 		void updateThermocoupleType(const QString &type);
15456
 		void updateThermocoupleType(const QString &type);
15443
 		void updateColumnName(const QString &name);
15457
 		void updateColumnName(const QString &name);
15458
+		void updateHidden(bool hidden);
15444
 };
15459
 };
15445
 
15460
 
15446
 @ This follows the same pattern of previous device configuration widgets. The
15461
 @ This follows the same pattern of previous device configuration widgets. The
15466
 	typeSelector->addItem("R");
15481
 	typeSelector->addItem("R");
15467
 	typeSelector->addItem("S");
15482
 	typeSelector->addItem("S");
15468
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15483
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15484
+	QCheckBox *hideSeries = new QCheckBox("Hide this channel");
15485
+	layout->addRow(hideSeries);
15469
 	setLayout(layout);
15486
 	setLayout(layout);
15470
 	@<Get device configuration data for current node@>@;
15487
 	@<Get device configuration data for current node@>@;
15471
 	for(int i = 0; i < configData.size(); i++)
15488
 	for(int i = 0; i < configData.size(); i++)
15480
 		{
15497
 		{
15481
 			columnName->setText(node.attribute("value"));
15498
 			columnName->setText(node.attribute("value"));
15482
 		}
15499
 		}
15500
+		else if(node.attribute("name") == "hidden")
15501
+		{
15502
+			hideSeries->setChecked(node.attribute("value") == "true");
15503
+		}
15483
 	}
15504
 	}
15484
 	updateThermocoupleType(typeSelector->currentText());
15505
 	updateThermocoupleType(typeSelector->currentText());
15485
 	updateColumnName(columnName->text());
15506
 	updateColumnName(columnName->text());
15507
+	updateHidden(hideSeries->isChecked());
15486
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)),
15508
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)),
15487
 	        this, SLOT(updateThermocoupleType(QString)));
15509
 	        this, SLOT(updateThermocoupleType(QString)));
15488
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15510
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15511
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
15489
 }
15512
 }
15490
 
15513
 
15491
 @ Two slots are used to pass configuration changes back to the underlying XML
15514
 @ Two slots are used to pass configuration changes back to the underlying XML
15502
 	updateAttribute("columnname", name);
15525
 	updateAttribute("columnname", name);
15503
 }
15526
 }
15504
 
15527
 
15528
+void Ni9211TcConfWidget::updateHidden(bool hidden)
15529
+{
15530
+	updateAttribute("hidden", hidden ? "true" : "false");
15531
+}
15532
+
15505
 @ These three widgets need to be registered so the configuration widget can
15533
 @ These three widgets need to be registered so the configuration widget can
15506
 instantiate them when the nodes are selected.
15534
 instantiate them when the nodes are selected.
15507
 
15535
 
15654
 		void updateDeviceId(const QString &newId);
15682
 		void updateDeviceId(const QString &newId);
15655
 		void updateThermocoupleType(const QString &type);
15683
 		void updateThermocoupleType(const QString &type);
15656
 		void updateColumnName(const QString &name);
15684
 		void updateColumnName(const QString &name);
15685
+		void updateHidden(bool hidden);
15657
 };
15686
 };
15658
 
15687
 
15659
 @ The implementation is similar to the other configuration widgets.
15688
 @ The implementation is similar to the other configuration widgets.
15678
 	typeSelector->addItem("R");
15707
 	typeSelector->addItem("R");
15679
 	typeSelector->addItem("S");
15708
 	typeSelector->addItem("S");
15680
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15709
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15710
+	QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
15711
+	layout->addRow(hideSeries);
15681
 	@<Get device configuration data for current node@>@;
15712
 	@<Get device configuration data for current node@>@;
15682
 	for(int i = 0; i < configData.size(); i++)
15713
 	for(int i = 0; i < configData.size(); i++)
15683
 	{
15714
 	{
15694
 		{
15725
 		{
15695
 			columnName->setText(node.attribute("value"));
15726
 			columnName->setText(node.attribute("value"));
15696
 		}
15727
 		}
15728
+		else if(node.attribute("name") == "hidden")
15729
+		{
15730
+			hideSeries->setChecked(node.attribute("value") == "true");
15731
+		}
15697
 	}
15732
 	}
15698
 	updateDeviceId(deviceId->text());
15733
 	updateDeviceId(deviceId->text());
15699
 	updateThermocoupleType(typeSelector->currentText());
15734
 	updateThermocoupleType(typeSelector->currentText());
15700
 	updateColumnName(columnName->text());
15735
 	updateColumnName(columnName->text());
15736
+	updateHidden(hideSeries->isChecked());
15701
 	connect(deviceId, SIGNAL(textEdited(QString)), this, SLOT(updateDeviceId(QString)));
15737
 	connect(deviceId, SIGNAL(textEdited(QString)), this, SLOT(updateDeviceId(QString)));
15702
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateThermocoupleType(QString)));
15738
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateThermocoupleType(QString)));
15703
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15739
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15704
 	setLayout(layout);
15740
 	setLayout(layout);
15741
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
15705
 }
15742
 }
15706
 
15743
 
15707
 void NiDaqMxTc01ConfWidget::updateDeviceId(const QString &newId)
15744
 void NiDaqMxTc01ConfWidget::updateDeviceId(const QString &newId)
15719
 	updateAttribute("columnname", name);
15756
 	updateAttribute("columnname", name);
15720
 }
15757
 }
15721
 
15758
 
15759
+void NiDaqMxTc01ConfWidget::updateHidden(bool hidden)
15760
+{
15761
+	updateAttribute("hidden", hidden ? "true" : "false");
15762
+}
15763
+
15722
 @ These configuration widgets need to be registered so they can be instantiated
15764
 @ These configuration widgets need to be registered so they can be instantiated
15723
 in response to node selections.
15765
 in response to node selections.
15724
 
15766
 
15782
 	QextPortInfo port;
15824
 	QextPortInfo port;
15783
 	foreach(port, ports)
15825
 	foreach(port, ports)
15784
 	{
15826
 	{
15827
+#ifdef Q_OS_WIN32
15785
 		addItem(port.portName);
15828
 		addItem(port.portName);
15829
+#else
15830
+		addItem(port.physName);
15831
+#endif
15786
 	}
15832
 	}
15787
 	lister->setUpNotifications();
15833
 	lister->setUpNotifications();
15788
 	connect(lister, SIGNAL(deviceDiscovered(QextPortInfo)),
15834
 	connect(lister, SIGNAL(deviceDiscovered(QextPortInfo)),
15801
 A later version of QextSerialPort than is used by \pn{} provides a helper
15847
 A later version of QextSerialPort than is used by \pn{} provides a helper
15802
 class which can be used more conveniently to create this sort of control. As
15848
 class which can be used more conveniently to create this sort of control. As
15803
 this is not yet available to \pn{}, we instead copy the |enum| specifying
15849
 this is not yet available to \pn{}, we instead copy the |enum| specifying
15804
-the appropriate values into the class and use Qt's meta-object system to
15850
+the appropriate values into the class and use Qt'@q'@>s meta-object system to
15805
 populate the combo box based on the values in that |enum|.
15851
 populate the combo box based on the values in that |enum|.
15806
 
15852
 
15807
 @<Class declarations@>=
15853
 @<Class declarations@>=
17289
 }
17335
 }
17290
 
17336
 
17291
 @ There are two ways that we might request measurement data. All of the
17337
 @ There are two ways that we might request measurement data. All of the
17292
-devices I've seen documented provide function 0x4 addresses for PV and SV
17338
+devices I'@q'@>ve seen documented provide function 0x4 addresses for PV and SV
17293
 such that SV can be obtained from the address immediately after the address
17339
 such that SV can be obtained from the address immediately after the address
17294
 from which we obtain PV. In this case we request both values at the same time.
17340
 from which we obtain PV. In this case we request both values at the same time.
17295
 
17341
 
17374
 this buffer will have incomplete data. This means that we must determine when
17420
 this buffer will have incomplete data. This means that we must determine when
17375
 the full response is available before passing the complete response along to
17421
 the full response is available before passing the complete response along to
17376
 the appropriate method. If the response has not been received in full, nothing
17422
 the appropriate method. If the response has not been received in full, nothing
17377
-is done. We'll be notified of more data shortly.
17423
+is done. We'@q'@>ll be notified of more data shortly.
17378
 
17424
 
17379
 When the message we see the response for was queued, a callback was also
17425
 When the message we see the response for was queued, a callback was also
17380
 registered to handle the response. Once we have the complete message, we pass
17426
 registered to handle the response. Once we have the complete message, we pass
17547
 	queueMessage(message, this, "ignore(QByteArray)");
17593
 	queueMessage(message, this, "ignore(QByteArray)");
17548
 }
17594
 }
17549
 
17595
 
17550
-@ We don't care about the response when sending a new SV.
17596
+@ We don'@q'@>t care about the response when sending a new SV.
17551
 
17597
 
17552
 @<ModbusRTUDevice implementation@>=
17598
 @<ModbusRTUDevice implementation@>=
17553
 void ModbusRTUDevice::ignore(QByteArray)
17599
 void ModbusRTUDevice::ignore(QByteArray)
17683
 		void updateSVWriteAddress(int address);
17729
 		void updateSVWriteAddress(int address);
17684
 		void updatePVColumnName(const QString &name);
17730
 		void updatePVColumnName(const QString &name);
17685
 		void updateSVColumnName(const QString &name);
17731
 		void updateSVColumnName(const QString &name);
17732
+		void updatePVHidden(bool hidden);
17733
+		void updateSVHidden(bool hidden);
17686
 	private:@/
17734
 	private:@/
17687
 		PortSelector *port;
17735
 		PortSelector *port;
17688
 		BaudSelector *baud;
17736
 		BaudSelector *baud;
17774
 	QFormLayout *pVSection = new QFormLayout;
17822
 	QFormLayout *pVSection = new QFormLayout;
17775
 	pVSection->addRow(tr("Value relative address:"), pVAddress);
17823
 	pVSection->addRow(tr("Value relative address:"), pVAddress);
17776
 	pVSection->addRow(tr("PV column name:"), pVColumnName);
17824
 	pVSection->addRow(tr("PV column name:"), pVColumnName);
17825
+	QCheckBox *pVHideBox = new QCheckBox(tr("Hide this channel"));
17826
+	pVSection->addRow(pVHideBox);
17777
 	QGroupBox *processValueBox = new QGroupBox(tr("Process Value"));
17827
 	QGroupBox *processValueBox = new QGroupBox(tr("Process Value"));
17778
 	processValueBox->setLayout(pVSection);
17828
 	processValueBox->setLayout(pVSection);
17779
 	seriesLayout->addWidget(processValueBox);
17829
 	seriesLayout->addWidget(processValueBox);
17794
 	sVSection->addRow(tr("Upper limit:"), sVUpper);
17844
 	sVSection->addRow(tr("Upper limit:"), sVUpper);
17795
 	sVSection->addRow(tr("Output set value:"), sVWritable);
17845
 	sVSection->addRow(tr("Output set value:"), sVWritable);
17796
 	sVSection->addRow(tr("Output relative address:"), sVOutputAddr);
17846
 	sVSection->addRow(tr("Output relative address:"), sVOutputAddr);
17847
+	QCheckBox *sVHideBox = new QCheckBox(tr("Hide this channel"));
17848
+	sVSection->addRow(sVHideBox);
17797
 	QGroupBox *setValueBox = new QGroupBox(tr("Set Value"));
17849
 	QGroupBox *setValueBox = new QGroupBox(tr("Set Value"));
17798
 	setValueBox->setLayout(sVSection);
17850
 	setValueBox->setLayout(sVSection);
17799
 	seriesLayout->addWidget(setValueBox);
17851
 	seriesLayout->addWidget(setValueBox);
17952
 		{
18004
 		{
17953
 			sVColumnName->setText(node.attribute("value"));
18005
 			sVColumnName->setText(node.attribute("value"));
17954
 		}
18006
 		}
18007
+		else if(node.attribute("name") == "pvhidden")
18008
+		{
18009
+			pVHideBox->setChecked(node.attribute("value") == "true");
18010
+		}
18011
+		else if(node.attribute("name") == "svhidden")
18012
+		{
18013
+			sVHideBox->setChecked(node.attribute("value") == "true");
18014
+		}
17955
 	}
18015
 	}
17956
 	updatePort(port->currentText());
18016
 	updatePort(port->currentText());
17957
 	updateBaudRate(baud->currentText());
18017
 	updateBaudRate(baud->currentText());
17979
 	updateSVWriteAddress(sVOutputAddr->value());
18039
 	updateSVWriteAddress(sVOutputAddr->value());
17980
 	updatePVColumnName(pVColumnName->text());
18040
 	updatePVColumnName(pVColumnName->text());
17981
 	updateSVColumnName(sVColumnName->text());
18041
 	updateSVColumnName(sVColumnName->text());
18042
+	updatePVHidden(pVHideBox->isChecked());
18043
+	updateSVHidden(sVHideBox->isChecked());
17982
 	connect(port, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
18044
 	connect(port, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
17983
 	connect(port, SIGNAL(editTextChanged(QString)), this, SLOT(updatePort(QString)));
18045
 	connect(port, SIGNAL(editTextChanged(QString)), this, SLOT(updatePort(QString)));
17984
 	connect(baud, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateBaudRate(QString)));
18046
 	connect(baud, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateBaudRate(QString)));
18006
 	connect(sVOutputAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVWriteAddress(int)));
18068
 	connect(sVOutputAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVWriteAddress(int)));
18007
 	connect(pVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updatePVColumnName(QString)));
18069
 	connect(pVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updatePVColumnName(QString)));
18008
 	connect(sVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updateSVColumnName(QString)));
18070
 	connect(sVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updateSVColumnName(QString)));
18071
+	connect(pVHideBox, SIGNAL(toggled(bool)), this, SLOT(updatePVHidden(bool)));
18072
+	connect(sVHideBox, SIGNAL(toggled(bool)), this, SLOT(updateSVHidden(bool)));
18009
 	layout->addWidget(form);
18073
 	layout->addWidget(form);
18010
 	setLayout(layout);
18074
 	setLayout(layout);
18011
 }
18075
 }
18140
 	updateAttribute("svcolname", name);
18204
 	updateAttribute("svcolname", name);
18141
 }
18205
 }
18142
 
18206
 
18207
+void ModbusConfigurator::updatePVHidden(bool hidden)
18208
+{
18209
+	updateAttribute("pvhidden", hidden ? "true" : "false");
18210
+}
18211
+
18212
+void ModbusConfigurator::updateSVHidden(bool hidden)
18213
+{
18214
+	updateAttribute("svhidden", hidden ? "true" : "false");
18215
+}
18216
+
18143
 @ This is registered with the configuration system.
18217
 @ This is registered with the configuration system.
18144
 
18218
 
18145
 @<Register device configuration widgets@>=
18219
 @<Register device configuration widgets@>=
18168
 		void updateDestinationColumn(const QString &dest);
18242
 		void updateDestinationColumn(const QString &dest);
18169
 		void updateKnots();
18243
 		void updateKnots();
18170
 	private:@/
18244
 	private:@/
18171
-		SaltModel *knotmodel;
18245
+		SaltModel *tablemodel;
18172
 };
18246
 };
18173
 
18247
 
18174
 @ This is configured by specifying a source column name, a destination column
18248
 @ This is configured by specifying a source column name, a destination column
18177
 
18251
 
18178
 @<LinearSplineInterpolationConfWidget implementation@>=
18252
 @<LinearSplineInterpolationConfWidget implementation@>=
18179
 LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
18253
 LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
18180
-: BasicDeviceConfigurationWidget(model, index), knotmodel(new SaltModel(2))
18254
+: BasicDeviceConfigurationWidget(model, index), tablemodel(new SaltModel(2))
18181
 {
18255
 {
18182
 	QFormLayout *layout = new QFormLayout;
18256
 	QFormLayout *layout = new QFormLayout;
18183
 	QLineEdit *source = new QLineEdit;
18257
 	QLineEdit *source = new QLineEdit;
18184
 	layout->addRow(tr("Source column name:"), source);
18258
 	layout->addRow(tr("Source column name:"), source);
18185
 	QLineEdit *destination = new QLineEdit;
18259
 	QLineEdit *destination = new QLineEdit;
18186
 	layout->addRow(tr("Destination column name:"), destination);
18260
 	layout->addRow(tr("Destination column name:"), destination);
18187
-	knotmodel->setHeaderData(0, Qt::Horizontal, "Input");
18188
-	knotmodel->setHeaderData(1, Qt::Horizontal, "Output");
18261
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Input");
18262
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Output");
18189
 	QTableView *mappingTable = new QTableView;
18263
 	QTableView *mappingTable = new QTableView;
18190
-	mappingTable->setModel(knotmodel);
18264
+	mappingTable->setModel(tablemodel);
18191
 	NumericDelegate *delegate = new NumericDelegate;
18265
 	NumericDelegate *delegate = new NumericDelegate;
18192
 	mappingTable->setItemDelegate(delegate);
18266
 	mappingTable->setItemDelegate(delegate);
18193
 	layout->addRow(tr("Mapping data:"), mappingTable);
18267
 	layout->addRow(tr("Mapping data:"), mappingTable);
18222
 	updateKnots();
18296
 	updateKnots();
18223
 	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
18297
 	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
18224
 	connect(destination, SIGNAL(textEdited(QString)), this, SLOT(updateDestinationColumn(QString)));
18298
 	connect(destination, SIGNAL(textEdited(QString)), this, SLOT(updateDestinationColumn(QString)));
18225
-	connect(knotmodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18299
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18226
 	setLayout(layout);
18300
 	setLayout(layout);
18227
 }
18301
 }
18228
 
18302
 
18245
 @<Populate model column from list@>=
18319
 @<Populate model column from list@>=
18246
 for(int i = 0; i < itemList.size(); i++)
18320
 for(int i = 0; i < itemList.size(); i++)
18247
 {
18321
 {
18248
-	knotmodel->setData(knotmodel->index(i, column),
18249
-	                   QVariant(itemList.at(i).toDouble()),
18250
-                       Qt::DisplayRole);
18322
+	tablemodel->setData(tablemodel->index(i, column),
18323
+	               QVariant(itemList.at(i).toDouble()),
18324
+                   Qt::DisplayRole);
18251
 }
18325
 }
18252
 
18326
 
18253
 @ When data in the table is changed we simply overwrite any previously saved
18327
 @ When data in the table is changed we simply overwrite any previously saved
18256
 @<LinearSplineInterpolationConfWidget implementation@>=
18330
 @<LinearSplineInterpolationConfWidget implementation@>=
18257
 void LinearSplineInterpolationConfWidget::updateKnots()
18331
 void LinearSplineInterpolationConfWidget::updateKnots()
18258
 {
18332
 {
18259
-	updateAttribute("sourcevalues", knotmodel->arrayLiteral(0, Qt::DisplayRole));
18260
-	updateAttribute("destinationvalues", knotmodel->arrayLiteral(1, Qt::DisplayRole));
18333
+	updateAttribute("sourcevalues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
18334
+	updateAttribute("destinationvalues", tablemodel->arrayLiteral(1, Qt::DisplayRole));
18261
 }
18335
 }
18262
 
18336
 
18263
 void LinearSplineInterpolationConfWidget::updateSourceColumn(const QString &source)
18337
 void LinearSplineInterpolationConfWidget::updateSourceColumn(const QString &source)
18368
 
18442
 
18369
 @i dataqsdk.w
18443
 @i dataqsdk.w
18370
 
18444
 
18445
+@i scales.w
18446
+
18447
+@i valueannotation.w
18448
+
18371
 @** Local changes.
18449
 @** Local changes.
18372
 
18450
 
18373
 \noindent This is the end of \pn{} as distributed by its author. It is expected
18451
 \noindent This is the end of \pn{} as distributed by its author. It is expected

+ 216
- 2
src/units.w View File

22
 			Fahrenheit = 10144,
22
 			Fahrenheit = 10144,
23
 			Celsius = 10143,
23
 			Celsius = 10143,
24
 			Kelvin = 10325,
24
 			Kelvin = 10325,
25
-			Rankine = 10145
25
+			Rankine = 10145,
26
+			Pound = 15876,
27
+			Kilogram = 15877,
28
+			Ounce = 1,
29
+			Gram = 2
26
 		};
30
 		};
27
 		static double convertTemperature(double value, Unit fromUnit, Unit toUnit);
31
 		static double convertTemperature(double value, Unit fromUnit, Unit toUnit);
28
 		static double convertRelativeTemperature(double value, Unit fromUnit, Unit toUnit);
32
 		static double convertRelativeTemperature(double value, Unit fromUnit, Unit toUnit);
29
 		static bool isTemperatureUnit(Unit unit);
33
 		static bool isTemperatureUnit(Unit unit);
34
+		static double convertWeight(double value, Unit fromUnit, Unit toUnit);
35
+		static bool isWeightUnit(Unit unit);
30
 };
36
 };
31
 
37
 
32
 #endif
38
 #endif
175
 				case Rankine:
181
 				case Rankine:
176
 					return value;
182
 					return value;
177
 					break;
183
 					break;
184
+				default:
185
+					return 0;
186
+					break;
178
 			}
187
 			}
179
 			break;
188
 			break;
180
 		case Celsius:
189
 		case Celsius:
192
 				case Rankine:
201
 				case Rankine:
193
 					return value * (9.0 / 5.0);
202
 					return value * (9.0 / 5.0);
194
 					break;
203
 					break;
204
+				default:
205
+					return 0;
206
+					break;
195
 			}
207
 			}
196
 			break;
208
 			break;
197
 		case Kelvin:
209
 		case Kelvin:
209
 				case Rankine:
221
 				case Rankine:
210
 					return value * (9.0 / 5.0);
222
 					return value * (9.0 / 5.0);
211
 					break;
223
 					break;
224
+				default:
225
+					return 0;
226
+					break;
212
 			}
227
 			}
213
 			break;
228
 			break;
214
 		case Rankine:
229
 		case Rankine:
226
 				case Rankine:
241
 				case Rankine:
227
 					return value;
242
 					return value;
228
 					break;
243
 					break;
244
+				default:
245
+					return 0;
246
+					break;
229
 			}
247
 			}
230
 			break;
248
 			break;
231
 		default:
249
 		default:
235
 	return 0;
253
 	return 0;
236
 }
254
 }
237
 
255
 
256
+@ Units of force are handled similarly to units of temperature.
257
+
258
+@(units.cpp@>=
259
+double Units::convertWeight(double value, Unit fromUnit, Unit toUnit)
260
+{
261
+	if(isWeightUnit(fromUnit) && isWeightUnit(toUnit))
262
+	{
263
+		switch(fromUnit)
264
+		{
265
+			case Pound:
266
+				switch(toUnit)
267
+				{
268
+					case Pound:
269
+						return value;
270
+						break;
271
+					case Kilogram:
272
+						return value / 2.2;
273
+						break;
274
+					case Ounce:
275
+						return value * 16.0;
276
+						break;
277
+					case Gram:
278
+						return value / 0.0022;
279
+						break;
280
+					default:
281
+						return 0;
282
+						break;
283
+				}
284
+				break;
285
+			case Kilogram:
286
+				switch(toUnit)
287
+				{
288
+					case Pound:
289
+						return value * 2.2;
290
+						break;
291
+					case Kilogram:
292
+						return value;
293
+						break;
294
+					case Ounce:
295
+						return value * 35.2;
296
+						break;
297
+					case Gram:
298
+						return value * 1000.0;
299
+						break;
300
+					default:
301
+						return 0;
302
+						break;
303
+				}
304
+				break;
305
+			case Ounce:
306
+				switch(toUnit)
307
+				{
308
+					case Pound:
309
+						return value / 16.0;
310
+						break;
311
+					case Kilogram:
312
+						return value / 35.2;
313
+						break;
314
+					case Ounce:
315
+						return value;
316
+						break;
317
+					case Gram:
318
+						return value / 0.0352;
319
+						break;
320
+					default:
321
+						return 0;
322
+						break;
323
+				}
324
+				break;
325
+			case Gram:
326
+				switch(toUnit)
327
+				{
328
+					case Pound:
329
+						return value * 0.0022;
330
+						break;
331
+					case Kilogram:
332
+						return value / 1000.0;
333
+						break;
334
+					case Ounce:
335
+						return value * 0.0352;
336
+						break;
337
+					case Gram:
338
+						return value;
339
+						break;
340
+					default:
341
+						return 0;
342
+						break;
343
+				}
344
+				break;
345
+			default:
346
+				return 0;
347
+				break;
348
+		}
349
+	}
350
+	return 0;
351
+}
352
+
353
+bool Units::isWeightUnit(Unit unit)
354
+{
355
+	if(unit == Pound ||
356
+	   unit == Kilogram ||
357
+	   unit == Ounce ||
358
+	   unit == Gram)
359
+	{
360
+		return true;
361
+	}
362
+	return false;
363
+}
364
+
238
 @ This class is exposed to the host environment. Note the lack of constructor.
365
 @ This class is exposed to the host environment. Note the lack of constructor.
239
 We do not wish to create any instances, just have access to the |Unit|
366
 We do not wish to create any instances, just have access to the |Unit|
240
-enumeration.
367
+enumeration and conversion methods.
368
+
369
+Unfortunately, marking a static method |Q_INVOKABLE| will not work as per Qt
370
+bug #18840. There seems to be no intention to correct this deficiency so
371
+instead of having something that just works, we must resort to the following
372
+hackery.
373
+
374
+@<Function prototypes for scripting@>=
375
+QScriptValue Units_convertTemperature(QScriptContext *context, QScriptEngine *engine);
376
+QScriptValue Units_convertRelativeTemperature(QScriptContext *context,
377
+                                              QScriptEngine *engine);
378
+QScriptValue Units_isTemperatureUnit(QScriptContext *context, QScriptEngine *engine);
379
+QScriptValue Units_convertWeight(QScriptContext *context, QScriptEngine *engine);
380
+QScriptValue Units_isWeightUnit(QScriptContext *context, QScriptEngine *engine);
381
+
382
+@ These functions are added as properties for the host environment.
241
 
383
 
242
 @<Set up the scripting engine@>=
384
 @<Set up the scripting engine@>=
243
 value = engine->newQMetaObject(&Units::staticMetaObject);
385
 value = engine->newQMetaObject(&Units::staticMetaObject);
386
+value.setProperty("convertTemperature", engine->newFunction(Units_convertTemperature));
387
+value.setProperty("convertRelativeTemperature",
388
+                  engine->newFunction(Units_convertRelativeTemperature));
389
+value.setProperty("isTemperatureUnit", engine->newFunction(Units_isTemperatureUnit));
390
+value.setProperty("convertWeight", engine->newFunction(Units_convertWeight));
391
+value.setProperty("isWeightUnit", engine->newFunction(Units_isWeightUnit));
244
 engine->globalObject().setProperty("Units", value);
392
 engine->globalObject().setProperty("Units", value);
245
 
393
 
394
+@ The implementation of these functions is trivial.
395
+
396
+@<Functions for scripting@>=
397
+QScriptValue Units_convertTemperature(QScriptContext *context, QScriptEngine *engine)
398
+{
399
+	return QScriptValue(Units::convertTemperature(argument<double>(0, context),
400
+	                                              argument<Units::Unit>(1, context),
401
+	                                              argument<Units::Unit>(2, context)));
402
+}
403
+
404
+QScriptValue Units_convertRelativeTemperature(QScriptContext *context,
405
+                                              QScriptEngine *engine)
406
+{
407
+	return QScriptValue(Units::convertRelativeTemperature(
408
+	                         argument<double>(0, context),
409
+	                         argument<Units::Unit>(1, context),
410
+	                         argument<Units::Unit>(2, context)));
411
+}
412
+
413
+QScriptValue Units_isTemperatureUnit(QScriptContext *context, QScriptEngine *engine)
414
+{
415
+	return QScriptValue(Units::isTemperatureUnit(argument<Units::Unit>(0, context)));
416
+}
417
+
418
+QScriptValue Units_convertWeight(QScriptContext *context, QScriptEngine *engine)
419
+{
420
+	return QScriptValue(Units::convertWeight(argument<double>(0, context),
421
+	                                         argument<Units::Unit>(1, context),
422
+	                                         argument<Units::Unit>(2, context)));
423
+}
424
+
425
+QScriptValue Units_isWeightUnit(QScriptContext *context, QScriptEngine *engine)
426
+{
427
+	return QScriptValue(Units::isWeightUnit(argument<Units::Unit>(0, context)));
428
+}
429
+
430
+@ To pass unit data in some circumstances, the inner enumeration must be
431
+registered as a meta-type with script conversions.
432
+
433
+@<Class declarations@>=
434
+Q_DECLARE_METATYPE(Units::Unit)
435
+
436
+@ A pair of conversion methods is required.
437
+
438
+@<Function prototypes for scripting@>=
439
+QScriptValue Unit_toScriptValue(QScriptEngine *engine, const Units::Unit &value);
440
+void Unit_fromScriptValue(const QScriptValue &sv, Units::Unit &value);
441
+
442
+@ These are implemented thusly.
443
+
444
+@<Functions for scripting@>=
445
+QScriptValue Unit_toScriptValue(QScriptEngine *engine, const Units::Unit &value)
446
+{
447
+	return engine->newVariant(QVariant(value));
448
+}
449
+
450
+void Unit_fromScriptValue(const QScriptValue &sv, Units::Unit &value)
451
+{
452
+	value = sv.toVariant().value<Units::Unit>();
453
+}
454
+
455
+@ These conversion functions are registered.
456
+
457
+@<Set up the scripting engine@>=
458
+qScriptRegisterMetaType(engine, Unit_toScriptValue, Unit_fromScriptValue);
459
+

+ 289
- 0
src/valueannotation.w View File

1
+@** Annotations for Values.
2
+
3
+\noindent In circumstances where a control setting is logged but this setting
4
+changes infrequently and has a small number of possible values, it is sometimes
5
+useful to not log the control values throughout the roast but rather simply add
6
+an annotation when the control value changes. It will commonly be desirable to
7
+also provide an annotation representing the current state at the start of every
8
+batch.
9
+
10
+To support this feature, there must be a configuration widget which can be used
11
+to identify which data series should be monitored and what annotations should
12
+be produced for what values.
13
+
14
+@<Class declarations@>=
15
+class ValueAnnotationConfWidget : public BasicDeviceConfigurationWidget
16
+{
17
+	Q_OBJECT
18
+	public:
19
+		Q_INVOKABLE ValueAnnotationConfWidget(DeviceTreeModel *model,
20
+                                              const QModelIndex &index);
21
+	private slots:
22
+		void updateSourceColumn(const QString &source);
23
+		void updateAnnotations();
24
+		void updateStart(bool noteOnStart);
25
+	private:
26
+		SaltModel *tablemodel;
27
+};
28
+
29
+@ The constructor sets up the configuration interface requesting a source
30
+column name, if an annotation should be emitted at the start of the batch, and
31
+what annotations should be produced for what values.
32
+
33
+@<ValueAnnotationConfWidget implementation@>=
34
+ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
35
+                                                     const QModelIndex &index)
36
+: BasicDeviceConfigurationWidget(model, index),
37
+  tablemodel(new SaltModel(2))
38
+{
39
+	QFormLayout *layout = new QFormLayout;
40
+	QLineEdit *source = new QLineEdit;
41
+	layout->addRow(tr("Source column name:"), source);
42
+	QCheckBox *noteOnStart = new QCheckBox(tr("Produce Start State Annotation"));
43
+	noteOnStart->setChecked(true);
44
+	layout->addRow(noteOnStart);
45
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Value");
46
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Annotation");
47
+	QTableView *annotationTable = new QTableView;
48
+	annotationTable->setModel(tablemodel);
49
+	NumericDelegate *delegate = new NumericDelegate;
50
+	annotationTable->setItemDelegateForColumn(0, delegate);
51
+	layout->addRow(tr("Annotations for values:"), annotationTable);
52
+	@<Get device configuration data for current node@>@;
53
+	for(int i = 0; i < configData.size(); i++)
54
+	{
55
+		node = configData.at(i).toElement();
56
+		if(node.attribute("name") == "source")
57
+		{
58
+			source->setText(node.attribute("value"));
59
+		}
60
+		else if(node.attribute("name") == "emitOnStart")
61
+		{
62
+			noteOnStart->setChecked(node.attribute("value") == "true" ? true : false);
63
+		}
64
+		else if(node.attribute("name") == "measuredValues")
65
+		{
66
+			@<Convert numeric array literal to list@>@;
67
+			int column = 0;
68
+			@<Populate model column from list@>@;
69
+		}
70
+		else if(node.attribute("name") == "annotations")
71
+		{
72
+			@<Convert string array literal to list@>@;
73
+			int column = 1;
74
+			@<Populate model column from string list@>@;
75
+		}
76
+	}
77
+	updateSourceColumn(source->text());
78
+	updateStart(noteOnStart->isChecked());
79
+	updateAnnotations();
80
+	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
81
+	connect(noteOnStart, SIGNAL(toggled(bool)), this, SLOT(updateStart(bool)));
82
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateAnnotations()));
83
+	setLayout(layout);
84
+}
85
+
86
+@ While we can re-use code for handling numeric array literals, string array
87
+literals need to be handled a little differently.
88
+
89
+@<Convert string array literal to list@>=
90
+QString data = node.attribute("value");
91
+if(data.length() > 3)
92
+{
93
+	data.chop(2);
94
+	data = data.remove(0, 2);
95
+}
96
+QStringList itemList = data.split(",");
97
+for(int i = 0; i < itemList.size(); i++)
98
+{
99
+	itemList[i] = itemList[i].simplified();
100
+}
101
+
102
+@ Populating the model must also be done a little differently.
103
+
104
+@<Populate model column from string list@>=
105
+for(int i = 0; i < itemList.size(); i++)
106
+{
107
+	tablemodel->setData(tablemodel->index(i, column),
108
+	                    QVariant(itemList.at(i)),
109
+	                    Qt::DisplayRole);
110
+}
111
+
112
+@ To update the table data, the measued values and annotations are saved in
113
+separate lists.
114
+
115
+@<ValueAnnotationConfWidget implementation@>=
116
+void ValueAnnotationConfWidget::updateAnnotations()
117
+{
118
+	updateAttribute("measuredValues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
119
+	updateAttribute("annotations", tablemodel->arrayLiteral(1, Qt::DisplayRole));
120
+}
121
+
122
+@ The other settings are updated based on values passed through the parameter
123
+to the update method.
124
+
125
+@<ValueAnnotationConfWidget implementation@>=
126
+void ValueAnnotationConfWidget::updateSourceColumn(const QString &source)
127
+{
128
+	updateAttribute("source", source);
129
+}
130
+
131
+void ValueAnnotationConfWidget::updateStart(bool noteOnStart)
132
+{
133
+	updateAttribute("emitOnStart", noteOnStart ? "true" : "false");
134
+}
135
+
136
+@ The widget is registered with the configuration system.
137
+
138
+@<Register device configuration widgets@>=
139
+app.registerDeviceConfigurationWidget("valueannotation",
140
+	ValueAnnotationConfWidget::staticMetaObject);
141
+
142
+@ A NodeInserter is needed to make this widget available.
143
+
144
+@<Add annotation control node inserters@>=
145
+NodeInserter *valueAnnotationInserter = new NodeInserter(tr("Value Annotation"),
146
+                                                         tr("Value Annotation"),
147
+                                                         "valueannotation");
148
+annotationMenu->addAction(valueAnnotationInserter);
149
+connect(valueAnnotationInserter, SIGNAL(triggered(QString, QString)),
150
+        this, SLOT(insertChildNode(QString, QString)));
151
+
152
+@ While it is possible to implement this feature with |ThresholdDetector|
153
+objects, the code to handle these would be difficult to understand and there
154
+would be excessive overhead in moving measurements through all of these.
155
+Instead, we create a new class that watches for any sort of measurement
156
+change and produces the annotation signals directly.
157
+
158
+As measured values are represented as a |double|, a small value should be
159
+provided such that comparisons are not against the value directly but instead
160
+are against the value plus or minus this other small value.
161
+
162
+Method names have been chosen to be compatible with the |AnnotationButton|
163
+class.
164
+
165
+@<Class declarations@>=
166
+class ValueAnnotation : public QObject
167
+{
168
+	Q_OBJECT
169
+	public:
170
+		ValueAnnotation();
171
+		Q_INVOKABLE void setAnnotation(double value, const QString &annotation);
172
+	public slots:
173
+		void newMeasurement(Measurement measure);
174
+		void annotate();
175
+		void setAnnotationColumn(int column);
176
+		void setTemperatureColumn(int column);
177
+		void setTolerance(double epsilon);
178
+	signals:
179
+		void annotation(QString annotation, int tempcolumn, int notecolumn);
180
+	private:
181
+		int lastIndex;
182
+		int annotationColumn;
183
+		int measurementColumn;
184
+		QList<double> values;
185
+		QStringList annotations;
186
+		double tolerance;
187
+};
188
+
189
+@ Most of the work of this class happens in the |newMeasurement| method. This
190
+compares the latest measurement with every value that has an associated
191
+annotation. If the value is near enough to a value in the list, the index of
192
+that value is compared with the index of the previous annotation (if any) and
193
+if the indices are different, the appropriate annotation is emitted.
194
+
195
+@<ValueAnnotation implementation@>=
196
+void ValueAnnotation::newMeasurement(Measurement measure)
197
+{
198
+	for(int i = 0; i < values.size(); i++)
199
+	{
200
+		if(measure.temperature() > values.at(i) - tolerance &&
201
+		   measure.temperature() < values.at(i) + tolerance)
202
+		{
203
+			if(i != lastIndex)
204
+			{
205
+				lastIndex = i;
206
+				emit annotation(annotations.at(i), measurementColumn, annotationColumn);
207
+			}
208
+		}
209
+	}
210
+}
211
+
212
+@ Another method is used to emit an annotation matching the current state at
213
+the start of a batch if that is desired. This will not produce any output if
214
+no state has yet been matched.
215
+
216
+@<ValueAnnotation implementation@>=
217
+void ValueAnnotation::annotate()
218
+{
219
+	if(lastIndex > -1)
220
+	{
221
+		emit annotation(annotations.at(lastIndex), measurementColumn, annotationColumn);
222
+	}
223
+}
224
+
225
+@ Values and annotations are added to separate lists with new mappings always
226
+appended. Entries are never removed from these lists.
227
+
228
+@<ValueAnnotation implementation@>=
229
+void ValueAnnotation::setAnnotation(double value, const QString &annotation)
230
+{
231
+	values.append(value);
232
+	annotations.append(annotation);
233
+}
234
+
235
+@ The remaining setter methods are trivial similarly trivial.
236
+
237
+@<ValueAnnotation implementation@>=
238
+void ValueAnnotation::setAnnotationColumn(int column)
239
+{
240
+	annotationColumn = column;
241
+}
242
+
243
+void ValueAnnotation::setTemperatureColumn(int column)
244
+{
245
+	measurementColumn = column;
246
+}
247
+
248
+void ValueAnnotation::setTolerance(double epsilon)
249
+{
250
+	tolerance = epsilon;
251
+}
252
+
253
+@ This just leaves a trivial constructor.
254
+
255
+@<ValueAnnotation implementation@>=
256
+ValueAnnotation::ValueAnnotation() : QObject(),
257
+	lastIndex(-1), annotationColumn(2), measurementColumn(1), tolerance(0.05)
258
+{
259
+	/* Nothing needs to be done here. */
260
+}
261
+
262
+@ This class is exposed to the host environment in the usual way. First with
263
+function prototypes.
264
+
265
+@<Function prototypes for scripting@>=
266
+QScriptValue constructValueAnnotation(QScriptContext *context, QScriptEngine *engine);
267
+void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine);
268
+
269
+@ Then setting up a new property of the global object.
270
+
271
+@<Set up the scripting engine@>=
272
+constructor = engine->newFunction(constructValueAnnotation);
273
+value = engine->newQMetaObject(&ValueAnnotation::staticMetaObject, constructor);
274
+engine->globalObject().setProperty("ValueAnnotation", value);
275
+
276
+@ The implementation of the functions also proceeds as usual.
277
+
278
+@<Functions for scripting@>=
279
+QScriptValue constructValueAnnotation(QScriptContext *context, QScriptEngine *engine)
280
+{
281
+	QScriptValue object = engine->newQObject(new ValueAnnotation);
282
+	setValueAnnotationProperties(object, engine);
283
+	return object;
284
+}
285
+
286
+void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine)
287
+{
288
+	setQObjectProperties(value, engine);
289
+}

Loading…
Cancel
Save