Browse Source

Configuration widgets for unsupported device configuration

Neal Wilson 10 years ago
parent
commit
8daf587b0d
2 changed files with 254 additions and 0 deletions
  1. 2
    0
      src/typica.w
  2. 252
    0
      src/unsupportedserial.w

+ 2
- 0
src/typica.w View File

@@ -18204,6 +18204,8 @@ app.registerDeviceConfigurationWidget("modbusrtu", ModbusConfigurator::staticMet
18204 18204
 inserter = new NodeInserter(tr("Modbus RTU Device"), tr("Modbus RTU Device"), "modbusrtu", NULL);
18205 18205
 topLevelNodeInserters.append(inserter);
18206 18206
 
18207
+@i unsupportedserial.w
18208
+
18207 18209
 @* Configuration widget for a calibrated data series.
18208 18210
 
18209 18211
 \noindent This control is used for adding a |LinearSplineInterpolator| to the

+ 252
- 0
src/unsupportedserial.w View File

@@ -0,0 +1,252 @@
1
+@** Unsupported Serial Port Devices
2
+
3
+\noindent There are many data acquisition products which connect over or
4
+present themselves as a serial port and it has become relatively easy for
5
+hardware tinkerers to develop new devices matching this description. In this
6
+space there is no apparent consideration given to interoperability and with
7
+some devices the communications protocol even changes significantly between
8
+firmware revisions. There are far too many devices like this to support
9
+everything. At the same time, there are people who have these devices, are
10
+capable of programming the basic communications handling, but have difficulty
11
+with all of the supporting code that must be written to properly integrate with
12
+the rest of \pn. By providing an in-program environment that handles much of
13
+the boilerplate and allowing people to write scripts implementing these
14
+protocols without the need to modify core \pn code or recompile the program,
15
+these people may find it easier to make their existing hardware work. Such
16
+scripts can also serve as prototypes for later integration.
17
+
18
+It is common among these devices that port settings aside from the port name
19
+are fixed, but there may be other characteristics that are configurable on a
20
+per-device or per-channel basis. As it is impossible to know what these
21
+details might be, the configuration widgets for these devices and channels
22
+simply provide space to provide key value pairs which are provided to the host
23
+environment. Common configurations will have a device node representing a
24
+single logical device, usually a single physical device but this is not in any
25
+way enforced, and one child node per channel, the details of which are made
26
+available to the device script and are also used to integrate with the logging
27
+view.
28
+
29
+The use of the word serial should be considered legacy of the initial
30
+conception of this feature. The implementation here is sufficiently generic
31
+that there is no reason this could not be extended to cover devices that are
32
+interfaced through USB HID, Bluetooth, COM, output piped from an external
33
+console program, devices interfaced through arbitrary libraries, or any other
34
+class of device not directly supported in the core code should there be an
35
+interest in these.
36
+
37
+@<Class declarations@>=
38
+class UnsupportedSerialDeviceConfWidget : public BasicDeviceConfigurationWidget
39
+{
40
+	Q_OBJECT
41
+	public:
42
+		Q_INVOKABLE UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
43
+		                                              const QModelIndex &index);
44
+	private slots:
45
+		void updateConfiguration();
46
+		void saveScript();
47
+		void addChannel();
48
+	private:
49
+		SaltModel *deviceSettingsModel;
50
+		QTextEdit *scriptEditor;
51
+};
52
+
53
+@ The device configuration widget consists of two tabs. One tab provides a
54
+button for adding channels and an area for entering device specific
55
+configuration details. The other provides an area for entering the device
56
+script. This may be extended later to provide better editing, testing, and
57
+debugging support, but the initial concern is simply having a working feature.
58
+
59
+@<UnsupportedSerialDeviceConfWidget implementation@>=
60
+UnsupportedSerialDeviceConfWidget::UnsupportedSerialDeviceConfWidget(DeviceTreeModel *model,
61
+                                                                     const QModelIndex &index)
62
+	: BasicDeviceConfigurationWidget(model, index),
63
+	deviceSettingsModel(new SaltModel(2)),
64
+	scriptEditor(new QTextEdit)
65
+{
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::DisplayRole);
109
+			}
110
+		}
111
+		else if(node.attribute("name") == "script")
112
+		{
113
+			scriptEditor->setPlainText(node.attribute("value"));
114
+		}
115
+	}
116
+	
117
+	connect(deviceSettingsModel, SIGNAL(dataChanged()),
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(0, 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
+	
204
+	}
205
+	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
206
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
207
+	connect(channelSettingsModel, SIGNAL(dataChanged()), this, SLOT(updateConfiguration()));
208
+}
209
+
210
+@ Arbitrary channel configuration data is handled in the same way as device
211
+level settings while the column name and hidden status is handled in the same
212
+way as they are in other channel nodes.
213
+
214
+@<UnsupportedSerialDeviceConfWidget implementation@>=
215
+void UnsupportedDeviceChannelConfWidget::updateColumnName(const QString &value)
216
+{
217
+	updateAttribute("columnname", value);
218
+}
219
+
220
+void UnsupportedDeviceChannelConfWidget::updateHidden(bool hidden)
221
+{
222
+	updateAttribute("hidden", hidden ? "true" : "false");
223
+}
224
+
225
+void UnsupportedDeviceChannelConfWidget::updateConfiguration()
226
+{
227
+	updateAttribute("keys", channelSettingsModel->arrayLiteral(0, Qt::DisplayRole));
228
+	updateAttribute("values", channelSettingsModel->arrayLiteral(0, Qt::DisplayRole));
229
+}
230
+
231
+@ The configuration widgets need to be registered so they can be instantiated
232
+as appropriate.
233
+
234
+@<Register device configuration widgets@>=
235
+app.registerDeviceConfigurationWidget("unsupporteddevicechannel",
236
+	UnsupportedDeviceChannelConfWidget::staticMetaObject);
237
+app.registerDeviceConfigurationWidget("unsupporteddevice",
238
+	UnsupportedSerialDeviceConfWidget::staticMetaObject);
239
+
240
+@ A |NodeInserter| for the device node is also provided.
241
+
242
+@<Register top level device configuration nodes@>=
243
+inserter = new NodeInserter(tr("Other Device"), tr("Other Device"),
244
+	"unsupporteddevice", NULL);
245
+topLevelNodeInserters.append(inserter);
246
+
247
+@ At present, implementations are not broken out to a separate file. This
248
+should be changed at some point.
249
+
250
+@<Class implementations@>=
251
+@<UnsupportedSerialDeviceConfWidget implementation@>
252
+

Loading…
Cancel
Save