-
Notifications
You must be signed in to change notification settings - Fork 0
/
BCI2000Remote.cs
359 lines (325 loc) · 12.2 KB
/
BCI2000Remote.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
///////////////////////////////////////////////////////////////////////
// Author: [email protected]
// Description: A class for controlling BCI2000 remotely from a .NET
// application. Does not depend on BCI2000 framework.
// On Error, a function returns false, and errors raised by
// the class are stored in Result, and errors raised by the
// Operator are stored in Received.
//
// Adapted from the C++ BCI2000Remote
// (C) 2000-2021, BCI2000 Project
// http://www.bci2000.org
///////////////////////////////////////////////////////////////////////
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace BCI2000RemoteNET
{
public class BCI2000Remote : BCI2000Connection //All public methods are boolean and return true if they succeed, false if they fail. Data output is handled by passing a reference.
{
private string subjectID;
public string SubjectID
{
get
{
return subjectID;
}
set
{
subjectID = value;
if (Connected() && !String.IsNullOrEmpty(subjectID))
Execute("set parameter SubjectName \"" + subjectID + "\"");
}
}
private string sessionID;
public string SessionID
{
get
{
return sessionID;
}
set
{
sessionID = value;
if (Connected() && !String.IsNullOrEmpty(sessionID))
Execute("set parameter SubjectSession \"" + sessionID + "\"");
}
}
private string dataDirectory;
public string DataDirectory
{
get
{
return dataDirectory;
}
set
{
dataDirectory = value;
if (Connected() && !String.IsNullOrEmpty(dataDirectory))
Execute("set parameter DataDirectory \"" + dataDirectory + "\"");
}
}
private const bool defaultStopOnQuit = true;
private const bool defaultDisconnectOnQuit = true;
public bool StopOnQuit { get; set; }
public bool DisconnectOnQuit { get; set; }
public BCI2000Remote()
{
StopOnQuit = defaultStopOnQuit;
DisconnectOnQuit = defaultDisconnectOnQuit;
}
~BCI2000Remote()
{
if (StopOnQuit)
Stop();
if (DisconnectOnQuit)
Disconnect();
}
public override bool Connect()
{
bool success = base.Connect();
if (success)
{
if (!String.IsNullOrEmpty(SubjectID))
SubjectID = subjectID;
if (!String.IsNullOrEmpty(SessionID))
SessionID = sessionID;
if (!String.IsNullOrEmpty(DataDirectory))
DataDirectory = dataDirectory;
}
return success;
}
/**
*
* takes module and arguments in the form of a dictionary with the keys being module names and value being a list of arguments
* uses lists and dictionary because parsing strings is annoying
* pass null as a value for no arguments other than --local
* arguments don't need "--" in front, and whitespace is removed
*
*
* **/
public bool StartupModules(Dictionary<string, List<string>> modules)
{
Execute("shutdown system");
Execute("startup system localhost");
StringBuilder errors = new StringBuilder();
int outCode = 0;
foreach (KeyValuePair<string, List<string>> module in modules)
{
StringBuilder moduleAndArgs = new StringBuilder(module.Key + ' ');
bool containsLocal = false;
if (module.Value != null && module.Value.Count > 0)
{
foreach (string argument in module.Value)
{
string argumentNoWS = new string(argument.Where(c => !Char.IsWhiteSpace(c)).ToArray());
if (!argumentNoWS.StartsWith("--"))//add dashes to beginning
argumentNoWS = "--" + argumentNoWS;
if (argumentNoWS.IndexOf("--local", StringComparison.OrdinalIgnoreCase) >= 0)
containsLocal = true;
moduleAndArgs.Append(argumentNoWS + ' ');
}
}
if (!containsLocal)//according to original, all modules start with option --local; appends --local to command
moduleAndArgs.Append("--local ");
Execute("start executable " + moduleAndArgs.ToString(), ref outCode);
if (outCode != 1)
{
errors.Append('\n' + module.Key + " returned " + outCode);
}
Result = errors.ToString();
}
if (!String.IsNullOrWhiteSpace(errors.ToString())) //errors while starting up modules
{
Result = "Could not start modules: " + errors.ToString();
return false;
}
WaitForSystemState("Connected");
return true;
}
public bool SetConfig()
{
SubjectID = subjectID;
SessionID = sessionID;
DataDirectory = dataDirectory;
Execute("capture messages none warnings errors");
string tempResult = "";
if (SimpleCommand("set config"))
WaitForSystemState("Resting|Initialization");
else
tempResult = Response;
Execute("capture messages none");
Execute("get system state");
//bool success = !ResponseContains("Resting");
Execute("flush messages");
if (!String.IsNullOrWhiteSpace(tempResult) && !tempResult.Equals(">"))//set config caused errors
Result = tempResult + '\n' + Response;
bool success = true;
return success;
}
public bool Start()
{
bool success = true;
Execute("get system state");
if (ResponseContains("Running"))
{
Result = "System is already running";
success = false;
}
else if (!ResponseContains("Resting") && !ResponseContains("Suspended"))
success = SetConfig();
if (success)
success = SimpleCommand("start system");
return success;
}
public bool Stop()
{
Execute("get system state");
if (!ResponseContains("Running"))
{
Result = "System is not running";
return false;
}
return SimpleCommand("stop system");
}
public bool SetParameter(string name, string value)
{
return SimpleCommand("set parameter \"" + name + "\" \"" + value + "\"");
}
bool GetParameter(string name, ref string outValue)//uses a ref to avoid the problem of returning a value if the command fails
{
int outCode = 0;
Execute("is parameter \"" + name + "\"", ref outCode);
if (outCode == 1)//name is a valid parameter
{
Execute("get parameter \"" + name + "\"");
outValue = Response;
return true;
}
else
{
Result = name + " is not a valid parameter name";
return false;
}
}
public bool LoadParametersLocal(string filename) //loads parameters from local (does not matter if running BCI2K locally, just use remote)
{//Also it probably doesnt work at the moment
StreamReader file;
try
{
file = File.OpenText(filename);
}
catch (Exception ex)
{
Result = "Could not open file " + filename + ", " + ex.Message;
return false;
}
string line;
int errors = 0;
while ((line = file.ReadLine()) != null)
{
errors += Convert.ToInt32(!SimpleCommand("add parameter " + EscapeSpecialChars(line)));//adds number of parameter adds which fail, inverted because a failure will return a false or 0
}
if (Convert.ToBoolean(errors))
{
Result = "Could not add " + errors + " parameter(s)";
}
errors = 0;
while ((line = file.ReadLine()) != null)
{
errors += Convert.ToInt32(!SimpleCommand("set parameter " + EscapeSpecialChars(line)));//adds number of parameter adds which fail, inverted because a failure will return a false or 0
}
if (Convert.ToBoolean(errors))
{
Result = "Could not set " + errors + " parameter(s)";
}
return true;
}
public bool LoadParametersRemote(string filename) //loads parameters on the machine on which BCI2K is running
{
return SimpleCommand("load parameters \"" + filename + "\"");
}
public bool AddStateVariable(string name, UInt32 bitWidth, double initialValue)
{
return SimpleCommand("add state \"" + name + "\" " + bitWidth + ' ' + initialValue);
}
public bool SetStateVariable(string name, double value)
{
return SimpleCommand("set state \"" + name + "\" " + value.ToString());
}
public bool GetStateVariable(string name, ref double outValue)
{
if (SimpleCommand("get state \"" + name + "\""))
{
try
{
outValue = Double.Parse(Response);
return true;
}
catch (Exception)
{
return false;
}
}
return false;
}
public bool WaitForSystemState(string state)
{
return SimpleCommand("wait for " + state);
}
public bool GetSystemState(ref string outState)
{
bool success = SimpleCommand("get system state");
outState = Response;
return success;
}
public bool SimpleCommand(string command)
{
Execute(command);
return string.IsNullOrWhiteSpace(Response) || Atoi(Response) != 0 || Response.Equals(">"); //returns true if Result is empty or nonzero
}
private string EscapeSpecialChars(string str)
{
string escapeChars = "#\"${}`&|<>;\n";
StringBuilder stringFinal = new StringBuilder();
char[] chars = str.ToCharArray();
for (int c = 0; c < chars.Length; c++)
{
Byte charBit = Convert.ToByte(chars[c]);
if (escapeChars.Contains(chars[c]) || charBit < 32 || charBit > 128)
{
//Byte CharBitChanged = (Byte)((charBit >> 4) | (charBit & 0xf));
stringFinal.Append('%' + BitConverter.ToString(new byte[] { charBit }));
}
else
stringFinal.Append(chars[c]);
}
return stringFinal.ToString();
}
private int Atoi(string str)//implementation of c atoi() since original code uses it
{
int output;
try
{
output = int.Parse(str);
}
catch (FormatException)
{
output = 0;
}
return output;
}
private bool Stricmp(string str1, string str2) // implementation of c stricmp
{
if (str1 == null || str2 == null)
return false;
return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) >= 0;
}
private bool ResponseContains(string str1)//using stricmp on result is annoying
{
return Stricmp(Response, str1);
}
}
}