Просмотр исходного кода

Merge branch 'programpipe' into development

Neal Wilson 10 лет назад
Родитель
Сommit
c8da5d3f6a
4 измененных файлов: 961 добавлений и 6 удалений
  1. 37
    0
      config/Windows/productionroaster.xml
  2. 72
    1
      src/measurement.w
  3. 157
    5
      src/typica.w
  4. 695
    0
      src/unsupportedserial.w

+ 37
- 0
config/Windows/productionroaster.xml Просмотреть файл

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

+ 72
- 1
src/measurement.w Просмотреть файл

@@ -121,4 +121,75 @@ public default constructor (already defined above), a public copy constructor,
121 121
 and a public destructor. These latter two are default generated.
122 122
 
123 123
 @<Register meta-types@>=
124
-qRegisterMetaType<Measurement>("Measurement");
124
+qRegisterMetaType<Measurement>("Measurement");
125
+
126
+@ A little more is required to use |Measurement| objects in scripts.
127
+
128
+@<Class declarations@>=
129
+Q_DECLARE_METATYPE(Measurement)
130
+
131
+@ The only thing unusual here is the conversion to and from script values.
132
+
133
+@<Function prototypes for scripting@>=
134
+QScriptValue constructMeasurement(QScriptContext *context, QScriptEngine *engine);
135
+void setMeasurementProperties(QScriptValue value, QScriptEngine *engine);
136
+QScriptValue Measurement_toScriptValue(QScriptEngine *engine, const Measurement &measurement);
137
+void Measurement_fromScriptValue(const QScriptValue &value, Measurement &measurement);
138
+
139
+@ This follows much the same pattern as other types not derived from |QObject|.
140
+
141
+@<Set up the scripting engine@>=
142
+constructor = engine->newFunction(constructMeasurement);
143
+engine->globalObject().setProperty("Measurement", constructor);
144
+qScriptRegisterMetaType(engine, Measurement_toScriptValue, Measurement_fromScriptValue);
145
+
146
+@ The constructor takes two or three arguments. If the third arguement is not
147
+supplied, we will assume that the measurements are in degrees Fahrenheit.
148
+
149
+@<Functions for scripting@>=
150
+QScriptValue constructMeasurement(QScriptContext *context, QScriptEngine *engine)
151
+{
152
+	QScriptValue object;
153
+	if(context->argumentCount() == 2 || context->argumentCount() == 3)
154
+	{
155
+		double measurement = argument<double>(0, context);
156
+		QTime timestamp = argument<QTime>(1, context);
157
+		Units::Unit unit = Units::Fahrenheit;
158
+		if(context->argumentCount() == 3)
159
+		{
160
+			unit = argument<Units::Unit>(2, context);
161
+		}
162
+		object = engine->toScriptValue<Measurement>(Measurement(measurement, timestamp, unit));
163
+		setMeasurementProperties(object, engine);
164
+	}
165
+	else
166
+	{
167
+		context->throwError("Incorrect number of arguments passed to "@|
168
+		                    "Measurement::Measurement(). This method takes two "@|
169
+		                    "or three arguments.");
170
+	}
171
+	return object;
172
+}
173
+
174
+@ No additional properties are currently needed, but if they were, they would go here.
175
+
176
+@<Functions for scripting@>=
177
+void setMeasurementProperties(QScriptValue, QScriptEngine *)
178
+{
179
+	/* Nothing needs to be done here. */
180
+}
181
+
182
+@ The script value conversions are reasonably straightforward.
183
+
184
+@<Functions for scripting@>=
185
+QScriptValue Measurement_toScriptValue(QScriptEngine *engine, const Measurement &measurement)
186
+{
187
+	QVariant var;
188
+	var.setValue(measurement);
189
+	return engine->newVariant(var);
190
+}
191
+
192
+void Measurement_fromScriptValue(const QScriptValue &value, Measurement &measurement)
193
+{
194
+	measurement = value.toVariant().value<Measurement>();
195
+}

+ 157
- 5
src/typica.w Просмотреть файл

@@ -679,6 +679,12 @@ template<> QTime getself(QScriptContext *context)
679 679
 	return self;
680 680
 }
681 681
 
682
+template<> QByteArray getself(QScriptContext *context)
683
+{
684
+	QByteArray self = context->thisObject().toVariant().toByteArray();
685
+	return self;
686
+}
687
+
682 688
 template<> SqlQueryConnection* getself(QScriptContext *context)
683 689
 {
684 690
 	SqlQueryConnection *self =@|
@@ -754,6 +760,11 @@ template<> Units::Unit argument(int arg, QScriptContext *context)
754 760
 	return (Units::Unit)(context->argument(arg).toInt32());
755 761
 }
756 762
 
763
+template<> QByteArray argument(int arg, QScriptContext *context)
764
+{
765
+	return qscriptvalue_cast<QByteArray>(context->argument(arg));
766
+}
767
+
757 768
 @ The scripting engine is informed of a number of classes defined elsewhere in
758 769
 the program. Code related to scripting these classes is grouped with the code
759 770
 implementing the classes. Additionally, there are several classes from Qt which
@@ -1858,6 +1869,9 @@ QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *engine);
1858 1869
 QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *engine);
1859 1870
 QScriptValue QIODevice_readToString(QScriptContext *context,
1860 1871
                                     QScriptEngine *engine);
1872
+QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *engine);
1873
+QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *engine);
1874
+QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *engine);
1861 1875
 
1862 1876
 @ This function is passed to the scripting engine.
1863 1877
 
@@ -1909,6 +1923,9 @@ void setQIODeviceProperties(QScriptValue value, QScriptEngine *engine)
1909 1923
 	value.setProperty("close", engine->newFunction(QIODevice_close));
1910 1924
 	value.setProperty("readToString",
1911 1925
 	                  engine->newFunction(QIODevice_readToString));
1926
+	value.setProperty("putChar", engine->newFunction(QIODevice_putChar));
1927
+	value.setProperty("writeString", engine->newFunction(QIODevice_writeString));
1928
+	value.setProperty("writeBytes", engine->newFunction(QIODevice_writeBytes));
1912 1929
 }
1913 1930
 
1914 1931
 @ These are simple wrappers. In the case of the |open()| property, one argument
@@ -1920,18 +1937,19 @@ not passed, it is assumed that the user wants read and write access.
1920 1937
 QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *)
1921 1938
 {
1922 1939
 	QIODevice *self = getself<QIODevice *>(context);
1940
+	bool retval = false;
1923 1941
 	if(context->argumentCount() == 1)
1924 1942
 	{
1925 1943
 		switch(argument<int>(0, context))
1926 1944
 		{
1927 1945
 			case 1:
1928
-				self->open(QIODevice::ReadOnly);
1946
+				retval = self->open(QIODevice::ReadOnly);
1929 1947
 				break;
1930 1948
 			case 2:
1931
-				self->open(QIODevice::WriteOnly);
1949
+				retval = self->open(QIODevice::WriteOnly);
1932 1950
 				break;
1933 1951
 			case 3:
1934
-				self->open(QIODevice::ReadWrite);
1952
+				retval = self->open(QIODevice::ReadWrite);
1935 1953
 				break;
1936 1954
 			default:
1937 1955
 				break;
@@ -1939,9 +1957,9 @@ QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *)
1939 1957
 	}
1940 1958
 	else
1941 1959
 	{
1942
-		self->open(QIODevice::ReadWrite);
1960
+		retval = self->open(QIODevice::ReadWrite);
1943 1961
 	}
1944
-	return QScriptValue();
1962
+	return QScriptValue(retval);
1945 1963
 }
1946 1964
 
1947 1965
 QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *)
@@ -1963,6 +1981,128 @@ QScriptValue QIODevice_readToString(QScriptContext *context, QScriptEngine *)
1963 1981
 	return QScriptValue(QString(self->readAll()));
1964 1982
 }
1965 1983
 
1984
+@ In support of serial port communications, wrappers around two methods for
1985
+writing data have been added. As these are valid for other classes derived from
1986
+|QIODevice|, they are added here so the functionality is available more
1987
+broadly.
1988
+
1989
+As we are unable to pass a type that guarantees only a single character, we
1990
+instead accept a string and only pass along the first character.
1991
+
1992
+@<Functions for scripting@>=
1993
+QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *)
1994
+{
1995
+	QIODevice *self = getself<QIODevice *>(context);
1996
+	if(context->argumentCount() == 1)
1997
+	{
1998
+		return QScriptValue(self->putChar(argument<QString>(0, context).toUtf8().at(0)));
1999
+	}
2000
+	context->throwError("Incorrect number of arguments passed to "@|
2001
+	                    "QIODevice::putChar()");
2002
+	return QScriptValue();
2003
+}
2004
+
2005
+@ Two wrappers are provided around |QIODevice::write()| for outputting
2006
+multi-byte data. If we are writing strings that are valid UTF-8, we can use the
2007
+|writeString| wrapper, but if we require full control over exactly which bytes
2008
+are output, the |writeBytes| wrapper is more appropriate.
2009
+
2010
+@<Functions for scripting@>=
2011
+QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *)
2012
+{
2013
+	QIODevice *self = getself<QIODevice *>(context);
2014
+	if(context->argumentCount() == 1)
2015
+	{
2016
+		self->write(argument<QString>(0, context).toUtf8());
2017
+	}
2018
+	else
2019
+	{
2020
+		context->throwError("Incorrect number of arguments passed to "@|
2021
+	                        "QIODevice::writeString()");
2022
+	}
2023
+	return QScriptValue();
2024
+}
2025
+
2026
+QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *)
2027
+{
2028
+	QIODevice *self = getself<QIODevice *>(context);
2029
+	if(context->argumentCount() == 1)
2030
+	{
2031
+		self->write(argument<QByteArray>(0, context));
2032
+	}
2033
+	else
2034
+	{
2035
+		context->throwError("Incorrect number of arguments passed to "@|
2036
+	                        "QIODevice::writeBytes()");
2037
+	}
2038
+	return QScriptValue();
2039
+}
2040
+
2041
+@ In order to work with |QByteArray| this should also be exposed to the host
2042
+environment.
2043
+
2044
+@<Function prototypes for scripting@>=
2045
+QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes);
2046
+void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes);
2047
+QScriptValue constructQByteArray(QScriptContext *context, QScriptEngine *engine);
2048
+void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine);
2049
+QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine);
2050
+
2051
+@ First, we provide some functionns for moving array data across the
2052
+language barrier.
2053
+
2054
+@<Functions for scripting@>=
2055
+QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes)
2056
+{
2057
+	QScriptValue object = engine->newVariant(QVariant(bytes));
2058
+	setQByteArrayProperties(object, engine);
2059
+	return object;
2060
+}
2061
+
2062
+void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes)
2063
+{
2064
+	bytes = value.toVariant().toByteArray();
2065
+}
2066
+
2067
+@ We register this our conversion functions and allow creation of new arrays
2068
+next.
2069
+
2070
+@<Set up the scripting engine@>=
2071
+qScriptRegisterMetaType(engine, QByteArray_toScriptValue, QByteArray_fromScriptValue);
2072
+constructor = engine->newFunction(constructQByteArray);
2073
+engine->globalObject().setProperty("QByteArray", constructor);
2074
+
2075
+@ The constructor is straightforward.
2076
+
2077
+@<Functions for scripting@>=
2078
+QScriptValue constructQByteArray(QScriptContext *, QScriptEngine *engine)
2079
+{
2080
+	QScriptValue object = engine->toScriptValue<QByteArray>(QByteArray());
2081
+	setQByteArrayProperties(object, engine);
2082
+	return object;
2083
+}
2084
+
2085
+@ There are many methods which are not automatically available which we may
2086
+want to have wrappers around. These should be added as required.
2087
+
2088
+@<Functions for scripting@>=
2089
+void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine)
2090
+{
2091
+	value.setProperty("fromHex", engine->newFunction(QByteArray_fromHex));
2092
+}
2093
+
2094
+@ Perhaps the easiest way to deal with fixed byte strings for serial
2095
+communications across script boundaries is to use a hex encoded string.
2096
+
2097
+@<Functions for scripting@>=
2098
+QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine)
2099
+{
2100
+	QByteArray self = getself<QByteArray>(context);
2101
+	QByteArray retval;
2102
+	retval = self.fromHex(argument<QString>(0, context).toUtf8());
2103
+	return engine->toScriptValue<QByteArray>(retval);
2104
+}
2105
+
1966 2106
 @* Scripting QBuffer.
1967 2107
 
1968 2108
 \noindent Sometimes it is desirable to load a roast profile from a file. At
@@ -3034,6 +3174,15 @@ QScriptValue QTime_fromString(QScriptContext *context, QScriptEngine *engine)
3034 3174
 	return object;
3035 3175
 }
3036 3176
 
3177
+@ In order to pass |QTime| objects back from a script, we also need to overload
3178
+|argument()| for this type.
3179
+
3180
+@<Functions for scripting@>=
3181
+template<> QTime argument(int arg, QScriptContext *context)
3182
+{
3183
+	return qscriptvalue_cast<QTime>(context->argument(arg));
3184
+}
3185
+
3037 3186
 @* Scripting Item View Classes.
3038 3187
 
3039 3188
 \noindent |QAbstractScrollArea| is a |QFrame| that serves as the base class for
@@ -14971,6 +15120,7 @@ DeviceConfigurationWindow::DeviceConfigurationWindow() : QWidget(NULL),
14971 15120
 	splitter->addWidget(leftWidget);
14972 15121
 	configArea->setMinimumWidth(580);
14973 15122
 	configArea->setMinimumHeight(460);
15123
+	configArea->setWidgetResizable(true);
14974 15124
 	splitter->addWidget(configArea);
14975 15125
 	QVBoxLayout *centralLayout = new QVBoxLayout;
14976 15126
 	centralLayout->addWidget(splitter);
@@ -18204,6 +18354,8 @@ app.registerDeviceConfigurationWidget("modbusrtu", ModbusConfigurator::staticMet
18204 18354
 inserter = new NodeInserter(tr("Modbus RTU Device"), tr("Modbus RTU Device"), "modbusrtu", NULL);
18205 18355
 topLevelNodeInserters.append(inserter);
18206 18356
 
18357
+@i unsupportedserial.w
18358
+
18207 18359
 @* Configuration widget for a calibrated data series.
18208 18360
 
18209 18361
 \noindent This control is used for adding a |LinearSplineInterpolator| to the

+ 695
- 0
src/unsupportedserial.w Просмотреть файл

@@ -0,0 +1,695 @@
1
+@** Script Driven Devices.
2
+
3
+\noindent There are many data acquisition products that are reasonable to use
4
+with \pn which are not natively supported due to lack of available hardware
5
+for testing, lack of time or money to develop that support, or lack of
6
+documentation. It has also become relatively simple for hardware tinkerers to
7
+develop new devices matching this description as well. Vendors in this space
8
+tend to give inadequate consideration to interoperability and with some devices
9
+the communications protocol used changes significantly between firmware
10
+revisions. There are simply far too many devices like this to support
11
+everything. At the same time there are people with these devices who are
12
+capable of programming the basic communications handling but have difficulty
13
+with integrating that with \pn. By providing an in-program environment that
14
+handles much of the boilerplate and allowing people to write scripts
15
+implementing these protocols without the need to modify core \pn code or
16
+recompile the program, these people may find it easier to make their existing
17
+hardware work. Such scripts can also serve as prototypes for native support.
18
+
19
+Configuration widgets for these devices allow key value pairs to be specified
20
+both at the device level and on a per-channel basis. This is intentionally kept
21
+generic as it is impossible to know what configurable details may be required.
22
+Common configurations will have a device node representing a single logical
23
+device, usually a single physical device but this is not in any way enforced,
24
+and one child node per channel. These details are made available to the device
25
+script and are used to integrate with the logging view.
26
+
27
+Some of the naming conventions used here are legacy of the initial conception
28
+of this feature and should be changed before release if there is time to do so.
29
+While initial support will be focused on devices that present as a serial port,
30
+there is no reason this could not be extended to cover devices that are
31
+interfaced through USB HID, Bluetooth, COM, output piped from an external
32
+console program, devices interfaced through arbitrary libraries, or any other
33
+class of device not directly supported in the core code should there be an
34
+interest in any of these.
35
+
36
+@<Class declarations@>=
37
+class UnsupportedSerialDeviceConfWidget : public BasicDeviceConfigurationWidget
38
+{
39
+	Q_OBJECT
40
+	public:
41
+		Q_INVOKABLE UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
42
+		                                              const QModelIndex &index);
43
+	private slots:
44
+		void updateConfiguration();
45
+		void saveScript();
46
+		void addChannel();
47
+	private:
48
+		SaltModel *deviceSettingsModel;
49
+		QTextEdit *scriptEditor;
50
+};
51
+
52
+@ The device configuration widget consists of two tabs. One tab provides a
53
+button for adding channels and an area for entering device specific
54
+configuration details. The other provides an area for entering the device
55
+script. This may be extended later to provide better editing, testing, and
56
+debugging support, but the initial concern is simply having a working feature.
57
+
58
+@<UnsupportedSerialDeviceConfWidget implementation@>=
59
+UnsupportedSerialDeviceConfWidget::UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
60
+                                                                     const QModelIndex &index)
61
+	: BasicDeviceConfigurationWidget(model, index),
62
+	deviceSettingsModel(new SaltModel(2)),
63
+	scriptEditor(new QTextEdit)
64
+{
65
+	scriptEditor->setTabStopWidth(20);
66
+	QVBoxLayout *dummyLayout = new QVBoxLayout;
67
+	QTabWidget *central = new QTabWidget;
68
+	QWidget *deviceConfigurationWidget = new QWidget;
69
+	QVBoxLayout *deviceConfigurationLayout = new QVBoxLayout;
70
+	QPushButton *addChannelButton = new QPushButton(tr("Add Channel"));
71
+	deviceConfigurationLayout->addWidget(addChannelButton);
72
+	connect(addChannelButton, SIGNAL(clicked()), this, SLOT(addChannel()));
73
+	QLabel *deviceSettingsLabel = new QLabel(tr("Device Settings:"));
74
+	deviceConfigurationLayout->addWidget(deviceSettingsLabel);
75
+	QTableView *deviceSettingsView = new QTableView;
76
+	deviceSettingsModel->setHeaderData(0, Qt::Horizontal, tr("Key"));
77
+	deviceSettingsModel->setHeaderData(1, Qt::Horizontal, tr("Value"));
78
+	deviceSettingsView->setModel(deviceSettingsModel);
79
+	deviceConfigurationLayout->addWidget(deviceSettingsView);
80
+	
81
+	deviceConfigurationWidget->setLayout(deviceConfigurationLayout);
82
+	central->addTab(deviceConfigurationWidget, tr("Configuration"));
83
+	central->addTab(scriptEditor, tr("Script"));
84
+	dummyLayout->addWidget(central);
85
+	
86
+	@<Get device configuration data for current node@>@;
87
+	for(int i = 0; i < configData.size(); i++)
88
+	{
89
+		node = configData.at(i).toElement();
90
+		if(node.attribute("name") == "keys" || node.attribute("name") == "values")
91
+		{
92
+			int column = 0;
93
+			if(node.attribute("name") == "values")
94
+			{
95
+				column = 1;
96
+			}
97
+			QString data = node.attribute("value");
98
+			if(data.length() > 3)
99
+			{
100
+				data.chop(2);
101
+				data = data.remove(0, 2);
102
+			}
103
+			QStringList keyList = data.split(", ");
104
+			for(int j = 0; j < keyList.size(); j++)
105
+			{
106
+				deviceSettingsModel->setData(deviceSettingsModel->index(j, column),
107
+				                             QVariant(keyList.at(j)),
108
+				                             Qt::EditRole);
109
+			}
110
+		}
111
+		else if(node.attribute("name") == "script")
112
+		{
113
+			scriptEditor->setPlainText(node.attribute("value"));
114
+		}
115
+	}
116
+	
117
+	connect(deviceSettingsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
118
+	        this, SLOT(updateConfiguration()));
119
+	connect(scriptEditor, SIGNAL(textChanged()), this, SLOT(saveScript()));
120
+	setLayout(dummyLayout);
121
+}
122
+
123
+@ Device configuration data is entered through an ordinary |QTableView| with a
124
+|SaltModel| backing. The original use case for that model does not apply here,
125
+but that model does ensure that additional blank rows are added as needed so
126
+that arbitrarily many key value pairs can be entered. When data changes in the
127
+model we write the full content of the model out. Note that commas may not be
128
+used in keys or values. For keys in which lists make sense, a different
129
+delimiter must be chosen.
130
+
131
+@<UnsupportedSerialDeviceConfWidget implementation@>=
132
+void UnsupportedSerialDeviceConfWidget::updateConfiguration()
133
+{
134
+	updateAttribute("keys", deviceSettingsModel->arrayLiteral(0, Qt::DisplayRole));
135
+	updateAttribute("values", deviceSettingsModel->arrayLiteral(1, Qt::DisplayRole));
136
+}
137
+
138
+@ Every time the script text is changed, the new version of the script is
139
+saved. My expectation is that scripts will either be small or that they will be
140
+pasted in from outside of \pn so that this decision will not cause usability
141
+issues, however if I am wrong about this there may be a need to handle this
142
+more intelligently.
143
+
144
+@<UnsupportedSerialDeviceConfWidget implementation@>=
145
+void UnsupportedSerialDeviceConfWidget::saveScript()
146
+{
147
+	updateAttribute("script", scriptEditor->toPlainText());
148
+}
149
+
150
+@ Typica requires channel nodes to simplify integration with other existing
151
+device code. Providing a new node type allows arbitrary attributes to be
152
+configured on a per-channel basis without resorting to strange conventions in
153
+the device configuration.
154
+
155
+@<UnsupportedSerialDeviceConfWidget implementation@>=
156
+void UnsupportedSerialDeviceConfWidget::addChannel()
157
+{
158
+	insertChildNode(tr("Channel"), "unsupporteddevicechannel");
159
+}
160
+
161
+@ Channel configuration for unsupported devices is like unsupported device
162
+configuration in that arbitrary key value pairs may be entered for use by the
163
+device script. Conventions common to all other channel node types are also
164
+present here.
165
+
166
+@<Class declarations@>=
167
+class UnsupportedDeviceChannelConfWidget : public BasicDeviceConfigurationWidget
168
+{
169
+	Q_OBJECT
170
+	public:
171
+		Q_INVOKABLE UnsupportedDeviceChannelConfWidget(DeviceTreeModel *model,
172
+		                                               const QModelIndex &index);
173
+	private slots:
174
+		void updateColumnName(const QString &value);
175
+		void updateHidden(bool hidden);
176
+		void updateConfiguration();
177
+	private:
178
+		SaltModel *channelSettingsModel;
179
+};
180
+
181
+@ The constructor is typical for for channel node configuraion widgets.
182
+
183
+@<UnsupportedSerialDeviceConfWidget implementation@>=
184
+UnsupportedDeviceChannelConfWidget::UnsupportedDeviceChannelConfWidget(DeviceTreeModel *model,
185
+                                                                       const QModelIndex &index)
186
+	: BasicDeviceConfigurationWidget(model, index),
187
+	channelSettingsModel(new SaltModel(2))
188
+{
189
+	QFormLayout *layout = new QFormLayout;
190
+	QLineEdit *columnName = new QLineEdit;
191
+	layout->addRow(tr("Column Name:"), columnName);
192
+	QCheckBox *hideSeries = new QCheckBox("Hide this channel");
193
+	layout->addRow(hideSeries);
194
+	QTableView *channelSettings = new QTableView;
195
+	channelSettingsModel->setHeaderData(0, Qt::Horizontal, "Key");
196
+	channelSettingsModel->setHeaderData(1, Qt::Horizontal, "Value");
197
+	channelSettings->setModel(channelSettingsModel);
198
+	layout->addRow(channelSettings);
199
+	setLayout(layout);
200
+	@<Get device configuration data for current node@>@;
201
+	for(int i = 0; i < configData.size(); i++)
202
+	{
203
+		node = configData.at(i).toElement();
204
+		if(node.attribute("name") == "columnname")
205
+		{
206
+			columnName->setText(node.attribute("value"));
207
+		}
208
+		else if(node.attribute("name") == "hidden")
209
+		{
210
+			hideSeries->setChecked(node.attribute("value") == "true");
211
+		}
212
+		else if(node.attribute("name") == "keys" || node.attribute("name") == "values")
213
+		{
214
+			int column = 0;
215
+			if(node.attribute("name") == "values")
216
+			{
217
+				column = 1;
218
+			}
219
+			QString data = node.attribute("value");
220
+			if(data.length() > 3)
221
+			{
222
+				data.chop(2);
223
+				data = data.remove(0, 2);
224
+			}
225
+			QStringList keyList = data.split(", ");
226
+			for(int j = 0; j < keyList.size(); j++)
227
+			{
228
+				channelSettingsModel->setData(channelSettingsModel->index(j, column),
229
+				                              QVariant(keyList.at(j)),
230
+				                              Qt::EditRole);
231
+			}
232
+		}
233
+	}
234
+	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
235
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
236
+	connect(channelSettingsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
237
+	        this, SLOT(updateConfiguration()));
238
+}
239
+
240
+@ Arbitrary channel configuration data is handled in the same way as device
241
+level settings while the column name and hidden status is handled in the same
242
+way as they are in other channel nodes.
243
+
244
+@<UnsupportedSerialDeviceConfWidget implementation@>=
245
+void UnsupportedDeviceChannelConfWidget::updateColumnName(const QString &value)
246
+{
247
+	updateAttribute("columnname", value);
248
+}
249
+
250
+void UnsupportedDeviceChannelConfWidget::updateHidden(bool hidden)
251
+{
252
+	updateAttribute("hidden", hidden ? "true" : "false");
253
+}
254
+
255
+void UnsupportedDeviceChannelConfWidget::updateConfiguration()
256
+{
257
+	updateAttribute("keys", channelSettingsModel->arrayLiteral(0, Qt::DisplayRole));
258
+	updateAttribute("values", channelSettingsModel->arrayLiteral(1, Qt::DisplayRole));
259
+}
260
+
261
+@ The configuration widgets need to be registered so they can be instantiated
262
+as appropriate.
263
+
264
+@<Register device configuration widgets@>=
265
+app.registerDeviceConfigurationWidget("unsupporteddevicechannel",
266
+	UnsupportedDeviceChannelConfWidget::staticMetaObject);
267
+app.registerDeviceConfigurationWidget("unsupporteddevice",
268
+	UnsupportedSerialDeviceConfWidget::staticMetaObject);
269
+
270
+@ A |NodeInserter| for the device node is also provided.
271
+
272
+@<Register top level device configuration nodes@>=
273
+inserter = new NodeInserter(tr("Other Device"), tr("Other Device"),
274
+	"unsupporteddevice", NULL);
275
+topLevelNodeInserters.append(inserter);
276
+
277
+@ A device abstraction is not strictly required for this feature, however
278
+having one greatly simplifies integrating this feature. At some point I would
279
+like to revise other device abstraction classes so that a huge amount of
280
+boilerplate associated with these can be removed from configuration documents.
281
+
282
+This device abstraction includes features in a few particular categories. First
283
+there are methods that are required for integrating the device with the logging
284
+view. The logging view instantiates the device abstraction, passing in the
285
+configuration data required to properly set up the device. It then is able to
286
+query information about the measurement channels that have been configured for
287
+this device and can set up all of the relevant indicators. Some device classes
288
+may be able to produce annotations, so this class can be treated exactly the
289
+same as any other annotation source. Another requested feature includes the
290
+ability of a device to trigger the start and end of batches, so signals are
291
+provided for this capability. Finally, there are methods associated with
292
+starting and stopping the device. The |start()| method will be called when the
293
+logging view has finished making all of the signal connections. The |stop()|
294
+method will be called when the logging view is closed, giving script code the
295
+chance to cleanly release any resources that must be held for device
296
+communications.
297
+
298
+@<Class declarations@>=
299
+class JavaScriptDevice : public QObject
300
+{
301
+	Q_OBJECT
302
+	public:
303
+		Q_INVOKABLE JavaScriptDevice(const QModelIndex &deviceIndex,
304
+		                             QScriptEngine *engine);
305
+		Q_INVOKABLE int channelCount();
306
+		Channel* getChannel(int channel);
307
+		Q_INVOKABLE bool isChannelHidden(int channel);
308
+		Q_INVOKABLE Units::Unit expectedChannelUnit(int channel);
309
+		Q_INVOKABLE QString channelColumnName(int channel);
310
+		Q_INVOKABLE QString channelIndicatorText(int channel);
311
+	public slots:
312
+		void setTemperatureColumn(int tcol);
313
+		void setAnnotationColumn(int ncol);
314
+		void start();
315
+		void stop();
316
+	signals:
317
+		void annotation(QString note, int tcol, int ncol);
318
+		void triggerStartBatch();
319
+		void triggerStopBatch();
320
+		void deviceStopRequested();
321
+	private:
322
+		QVariantMap deviceSettings;
323
+		QString deviceScript;
324
+		QList<Channel *> channelList;
325
+		QList<bool> hiddenState;
326
+		QList<Units::Unit> channelUnits;
327
+		QList<QString> columnNames;
328
+		QList<QString> indicatorTexts;
329
+		QList<QVariantMap> channelSettings;
330
+		int annotationTemperatureColumn;
331
+		int annotationNoteColumn;
332
+		QScriptEngine *scriptengine;
333
+};
334
+
335
+@ The |JavaScriptDevice| instance provides two interfaces. Its invokable
336
+methods provide the information needed to integrate script driven devices with
337
+a generic logging view. Additional information is also exposed through the host
338
+environment running the device script. This means that the class requires
339
+knowledge of the host environment, which it obtains through a script function
340
+similar to what is done for window creation.
341
+
342
+The name of the function is generic so this may be easily extended later to
343
+create all device abstraction instances.
344
+
345
+@<Function prototypes for scripting@>=
346
+QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine);
347
+
348
+@ That method is made available to the scripting engine.
349
+
350
+@<Set up the scripting engine@>=
351
+engine->globalObject().setProperty("createDevice",
352
+                                   engine->newFunction(createDevice));
353
+
354
+@ This function currently creates a |JavaScriptDevice| from a device
355
+configuration node which must be passed through as an argument.
356
+
357
+@<Functions for scripting@>=
358
+QScriptValue createDevice(QScriptContext *context, QScriptEngine *engine)@/
359
+{
360
+	QModelIndex deviceIndex = argument<QModelIndex>(0, context);
361
+	JavaScriptDevice *device = new JavaScriptDevice(deviceIndex, engine);
362
+	QScriptValue object = engine->newQObject(device);
363
+	setQObjectProperties(object, engine);
364
+	object.setProperty("getChannel", engine->newFunction(JavaScriptDevice_getChannel));
365
+	return object;
366
+}
367
+
368
+@ The |start()| method is responsible for preparing the host environment and
369
+executing the device script.
370
+
371
+@<JavaScriptDevice implementation@>=
372
+void JavaScriptDevice::start()
373
+{
374
+	QScriptValue object = scriptengine->newQObject(this);
375
+	@<Expose device settings as object property@>@;
376
+	@<Expose channels and channel settings to device script@>@;
377
+	QScriptContext *context = scriptengine->currentContext();
378
+	QScriptValue oldThis = context->thisObject();
379
+	context->setThisObject(object);
380
+	QScriptValue result = scriptengine->evaluate(deviceScript);
381
+	QScriptEngine *engine = scriptengine;
382
+	@<Report scripting errors@>@;
383
+	context->setThisObject(oldThis);
384
+}
385
+
386
+@ Device settings are only needed from the device script itself. As such, these
387
+are presented under a settings property available from the |this| object when
388
+the script is run.
389
+
390
+@<Expose device settings as object property@>=
391
+QScriptValue settingsObject = scriptengine->newObject();
392
+QVariantMap::const_iterator i = deviceSettings.constBegin();
393
+while(i != deviceSettings.constEnd())
394
+{
395
+	settingsObject.setProperty(i.key(), i.value().toString());
396
+	i++;
397
+}
398
+object.setProperty("settings", settingsObject);
399
+
400
+@ While channels are available to the device script through the same
401
+|getChannel()| interface used outside of the device script for integration
402
+purposes, it is more convenient to have an array of channels with channel
403
+specific settings as properties of the channel.
404
+
405
+@<Expose channels and channel settings to device script@>=
406
+QScriptValue channelsArray = scriptengine->newArray(channelCount());
407
+for(int i = 0; i < channelCount(); i++)
408
+{
409
+	QScriptValue channelObject = scriptengine->newQObject(getChannel(i));
410
+	QScriptValue channelSettingsObject = scriptengine->newObject();
411
+	QVariantMap::const_iterator j = channelSettings.at(i).constBegin();
412
+	while(j != channelSettings.at(i).constEnd())
413
+	{
414
+		channelSettingsObject.setProperty(j.key(), j.value().toString());
415
+		j++;
416
+	}
417
+	channelObject.setProperty("settings", channelSettingsObject);
418
+	channelsArray.setProperty(i, channelObject);
419
+}
420
+object.setProperty("channels", channelsArray);
421
+
422
+@ Currently we require wrapper functions to work with channels in the host
423
+environment.
424
+
425
+@<Function prototypes for scripting@>=
426
+QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine);
427
+
428
+@ The implementation is trivial.
429
+
430
+@<Functions for scripting@>=
431
+QScriptValue JavaScriptDevice_getChannel(QScriptContext *context, QScriptEngine *engine)
432
+{
433
+	JavaScriptDevice *self = getself<JavaScriptDevice *>(context);
434
+	QScriptValue object;
435
+	if(self)
436
+	{
437
+		object = engine->newQObject(self->getChannel(argument<int>(0, context)));
438
+		setChannelProperties(object, engine);
439
+	}
440
+	return object;
441
+}
442
+
443
+@ The |stop()| method just fires off a signal that the script can hook into for
444
+any required cleanup.
445
+
446
+@<JavaScriptDevice implementation@>=
447
+void JavaScriptDevice::stop()
448
+{
449
+	emit deviceStopRequested();
450
+}
451
+
452
+@ The constructor is responsible for all boilerplate initialization required
453
+for integrating script defined devices with the logging view.
454
+
455
+Note: At present expected units are assumed to be Fahrenheit. The configuration
456
+widget must be updated to allow at least for control measurements and
457
+eventually support for runtime defined units should also be added.
458
+
459
+@<JavaScriptDevice implementation@>=
460
+JavaScriptDevice::JavaScriptDevice(const QModelIndex &index,
461
+                                   QScriptEngine *engine) :
462
+	QObject(NULL), scriptengine(engine)
463
+{
464
+	DeviceTreeModel *model = (DeviceTreeModel *)(index.model());
465
+	QDomElement deviceReferenceElement =
466
+		model->referenceElement(model->data(index, Qt::UserRole).toString());
467
+	QDomNodeList deviceConfigData = deviceReferenceElement.elementsByTagName("attribute");
468
+	QDomElement node;
469
+	QStringList deviceKeys;
470
+	QStringList deviceValues;
471
+	for(int i = 0; i < deviceConfigData.size(); i++)
472
+	{
473
+		node = deviceConfigData.at(i).toElement();
474
+		if(node.attribute("name") == "keys")
475
+		{
476
+			QString data = node.attribute("value");
477
+			if(data.length() > 3)
478
+			{
479
+				data.chop(2);
480
+				data = data.remove(0, 2);
481
+			}
482
+			deviceKeys = data.split(", ");
483
+		}
484
+		else if(node.attribute("name") == "values")
485
+		{
486
+			QString data = node.attribute("value");
487
+			if(data.length() > 3)
488
+			{
489
+				data.chop(2);
490
+				data = data.remove(0, 2);
491
+			}
492
+			deviceValues = data.split(", ");
493
+		}
494
+		else if(node.attribute("name") == "script")
495
+		{
496
+			deviceScript = node.attribute("value");
497
+		}
498
+		deviceSettings.insert(node.attribute("name"), node.attribute("value"));
499
+	}
500
+	for(int i = 0; i < qMin(deviceKeys.length(), deviceValues.length()); i++)
501
+	{
502
+		deviceSettings.insert(deviceKeys[i], deviceValues[i]);
503
+	}
504
+	if(model->hasChildren(index))
505
+	{
506
+		for(int i = 0; i < model->rowCount(index); i++)
507
+		{
508
+			QModelIndex channelIndex = model->index(i, 0, index);
509
+			QDomElement channelReference = model->referenceElement(model->data(channelIndex, 32).toString());
510
+			channelList.append(new Channel);
511
+			QDomElement channelReferenceElement =
512
+				model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
513
+			QDomNodeList channelConfigData =
514
+				channelReferenceElement.elementsByTagName("attribute");
515
+			QStringList channelKeys;
516
+			QStringList channelValues;
517
+			for(int j = 0; j < channelConfigData.size(); j++)
518
+			{
519
+				node = channelConfigData.at(j).toElement();
520
+				if(node.attribute("name") == "keys")
521
+				{
522
+					QString data = node.attribute("value");
523
+					if(data.length() > 3)
524
+					{
525
+						data.chop(2);
526
+						data = data.remove(0, 2);
527
+					}
528
+					channelKeys = data.split(", ");
529
+				}
530
+				else if(node.attribute("name") == "values")
531
+				{
532
+					QString data = node.attribute("value");
533
+					if(data.length() > 3)
534
+					{
535
+						data.chop(2);
536
+						data = data.remove(0, 2);
537
+					}
538
+					channelValues = data.split(", ");
539
+				}
540
+				else if(node.attribute("name") == "hidden")
541
+				{
542
+					hiddenState.append(node.attribute("value") == "true");
543
+				}
544
+				else if(node.attribute("name") == "columnname")
545
+				{
546
+					columnNames.append(node.attribute("value"));
547
+				}
548
+			}
549
+			QVariantMap cs;
550
+			for(int j = 0; j < qMin(channelKeys.length(), channelValues.length()); j++)
551
+			{
552
+				cs.insert(channelKeys[j], channelValues[j]);
553
+			}
554
+			channelSettings.append(cs);
555
+			indicatorTexts.append(model->data(channelIndex, Qt::DisplayRole).toString());
556
+			channelUnits.append(Units::Fahrenheit);
557
+		}
558
+	}
559
+}
560
+
561
+@ Several methods are available to query information about the configured
562
+channels.
563
+
564
+@<JavaScriptDevice implementation@>=
565
+int JavaScriptDevice::channelCount()
566
+{
567
+	return channelList.length();
568
+}
569
+
570
+Channel* JavaScriptDevice::getChannel(int channel)
571
+{
572
+	return channelList.at(channel);
573
+}
574
+
575
+bool JavaScriptDevice::isChannelHidden(int channel)
576
+{
577
+	return hiddenState.at(channel);
578
+}
579
+
580
+Units::Unit JavaScriptDevice::expectedChannelUnit(int channel)
581
+{
582
+	return channelUnits.at(channel);
583
+}
584
+
585
+QString JavaScriptDevice::channelColumnName(int channel)
586
+{
587
+	if(channel >= 0 && channel < columnNames.length())
588
+	{
589
+		return columnNames.at(channel);
590
+	}
591
+	return QString();
592
+}
593
+
594
+QString JavaScriptDevice::channelIndicatorText(int channel)
595
+{
596
+	return indicatorTexts.at(channel);
597
+}
598
+
599
+@ Two slots are provided for controlling the placement of annotations.
600
+
601
+@<JavaScriptDevice implementation@>=
602
+void JavaScriptDevice::setTemperatureColumn(int tcol)
603
+{
604
+	annotationTemperatureColumn = tcol;
605
+}
606
+
607
+void JavaScriptDevice::setAnnotationColumn(int ncol)
608
+{
609
+	annotationNoteColumn = ncol;
610
+}
611
+
612
+@ Device scripts must be able to produce measurements on a channel. To do this,
613
+a function is provided for obtaining a timestamp. The returned timestamp should
614
+not be examined as future changes may break assumptions about the content of
615
+the timestamp.
616
+
617
+@<Function prototypes for scripting@>=
618
+QScriptValue getMeasurementTimestamp(QScriptContext *context, QScriptEngine *engine);
619
+
620
+@ That method is made available to the scripting engine.
621
+
622
+@<Set up the scripting engine@>=
623
+engine->globalObject().setProperty("getMeasurementTimestamp",
624
+                                   engine->newFunction(getMeasurementTimestamp));
625
+
626
+@ At present this simply obtains the current system time. It is planned to
627
+switch to a better quality clock in the future, but this should be done for
628
+everything that uses |Measurement| objects at once.
629
+
630
+@<Functions for scripting@>=
631
+QScriptValue getMeasurementTimestamp(QScriptContext *, QScriptEngine *engine)@/
632
+{
633
+	return engine->toScriptValue<QTime>(QTime::currentTime());
634
+}
635
+
636
+@ At present, implementations are not broken out to a separate file. This
637
+should be changed at some point.
638
+
639
+@<Class implementations@>=
640
+@<UnsupportedSerialDeviceConfWidget implementation@>
641
+@<JavaScriptDevice implementation@>
642
+
643
+@* Serial Ports.
644
+
645
+\noindent The first use case for script driven devices was connecting to
646
+devices which present themselves as a serial port. This covers a broad range
647
+of data acquisition products. To provide this support, |QextSerialPort|, which
648
+was already used to support some other hardware options, is directly exposed to
649
+the host environment.
650
+
651
+@<Function prototypes for scripting@>=
652
+QScriptValue constructSerialPort(QScriptContext *context, QScriptEngine *engine);
653
+void setSerialPortProperties(QScriptValue value, QScriptEngine *engine);
654
+QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *engine);
655
+
656
+@ Our constructor is passed to the scripting engine.
657
+
658
+@<Set up the scripting engine@>=
659
+constructor = engine->newFunction(constructSerialPort);
660
+value = engine->newQMetaObject(&QextSerialPort::staticMetaObject, constructor);
661
+engine->globalObject().setProperty("SerialPort", value);
662
+
663
+@ At present we only support event driven communications and are not passing
664
+any port settings through the constructor. Such functionality may be added in
665
+the future, but it is not strictly necessary.
666
+
667
+@<Functions for scripting@>=
668
+QScriptValue constructSerialPort(QScriptContext *, QScriptEngine *engine)
669
+{
670
+	QScriptValue object = engine->newQObject(new QextSerialPort());
671
+	setSerialPortProperties(object, engine);
672
+	return object;
673
+}
674
+
675
+@ Some properties of |QIODevice| are brought in as usual for similar subclasses
676
+but we also add a wrapper around the |flush()| method.
677
+
678
+@<Functions for scripting@>=
679
+void setSerialPortProperties(QScriptValue value, QScriptEngine *engine)
680
+{
681
+	setQIODeviceProperties(value, engine);
682
+	value.setProperty("flush", engine->newFunction(SerialPort_flush));
683
+}
684
+
685
+@ The wrapper around |flush()| is trivial.
686
+
687
+@<Functions for scripting@>=
688
+QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *)
689
+{
690
+	QextSerialPort *self = getself<QextSerialPort *>(context);
691
+	self->flush();
692
+	return QScriptValue();
693
+}
694
+
695
+

Загрузка…
Отмена
Сохранить