Skip to content

Commit 908f6ea

Browse files
authored
Merge pull request #1148 from OpenBCI/emg-joystick-and-settings
Add EMG Joystick Widget #1131
2 parents 7b971f2 + 4c47d62 commit 908f6ea

17 files changed

Lines changed: 1849 additions & 1397 deletions

CHANGELOG.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
- Fix error starting BrainFlow Streaming Board from external process #1102
88
- Fix Hardware Settings button not clickable after resizing app #1132
99
- Fix bug loading Session Settings related to Widget Layouts #1138
10+
- Fix bug caused by NullPointerException with ControlP5 Window on App Start #1150
1011

1112
### Improvements
1213

14+
- Add EMG Joystick widget and EMG Settings UI from NeuroFly Project #1131
1315
- Update to BrainFlow 5.6.1
1416
- Add feature to connect to Ganglion using Native Bluetooth #1080
1517
- Refactor the creation and playback of OpenBCI GUI CSV files #1119
1618
- Filter out .tty serial ports in Networking Widget Serial list #1097 - Thanks @kkashiva
19+
- Add ChannelSelect Feature to EMG Widget #1149
1720

1821
# v5.1.0
1922

@@ -22,7 +25,7 @@
2225
- Stop data stream when no data received after 5 seconds #1011
2326
- Revisit Ganglion Impedance widget so it behaves like new Cyton Impedance Widget #1021
2427
- Fix dropdown backgrounds in Networking Widget
25-
- Update priveleges for Windows users and check if GUI has been run as Administrator
28+
- Update privileges for Windows users and check if GUI has been run as Administrator
2629
- Fix High DPI scaling on some Macs with Retina Display
2730

2831
### Improvements
@@ -84,7 +87,7 @@
8487
### Bug Fixes
8588

8689
- Fix drawing error in Control Panel WiFi Shield static IP Textfield
87-
- Accomodate high-dpi screens Fixes #968
90+
- Accommodate high-dpi screens Fixes #968
8891
- Add Arduino Focus Fan example to networking test kit on GitHub repo
8992
- Allow synthetic square wave expert mode keyboard shortcut for Cyton and Ganglion Fixes #976
9093

@@ -149,7 +152,7 @@
149152
- Update ChannelSelect Feature in Widget Class to show what channels are on or off
150153
- Improve Time Series y-axis autoscale performance
151154
- Add channel select feature to FFT widget
152-
- Remove configurable gain behaviour and default to dynamic gain scaler
155+
- Remove configurable gain behavior and default to dynamic gain scaler
153156

154157
### Bug Fixes
155158

@@ -166,7 +169,7 @@
166169
### Improvements
167170

168171
- Add ability to save and load hardware settings
169-
- Add configurable gain behaviour
172+
- Add configurable gain behavior
170173
- Add custom vertical scale UI to Time Series
171174

172175
### Bug Fixes
@@ -186,7 +189,7 @@
186189
- Use BrainFlow Java Binding to handle data acquisition (no need to run the Hub!)
187190
- Speed up entire GUI by plotting data more efficiently
188191
- Updated OpenBCI Data Format (CSV) Files, with more detailed information and data
189-
- Popup with link to GUI v4 file coverter script
192+
- Popup with link to GUI v4 file converter script
190193
- Improved Playback Mode and Time Series
191194
- Refactored GUI data flow
192195
- Add Travis and Appveyor CI tests and builds for all OS
@@ -248,7 +251,7 @@ Use OpenBCIHub v2.1.0 please.
248251
### Improvements
249252

250253
- Add prominent time display for all data modes #635
251-
- Add button for Networking Data Ouputs Guide #643
254+
- Add button for Networking Data Outputs Guide #643
252255
- Add button to open Sample Data file directory #645
253256

254257
### Bug Fixes

OpenBCI_GUI/ConsoleLog.pde

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ static class ConsoleWindow extends PApplet implements Runnable {
6060

6161
logApplet = this;
6262

63-
surface.setAlwaysOnTop(true);
63+
surface.setAlwaysOnTop(false);
6464
surface.setResizable(true);
6565

66+
Frame frame = ( (PSurfaceAWT.SmoothCanvas) ((PSurfaceAWT)surface).getNative()).getFrame();
67+
frame.toFront();
68+
frame.requestFocus();
69+
6670
clipboardCopy = new ClipHelper();
6771
cp5 = new ControlP5(this);
6872
PFont textAreaFont = createFont("Arial", 12, true);

OpenBCI_GUI/DataProcessing.pde

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,9 @@ import ddf.minim.analysis.*; //for FFT
77
import brainflow.DataFilter;
88
import brainflow.FilterTypes;
99

10-
DataProcessing dataProcessing;
1110
String curTimestamp;
1211
HashMap<Integer,String> index_of_times;
1312

14-
// indexes
15-
final int DELTA = 0; // 1-4 Hz
16-
final int THETA = 1; // 4-8 Hz
17-
final int ALPHA = 2; // 8-13 Hz
18-
final int BETA = 3; // 13-30 Hz
19-
final int GAMMA = 4; // 30-55 Hz
20-
2113
float playback_speed_fac = 1.0f; //make 1.0 for real-time. larger for faster playback
2214

2315
//------------------------------------------------------------------------
@@ -101,6 +93,8 @@ class DataProcessing {
10193
float avgPowerInBins[][];
10294
float headWidePower[];
10395

96+
public EmgSettings emgSettings;
97+
10498
DataProcessing(int NCHAN, float sample_rate_Hz) {
10599
nchan = NCHAN;
106100
fs_Hz = sample_rate_Hz;
@@ -109,6 +103,8 @@ class DataProcessing {
109103
newDataToSend = false;
110104
avgPowerInBins = new float[nchan][processing_band_low_Hz.length];
111105
headWidePower = new float[processing_band_low_Hz.length];
106+
107+
emgSettings = new EmgSettings();
112108
}
113109

114110
//Process data on a channel-by-channel basis
@@ -281,9 +277,6 @@ class DataProcessing {
281277
headWidePower[i] = sum/nchan; // averaging power over all channels
282278
}
283279

284-
//delta in channel 2 ... avgPowerInBins[1][DELTA];
285-
//headwide beta ... headWidePower[BETA];
286-
287280
//find strongest channel
288281
int refChanInd = findMax(data_std_uV);
289282
//println("EEG_Processing: strongest chan (one referenced) = " + (refChanInd+1));
@@ -302,5 +295,8 @@ class DataProcessing {
302295
polarity[Ichan]=-1.0;
303296
}
304297
}
298+
299+
//Compute EMG values independent of widgets
300+
emgSettings.values.process(dataProcessingFilteredBuffer);
305301
}
306302
}

OpenBCI_GUI/EmgSettings.pde

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
class EmgSettings {
2+
3+
public EmgSettingsValues values;
4+
5+
private int channelCount;
6+
7+
private boolean settingsWereLoaded = false;
8+
9+
EmgSettings() {
10+
channelCount = currentBoard.getNumEXGChannels();
11+
values = new EmgSettingsValues();
12+
}
13+
14+
public boolean loadSettingsValues(String filename) {
15+
try {
16+
File file = new File(filename);
17+
StringBuilder fileContents = new StringBuilder((int)file.length());
18+
Scanner scanner = new Scanner(file);
19+
while(scanner.hasNextLine()) {
20+
fileContents.append(scanner.nextLine() + System.lineSeparator());
21+
}
22+
Gson gson = new Gson();
23+
EmgSettingsValues tempValues = gson.fromJson(fileContents.toString(), EmgSettingsValues.class);
24+
if (tempValues.smoothing.length != channelCount) {
25+
outputError("Emg Settings: Loaded EMG Settings file has different number of channels than the current board.");
26+
return false;
27+
}
28+
//Explicitely copy values over to avoid reference issues
29+
//(e.g. values = tempValues "nukes" the old values object)
30+
values.smoothing = tempValues.smoothing;
31+
values.uvLimit = tempValues.uvLimit;
32+
values.creepIncreasing = tempValues.creepIncreasing;
33+
values.creepDecreasing = tempValues.creepDecreasing;
34+
values.minimumDeltaUV = tempValues.minimumDeltaUV;
35+
values.lowerThresholdMinimum = tempValues.lowerThresholdMinimum;
36+
return true;
37+
} catch (IOException e) {
38+
e.printStackTrace();
39+
File f = new File(filename);
40+
if (f.exists()) {
41+
if (f.delete()) {
42+
outputError("Emg Settings: Could not load EMG settings from disk. Deleting this file...");
43+
} else {
44+
outputError("Emg Settings: Error deleting old/broken EMG settings file! Please make sure the GUI has proper read/write permissions.");
45+
}
46+
}
47+
return false;
48+
}
49+
}
50+
51+
public String getJson() {
52+
Gson gson = new GsonBuilder().setPrettyPrinting().create();
53+
return gson.toJson(values);
54+
}
55+
56+
public boolean saveToFile(String filename) {
57+
String json = getJson();
58+
try {
59+
FileWriter writer = new FileWriter(filename);
60+
writer.write(json);
61+
writer.close();
62+
return true;
63+
} catch (IOException e) {
64+
e.printStackTrace();
65+
return false;
66+
}
67+
}
68+
69+
public void revertAllChannelsToDefaultValues() {
70+
values = new EmgSettingsValues();
71+
settingsWereLoaded = true;
72+
}
73+
74+
//Called in UI to control number of channels. This is set from the board when this class is instantiated.
75+
public int getChannelCount() {
76+
return channelCount;
77+
}
78+
79+
//Avoid error with popup being in another thread.
80+
public void storeSettings() {
81+
StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath());
82+
settingsFilename.append("EmgSettings");
83+
settingsFilename.append("_");
84+
settingsFilename.append(getChannelCount());
85+
settingsFilename.append("Channels.json");
86+
String filename = settingsFilename.toString();
87+
File fileToSave = new File(filename);
88+
selectOutput("Save EMG settings to file", "storeEmgSettings", fileToSave);
89+
}
90+
91+
//Avoid error with popup being in another thread.
92+
public void loadSettings() {
93+
StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath());
94+
settingsFilename.append("EmgSettings");
95+
settingsFilename.append("_");
96+
settingsFilename.append(getChannelCount());
97+
settingsFilename.append("Channels.json");
98+
String filename = settingsFilename.toString();
99+
File fileToLoad = new File(filename);
100+
selectInput("Select EMG settings file to load", "loadEmgSettings", fileToLoad);
101+
}
102+
103+
public boolean getSettingsWereLoaded() {
104+
return settingsWereLoaded;
105+
}
106+
107+
public void setSettingsWereLoaded(boolean settingsWereLoaded) {
108+
this.settingsWereLoaded = settingsWereLoaded;
109+
}
110+
}
111+
112+
//Used by button in the EMG UI. Must be global and public. Called in above loadSettings method.
113+
public void loadEmgSettings(File selection) {
114+
if (selection == null) {
115+
output("EMG Settings file not selected.");
116+
} else {
117+
if (dataProcessing.emgSettings.loadSettingsValues(selection.getAbsolutePath())) {
118+
outputSuccess("EMG Settings Loaded!");
119+
dataProcessing.emgSettings.setSettingsWereLoaded(true);
120+
}
121+
}
122+
}
123+
124+
//Used by button in the EMG UI. Must be global and public. Called in above storeSettings method.
125+
public void storeEmgSettings(File selection) {
126+
if (selection == null) {
127+
output("EMG Settings file not selected.");
128+
} else {
129+
if (dataProcessing.emgSettings.saveToFile(selection.getAbsolutePath())) {
130+
outputSuccess("EMG Settings Saved!");
131+
} else {
132+
outputError("Failed to save EMG Settings.");
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)