diff --git a/.gitignore b/.gitignore
index 5ca027772..c2b853dcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,7 @@ application.*
*.autosave
.vscode/*
temp/*
+libBoardController.so
+libDataHandler.so
+libGanglionLib.so
+libGanglionScan.so
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92e07ee48..5e53da4b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# v5.0.3
+
+### Improvements
+* Increase sampling rate for Pulse data output in Networking Widget
+
+### Bug Fixes
+* Fix Pulse LSL output error #943
+* Fix Accel/Aux UDP output #944
+* Fix Expert Mode unplanned keyboard shortcuts crash GUI #941
+* Fix bugs found when loading Session Settings #942
+
# v5.0.2
### Improvements
diff --git a/Networking-Test-Kit/LSL/lslStreamTest_AnalogPinPulse.py b/Networking-Test-Kit/LSL/lslStreamTest_AnalogPinPulse.py
new file mode 100644
index 000000000..91b58d1db
--- /dev/null
+++ b/Networking-Test-Kit/LSL/lslStreamTest_AnalogPinPulse.py
@@ -0,0 +1,58 @@
+"""Example program to show how to read a multi-channel time series from LSL."""
+import time
+from pylsl import StreamInlet, resolve_stream
+from time import sleep
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib import style
+from collections import deque
+
+# first resolve an EEG stream on the lab network
+print("looking for an EEG stream...")
+streams = resolve_stream('type', 'EEG')
+
+# create a new inlet to read from the stream
+inlet = StreamInlet(streams[0])
+duration = 10
+
+sleep(0)
+
+def testLSLSamplingRate():
+ start = time.time()
+ numSamples = 0
+ numChunks = 0
+
+ while time.time() <= start + duration:
+ # get chunks of samples
+ chunk, timestamp = inlet.pull_chunk()
+ if timestamp:
+ numChunks += 1
+ for sample in chunk:
+ numSamples += 1
+
+ print( "Number of Chunks == {}".format(numChunks) )
+ print( "Avg Sampling Rate == {}".format(numSamples / duration) )
+
+
+testLSLSamplingRate()
+
+print("gathering data to plot...")
+
+def testLSLPulseData():
+ start = time.time()
+ raw_pulse_signal = []
+
+ while time.time() <= start + duration:
+ chunk, timestamp = inlet.pull_chunk()
+ if timestamp:
+ for sample in chunk:
+ # print(sample)
+ raw_pulse_signal.append(sample[1])
+
+ print(raw_pulse_signal)
+ print( "Avg Sampling Rate == {}".format(len(raw_pulse_signal) / duration) )
+ plt.plot(raw_pulse_signal)
+ plt.ylabel('raw analog signal')
+ plt.show()
+
+testLSLPulseData()
\ No newline at end of file
diff --git a/Networking-Test-Kit/UDP/udp_receive.py b/Networking-Test-Kit/UDP/udp_receive.py
index f8a1f0df6..51109281c 100755
--- a/Networking-Test-Kit/UDP/udp_receive.py
+++ b/Networking-Test-Kit/UDP/udp_receive.py
@@ -10,10 +10,11 @@
# Print received message to console
def print_message(*args):
try:
- obj = json.loads(args[0])
- print obj.get('data')
+ print(args[0]) #added to see raw data
+ obj = json.loads(args[0].decode())
+ print(obj.get('data'))
except BaseException as e:
- print e
+ print(e)
# print("(%s) RECEIVED MESSAGE: " % time.time() +
# ''.join(str(struct.unpack('>%df' % int(length), args[0]))))
@@ -78,9 +79,16 @@ def close_file(*args):
# Receive messages
print("Listening...")
- while True:
+ start = time.time()
+ numSamples = 0
+ duration = 10
+ while time.time() <= start + duration:
data, addr = sock.recvfrom(20000) # buffer size is 20000 bytes
if args.option=="print":
print_message(data)
+ numSamples += 1
elif args.option=="record":
record_to_file(data)
+print( "Samples == {}".format(numSamples) )
+print( "Duration == {}".format(duration) )
+print( "Avg Sampling Rate == {}".format(numSamples / duration) )
diff --git a/Networking-Test-Kit/UDP/udp_receive_pulse.py b/Networking-Test-Kit/UDP/udp_receive_pulse.py
new file mode 100755
index 000000000..eec641d3f
--- /dev/null
+++ b/Networking-Test-Kit/UDP/udp_receive_pulse.py
@@ -0,0 +1,106 @@
+import socket
+import sys
+import time
+import argparse
+import signal
+import struct
+import os
+import json
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib import style
+
+raw_pulse_signal = []
+
+# Print received message to console
+def print_message(*args):
+ try:
+ # print(args[0]) #added to see raw data
+ obj = json.loads(args[0].decode())
+ print(obj.get('data'))
+ except BaseException as e:
+ print(e)
+ # print("(%s) RECEIVED MESSAGE: " % time.time() +
+ # ''.join(str(struct.unpack('>%df' % int(length), args[0]))))
+
+# Clean exit from print mode
+def exit_print(signal, frame):
+ print("Closing listener")
+ sys.exit(0)
+
+# Record received message in text file
+def record_to_file(*args):
+ textfile.write(str(time.time()) + ",")
+ textfile.write(''.join(str(struct.unpack('>%df' % length,args[0]))))
+ textfile.write("\n")
+
+# Save recording, clean exit from record mode
+def close_file(*args):
+ print("\nFILE SAVED")
+ textfile.close()
+ sys.exit(0)
+
+if __name__ == "__main__":
+ # Collect command line arguments
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--ip",
+ default="127.0.0.1", help="The ip to listen on")
+ parser.add_argument("--port",
+ type=int, default=12345, help="The port to listen on")
+ parser.add_argument("--address",default="/openbci", help="address to listen to")
+ parser.add_argument("--option",default="print",help="Debugger option")
+ parser.add_argument("--len",default=8,help="Debugger option")
+ args = parser.parse_args()
+
+ # Set up necessary parameters from command line
+ length = args.len
+ if args.option=="print":
+ signal.signal(signal.SIGINT, exit_print)
+ elif args.option=="record":
+ i = 0
+ while os.path.exists("udp_test%s.txt" % i):
+ i += 1
+ filename = "udp_test%i.txt" % i
+ textfile = open(filename, "w")
+ textfile.write("time,address,messages\n")
+ textfile.write("-------------------------\n")
+ print("Recording to %s" % filename)
+ signal.signal(signal.SIGINT, close_file)
+
+ # Connect to socket
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ server_address = (args.ip, args.port)
+ sock.bind(server_address)
+
+ # Display socket attributes
+ print('--------------------')
+ print("-- UDP LISTENER -- ")
+ print('--------------------')
+ print("IP:", args.ip)
+ print("PORT:", args.port)
+ print('--------------------')
+ print("%s option selected" % args.option)
+
+ # Receive messages
+ print("Listening...")
+ start = time.time()
+ numSamples = 0
+ duration = 3
+
+ while time.time() <= start + duration:
+ data, addr = sock.recvfrom(20000) # buffer size is 20000 bytes
+ if args.option=="print":
+ print_message(data)
+ sample = json.loads(data.decode()).get('data')[1]
+ raw_pulse_signal.append(sample)
+ numSamples += 1
+ elif args.option=="record":
+ record_to_file(data)
+
+print( "Samples == {}".format(numSamples) )
+print( "Duration == {}".format(duration) )
+print( "Avg Sampling Rate == {}".format(numSamples / duration) )
+plt.plot(raw_pulse_signal)
+plt.ylabel('raw analog signal')
+plt.show()
diff --git a/OpenBCI_GUI/DataProcessing.pde b/OpenBCI_GUI/DataProcessing.pde
index 01b203cf8..6d39cbb52 100644
--- a/OpenBCI_GUI/DataProcessing.pde
+++ b/OpenBCI_GUI/DataProcessing.pde
@@ -93,8 +93,8 @@ class DataProcessing {
float data_std_uV[];
float polarity[];
boolean newDataToSend;
- BandPassRanges bpRange = BandPassRanges.FiveToFifty;
- BandStopRanges bsRange = BandStopRanges.Sixty;
+ public BandPassRanges bpRange = BandPassRanges.FiveToFifty;
+ public BandStopRanges bsRange = BandStopRanges.Sixty;
final int[] processing_band_low_Hz = {
1, 4, 8, 13, 30
}; //lower bound for each frequency band of interest (2D classifier only)
diff --git a/OpenBCI_GUI/FilterEnums.pde b/OpenBCI_GUI/FilterEnums.pde
index 65cb449fd..26d7a9ec1 100644
--- a/OpenBCI_GUI/FilterEnums.pde
+++ b/OpenBCI_GUI/FilterEnums.pde
@@ -1,21 +1,32 @@
public enum BandStopRanges
{
- Sixty(60.0d),
- Fifty(50.0d),
- None(null);
+ Sixty(0, 60.0d),
+ Fifty(1, 50.0d),
+ None(2, null);
+ private int index;
private Double freq;
private static BandStopRanges[] vals = values();
- BandStopRanges(Double freq) {
+ BandStopRanges(int index, Double freq) {
+ this.index = index;
this.freq = freq;
}
+
+ public int getIndex() {
+ return index;
+ }
public Double getFreq() {
return freq;
}
+ public static BandStopRanges getByIndex(int i)
+ {
+ return vals[i];
+ }
+
public BandStopRanges next()
{
return vals[(this.ordinal() + 1) % vals.length];
@@ -31,22 +42,28 @@ public enum BandStopRanges
public enum BandPassRanges
{
- FiveToFifty(5.0d, 50.0d),
- SevenToThirteen(7.0d, 13.0d),
- FifteenToFifty(15.0d, 50.0d),
- OneToFifty(1.0d, 50.0d),
- OneToHundred(1.0d, 100.0d),
- None(null, null);
+ FiveToFifty(0, 5.0d, 50.0d),
+ SevenToThirteen(1, 7.0d, 13.0d),
+ FifteenToFifty(2, 15.0d, 50.0d),
+ OneToFifty(3, 1.0d, 50.0d),
+ OneToHundred(4, 1.0d, 100.0d),
+ None(5, null, null);
+ private int index;
private Double start;
private Double stop;
private static BandPassRanges[] vals = values();
- BandPassRanges(Double start, Double stop) {
+ BandPassRanges(int index, Double start, Double stop) {
+ this.index = index;
this.start = start;
this.stop = stop;
}
+
+ public int getIndex() {
+ return index;
+ }
public Double getStart() {
return start;
@@ -56,6 +73,11 @@ public enum BandPassRanges
return stop;
}
+ public static BandPassRanges getByIndex(int i)
+ {
+ return vals[i];
+ }
+
public BandPassRanges next()
{
return vals[(this.ordinal() + 1) % vals.length];
diff --git a/OpenBCI_GUI/Info.plist.tmpl b/OpenBCI_GUI/Info.plist.tmpl
index 0f869a761..e814301d7 100644
--- a/OpenBCI_GUI/Info.plist.tmpl
+++ b/OpenBCI_GUI/Info.plist.tmpl
@@ -23,16 +23,16 @@
CFBundleShortVersionString
4
CFBundleVersion
- 5.0.2
+ 5.0.3
CFBundleSignature
????
NSHumanReadableCopyright
MIT License
- Copyright © 2020 OpenBCI
+ Copyright © 2021 OpenBCI
CFBundleGetInfoString
- December 2020
+ January 2021
@@jvm_runtime@@
diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde
index 282612d99..6f4dff755 100644
--- a/OpenBCI_GUI/Interactivity.pde
+++ b/OpenBCI_GUI/Interactivity.pde
@@ -44,10 +44,10 @@ void parseKey(char val) {
case ' ':
// space to start/stop the stream
topNav.stopButtonWasPressed();
- break;
+ return;
case ',':
drawContainers = !drawContainers;
- break;
+ return;
case '{':
if(colorScheme == COLOR_SCHEME_DEFAULT){
colorScheme = COLOR_SCHEME_ALTERNATIVE_A;
@@ -56,153 +56,167 @@ void parseKey(char val) {
}
//topNav.updateNavButtonsBasedOnColorScheme();
output("New Dark color scheme coming soon!");
- break;
+ return;
- //deactivate channels 1-16
+ //deactivate channels 1-4
case '1':
currentBoard.setEXGChannelActive(1-1, false);
- break;
+ return;
case '2':
currentBoard.setEXGChannelActive(2-1, false);
- break;
+ return;
case '3':
currentBoard.setEXGChannelActive(3-1, false);
- break;
+ return;
case '4':
currentBoard.setEXGChannelActive(4-1, false);
- break;
- case '5':
- currentBoard.setEXGChannelActive(5-1, false);
- break;
- case '6':
- currentBoard.setEXGChannelActive(6-1, false);
- break;
- case '7':
- currentBoard.setEXGChannelActive(7-1, false);
- break;
- case '8':
- currentBoard.setEXGChannelActive(8-1, false);
- break;
- case 'q':
- currentBoard.setEXGChannelActive(9-1, false);
- break;
- case 'w':
- currentBoard.setEXGChannelActive(10-1, false);
- break;
- case 'e':
- currentBoard.setEXGChannelActive(11-1, false);
- break;
- case 'r':
- currentBoard.setEXGChannelActive(12-1, false);
- break;
- case 't':
- currentBoard.setEXGChannelActive(13-1, false);
- break;
- case 'y':
- currentBoard.setEXGChannelActive(14-1, false);
- break;
- case 'u':
- currentBoard.setEXGChannelActive(15-1, false);
- break;
- case 'i':
- currentBoard.setEXGChannelActive(16-1, false);
- break;
+ return;
- //activate channels 1-16
+ //activate channels 1-4
case '!':
currentBoard.setEXGChannelActive(1-1, true);
- break;
+ return;
case '@':
currentBoard.setEXGChannelActive(2-1, true);
- break;
+ return;
case '#':
currentBoard.setEXGChannelActive(3-1, true);
- break;
+ return;
case '$':
currentBoard.setEXGChannelActive(4-1, true);
- break;
- case '%':
- currentBoard.setEXGChannelActive(5-1, true);
- break;
- case '^':
- currentBoard.setEXGChannelActive(6-1, true);
- break;
- case '&':
- currentBoard.setEXGChannelActive(7-1, true);
- break;
- case '*':
- currentBoard.setEXGChannelActive(8-1, true);
- break;
- case 'Q':
- currentBoard.setEXGChannelActive(9-1, true);
- break;
- case 'W':
- currentBoard.setEXGChannelActive(10-1, true);
- break;
- case 'E':
- currentBoard.setEXGChannelActive(11-1, true);
- break;
- case 'R':
- currentBoard.setEXGChannelActive(12-1, true);
- break;
- case 'T':
- currentBoard.setEXGChannelActive(13-1, true);
- break;
- case 'Y':
- currentBoard.setEXGChannelActive(14-1, true);
- break;
- case 'U':
- currentBoard.setEXGChannelActive(15-1, true);
- break;
- case 'I':
- currentBoard.setEXGChannelActive(16-1, true);
- break;
+ return;
//other controls
case 's':
stopRunning();
- break;
+ return;
case 'b':
startRunning();
- break;
+ return;
///////////////////// Save User settings lowercase n
case 'n':
println("Save key pressed!");
settings.save(settings.getPath("User", eegDataSource, nchan));
outputSuccess("Settings Saved! The GUI will now load with these settings. Click \"Default\" to revert to factory settings.");
- break;
+ return;
///////////////////// Load User settings uppercase N
case 'N':
println("Load key pressed!");
settings.loadKeyPressed();
- break;
+ return;
case '?':
if(currentBoard instanceof BoardCyton) {
((BoardCyton)currentBoard).printRegisters();
}
- break;
+ return;
case 'd':
- break;
+ return;
case 'm':
String picfname = "OpenBCI-" + directoryManager.getFileNameDateTime() + ".jpg";
//println("OpenBCI_GUI: 'm' was pressed...taking screenshot:" + picfname);
saveFrame(directoryManager.getGuiDataPath() + "Screenshots" + System.getProperty("file.separator") + picfname); // take a shot of that!
output("Screenshot captured! Saved to /Documents/OpenBCI_GUI/Screenshots/" + picfname);
- break;
-
+ return;
default:
- if (currentBoard instanceof Board) {
- println("Interactivity: '" + key + "' Pressed...sending to Board...");
- ((Board)currentBoard).sendCommand(str(key));
- }
break;
}
+
+ if (nchan > 4) {
+ switch (val) {
+ case '5':
+ currentBoard.setEXGChannelActive(5-1, false);
+ return;
+ case '6':
+ currentBoard.setEXGChannelActive(6-1, false);
+ return;
+ case '7':
+ currentBoard.setEXGChannelActive(7-1, false);
+ return;
+ case '8':
+ currentBoard.setEXGChannelActive(8-1, false);
+ return;
+ case '%':
+ currentBoard.setEXGChannelActive(5-1, true);
+ return;
+ case '^':
+ currentBoard.setEXGChannelActive(6-1, true);
+ return;
+ case '&':
+ currentBoard.setEXGChannelActive(7-1, true);
+ return;
+ case '*':
+ currentBoard.setEXGChannelActive(8-1, true);
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (nchan > 8) {
+ switch (val) {
+ case 'q':
+ currentBoard.setEXGChannelActive(9-1, false);
+ return;
+ case 'w':
+ currentBoard.setEXGChannelActive(10-1, false);
+ return;
+ case 'e':
+ currentBoard.setEXGChannelActive(11-1, false);
+ return;
+ case 'r':
+ currentBoard.setEXGChannelActive(12-1, false);
+ return;
+ case 't':
+ currentBoard.setEXGChannelActive(13-1, false);
+ return;
+ case 'y':
+ currentBoard.setEXGChannelActive(14-1, false);
+ return;
+ case 'u':
+ currentBoard.setEXGChannelActive(15-1, false);
+ return;
+ case 'i':
+ currentBoard.setEXGChannelActive(16-1, false);
+ return;
+ case 'Q':
+ currentBoard.setEXGChannelActive(9-1, true);
+ return;
+ case 'W':
+ currentBoard.setEXGChannelActive(10-1, true);
+ return;
+ case 'E':
+ currentBoard.setEXGChannelActive(11-1, true);
+ return;
+ case 'R':
+ currentBoard.setEXGChannelActive(12-1, true);
+ return;
+ case 'T':
+ currentBoard.setEXGChannelActive(13-1, true);
+ return;
+ case 'Y':
+ currentBoard.setEXGChannelActive(14-1, true);
+ return;
+ case 'U':
+ currentBoard.setEXGChannelActive(15-1, true);
+ return;
+ case 'I':
+ currentBoard.setEXGChannelActive(16-1, true);
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (currentBoard instanceof Board) {
+ output("Expert Mode: '" + key + "' pressed. This is not assigned or applicable to current setup.");
+ //((Board)currentBoard).sendCommand(str(key));
+ }
}
void mouseDragged() {
diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde
index a48a81a8f..880cb5327 100644
--- a/OpenBCI_GUI/OpenBCI_GUI.pde
+++ b/OpenBCI_GUI/OpenBCI_GUI.pde
@@ -64,8 +64,8 @@ import http.requests.*;
// Global Variables & Instances
//------------------------------------------------------------------------
//Used to check GUI version in TopNav.pde and displayed on the splash screen on startup
-String localGUIVersionString = "v5.0.2";
-String localGUIVersionDate = "December 2020";
+String localGUIVersionString = "v5.0.3";
+String localGUIVersionDate = "January 2021";
String guiLatestVersionGithubAPI = "https://api.github.com/repos/OpenBCI/OpenBCI_GUI/releases/latest";
String guiLatestReleaseLocation = "https://github.com/OpenBCI/OpenBCI_GUI/releases/latest";
diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde
index fcf050386..7fb6c8968 100644
--- a/OpenBCI_GUI/SessionSettings.pde
+++ b/OpenBCI_GUI/SessionSettings.pde
@@ -61,9 +61,6 @@ class SessionSettings {
CColor dropdownColors = new CColor();
///These `Save` vars are set to default when each widget instantiates
///and updated every time user selects from dropdown
- //Notch and Bandpass filter variables for save
- int dataProcessingNotchSave = 0;
- int dataProcessingBandpassSave = 3;
//Accelerometer settings
int accVertScaleSave;
int accHorizScaleSave;
@@ -171,11 +168,6 @@ class SessionSettings {
String[] spectMaxFrqArray = {"20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz"};
String[] spectSampleRateArray = {"1 Hz", "5 hz", "10 Hz", "20 Hz", "40 Hz"};
- //Load global settings variables
- int loadLayoutSetting;
- int loadNotchSetting;
- int loadBandpassSetting;
-
//Load Accel. dropdown variables
int loadAccelVertScale;
int loadAccelHorizScale;
@@ -376,10 +368,13 @@ class SessionSettings {
JSONObject saveGlobalSettings = new JSONObject();
saveGlobalSettings.setBoolean("Expert Mode", expertModeToggle);
saveGlobalSettings.setInt("Current Layout", currentLayout);
- saveGlobalSettings.setInt("Notch", dataProcessingNotchSave);
- saveGlobalSettings.setInt("Bandpass Filter", dataProcessingBandpassSave);
+ saveGlobalSettings.setInt("Notch", dataProcessing.bsRange.getIndex());
+ saveGlobalSettings.setInt("Bandpass Filter", dataProcessing.bpRange.getIndex());
saveGlobalSettings.setInt("Analog Read Vert Scale", arVertScaleSave);
saveGlobalSettings.setInt("Analog Read Horiz Scale", arHorizScaleSave);
+ if (currentBoard instanceof SmoothingCapableBoard) {
+ saveGlobalSettings.setBoolean("Data Smoothing", ((SmoothingCapableBoard)currentBoard).getSmoothingActive());
+ }
saveSettingsJSONData.setJSONObject(kJSONKeySettings, saveGlobalSettings);
/////Setup JSON Object for gui version and settings Version
@@ -585,15 +580,15 @@ class SessionSettings {
//get the global settings JSON object
JSONObject loadGlobalSettings = loadSettingsJSONData.getJSONObject(kJSONKeySettings);
- loadLayoutSetting = loadGlobalSettings.getInt("Current Layout");
- loadNotchSetting = loadGlobalSettings.getInt("Notch");
- loadBandpassSetting = loadGlobalSettings.getInt("Bandpass Filter");
- Boolean loadExpertModeToggle = loadGlobalSettings.getBoolean("Expert Mode");
+ //Store loaded layout to current layout variable
+ currentLayout = loadGlobalSettings.getInt("Current Layout");
loadAnalogReadVertScale = loadGlobalSettings.getInt("Analog Read Vert Scale");
loadAnalogReadHorizScale = loadGlobalSettings.getInt("Analog Read Horiz Scale");
- //Store loaded layout to current layout variable
- currentLayout = loadLayoutSetting;
//Load more global settings after this line, if needed
+ int loadNotchSetting = loadGlobalSettings.getInt("Notch");
+ int loadBandpassSetting = loadGlobalSettings.getInt("Bandpass Filter");
+ Boolean loadExpertModeToggle = loadGlobalSettings.getBoolean("Expert Mode");
+ Boolean loadDataSmoothingSetting = (currentBoard instanceof SmoothingCapableBoard) ? loadGlobalSettings.getBoolean("Data Smoothing") : null;
//get the FFT settings
JSONObject loadFFTSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFFT);
@@ -717,8 +712,8 @@ class SessionSettings {
//get the Widget/Container settings
JSONObject loadWidgetSettings = loadSettingsJSONData.getJSONObject(kJSONKeyWidget);
//Apply Layout directly before loading and applying widgets to containers
- wm.setNewContainerLayout(loadLayoutSetting);
- verbosePrint("LoadGUISettings: Layout " + loadLayoutSetting + " Loaded!");
+ wm.setNewContainerLayout(currentLayout);
+ verbosePrint("LoadGUISettings: Layout " + currentLayout + " Loaded!");
numLoadedWidgets = loadWidgetSettings.size();
@@ -749,11 +744,28 @@ class SessionSettings {
/////////////////////////////////////////////////////////////
// Load more widget settings above this line as above //
+ /////////////////////////////////////////////////////////////
- //}//end case for all objects in JSON
+ /////////////////////////////////////////////////////////////
+ // Apply Settings below this line //
+ /////////////////////////////////////////////////////////////
+
+ //Apply notch
+ dataProcessing.bsRange = BandStopRanges.getByIndex(loadNotchSetting);
+ topNav.filtNotchButton.getCaptionLabel().setText("Notch\n" + dataProcessing.getShortNotchDescription());
+ //Apply Bandpass filter
+ dataProcessing.bpRange = BandPassRanges.getByIndex(loadBandpassSetting);
+ topNav.filtBPButton.getCaptionLabel().setText("BP Filt\n" + dataProcessing.getShortFilterDescription());
+
+ //Apply Data Smoothing for capable boards
+ if (currentBoard instanceof SmoothingCapableBoard) {
+ ((SmoothingCapableBoard)currentBoard).setSmoothingActive(loadDataSmoothingSetting);
+ topNav.updateSmoothingButtonText();
+ }
//Apply Expert Mode toggle
- topNav.configSelector.toggleExpertMode(loadExpertModeToggle);
+ //This should not be loaded with other session settings - RW Jan 2021
+ //topNav.configSelector.toggleExpertMode(loadExpertModeToggle);
//Load and apply all of the settings that are in dropdown menus. It's a bit much, so it has it's own function below.
loadApplyWidgetDropdownText();
@@ -987,7 +999,7 @@ class SessionSettings {
w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(w_timeSeries.getTSVertScale().getString()); //changes front-end
w_timeSeries.setTSHorizScale(loadTimeSeriesSettings.getInt("Time Series Horiz Scale"));
- w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(w_timeSeries.getTSVertScale().getString());
+ w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(w_timeSeries.getTSHorizScale().getString());
JSONArray loadTSChan = loadTimeSeriesSettings.getJSONArray("activeChannels");
w_timeSeries.tsChanSelect.deactivateAllButtons();
diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde
index 78b7acbe4..448dec2b9 100644
--- a/OpenBCI_GUI/TopNav.pde
+++ b/OpenBCI_GUI/TopNav.pde
@@ -398,6 +398,10 @@ class TopNav {
return val;
}
+ public void updateSmoothingButtonText() {
+ smoothingButton.getCaptionLabel().setText(getSmoothingString());
+ }
+
private String getSmoothingString() {
return ((SmoothingCapableBoard)currentBoard).getSmoothingActive() ? "Smoothing\n On" : "Smoothing\n Off";
}
diff --git a/OpenBCI_GUI/W_Networking.pde b/OpenBCI_GUI/W_Networking.pde
index 59f53c5ab..e82ded28b 100644
--- a/OpenBCI_GUI/W_Networking.pde
+++ b/OpenBCI_GUI/W_Networking.pde
@@ -13,7 +13,8 @@
// Created by: Gabriel Ibagon (github.com/gabrielibagon), January 2017
//
///////////////////////////////////////////////////////////////////////////////
-
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
class W_Networking extends Widget {
@@ -1201,12 +1202,15 @@ class Stream extends Thread {
String streamName;
int nChanLSL;
int numChan = 0;
+ DecimalFormat threeDecimalPlaces;
+ DecimalFormat fourLeadingPlaces;
Boolean isStreaming;
Boolean newData = false;
// Data buffers set dynamically in updateNumChan()
int start;
float[] dataToSend;
+ private double[][] previousFrameData;
//OSC Objects
OscP5 osc;
@@ -1266,6 +1270,15 @@ class Stream extends Thread {
this.filter = filter;
this.isStreaming = false;
updateNumChan(_nchan);
+
+ //Force decimal formatting for all Locales
+ Locale currentLocale = Locale.getDefault();
+ DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(currentLocale);
+ otherSymbols.setDecimalSeparator('.');
+ otherSymbols.setGroupingSeparator(',');
+ threeDecimalPlaces = new DecimalFormat("0.000", otherSymbols);
+ fourLeadingPlaces = new DecimalFormat("####", otherSymbols);
+
if (this.dataType.equals("TimeSeries")) {
buffer = ByteBuffer.allocate(4*numChan);
} else {
@@ -1336,7 +1349,7 @@ class Stream extends Thread {
println(e.getMessage());
}
} else {
- if (checkForData()) {
+ if (checkForData()) { //This needs to be removed or modified in next version of the GUI
sendData();
setDataFalse();
} else {
@@ -1356,14 +1369,15 @@ class Stream extends Thread {
println(e.getMessage());
}
} else {
- if (checkForData()) {
+ if (checkForData()) { //This needs to be removed or modified in next version of the GUI
sendData();
+ setDataFalse();
}
}
}
}
- Boolean checkForData() {
+ Boolean checkForData() { //Try to remove these methods in next version of GUI
if (this.dataType.equals("TimeSeries")) {
return w_networking.newDataToSend;
} else if (this.dataType.equals("FFT")) {
@@ -1375,7 +1389,7 @@ class Stream extends Thread {
} else if (this.dataType.equals("Accel/Aux")) {
return w_networking.newDataToSend;
} else if (this.dataType.equals("Pulse")) {
- return w_networking.newDataToSend;
+ return true;
}
return false;
}
@@ -1799,7 +1813,8 @@ class Stream extends Thread {
String outputter = "{\"type\":\"accelerometer\",\"data\":[";
for (int i = 0; i < NUM_ACCEL_DIMS; i++) {
float accelData = w_accelerometer.getLastAccelVal(i);
- String accelData_3dec = String.format("%.3f", accelData);
+ String accelData_3dec = threeDecimalPlaces.format(accelData);
+ //String accelData_3dec = String.format("%.3f", accelData); //This does not work in all international settings
outputter += accelData_3dec;
if (i != NUM_ACCEL_DIMS - 1) {
outputter += ",";
@@ -1853,65 +1868,64 @@ class Stream extends Thread {
final int NUM_ANALOG_READS = analogChannels.length;
// UNFILTERED & FILTERED, Aux data is not affected by filters anyways
- if (this.filter==false || this.filter==true) {
- // OSC
- if (this.protocol.equals("OSC")) {
- for (int i = 0; i < NUM_ANALOG_READS; i++) {
- msg.clearArguments();
- msg.add(i+1);
- msg.add((int)lastSample[analogChannels[i]]);
- try {
- this.osc.send(msg,this.netaddress);
- } catch (Exception e) {
- println(e.getMessage());
- }
- }
- // UDP
- } else if (this.protocol.equals("UDP")) {
- String outputter = "{\"type\":\"auxiliary\",\"data\":[";
- for (int i = 0; i < NUM_ANALOG_READS; i++) {
- int auxData = (int)lastSample[analogChannels[i]];
- String auxData_formatted = String.format("%04d", auxData);
- outputter += auxData_formatted;
- if (i != NUM_ANALOG_READS - 1) {
- outputter += ",";
- } else {
- outputter += "]}\r\n";
- }
- }
+ //if (this.filter==false || this.filter==true) {
+ // OSC
+ if (this.protocol.equals("OSC")) {
+ for (int i = 0; i < NUM_ANALOG_READS; i++) {
+ msg.clearArguments();
+ msg.add(i+1);
+ msg.add((int)lastSample[analogChannels[i]]);
try {
- this.udp.send(outputter, this.ip, this.port);
+ this.osc.send(msg,this.netaddress);
} catch (Exception e) {
println(e.getMessage());
}
- // LSL
- } else if (this.protocol.equals("LSL")) {
- for (int i = 0; i < NUM_ANALOG_READS; i++) {
- dataToSend[i] = (int)lastSample[analogChannels[i]];
- }
- // Add timestamp to LSL Stream
- outlet_data.push_sample(dataToSend);
- } else if (this.protocol.equals("Serial")) {
- // Data Format: 0001,0002,0003\n or 0001,0002\n depending if Wifi Shield is used
- // 5 chars per pin, including \n char for Z
- serialMessage = "";
- for (int i = 0; i < NUM_ANALOG_READS; i++) {
- int auxData = (int)lastSample[analogChannels[i]];
- String auxData_formatted = String.format("%04d", auxData);
- serialMessage += auxData_formatted;
- if (i != NUM_ANALOG_READS - 1) {
- serialMessage += ",";
- } else {
- serialMessage += "\n";
- }
+ }
+ // UDP
+ } else if (this.protocol.equals("UDP")) {
+ String outputter = "{\"type\":\"auxiliary\",\"data\":[";
+ for (int i = 0; i < NUM_ANALOG_READS; i++) {
+ int auxData = (int)lastSample[analogChannels[i]];
+ String auxData_formatted = fourLeadingPlaces.format(auxData);
+ outputter += auxData_formatted;
+ if (i != NUM_ANALOG_READS - 1) {
+ outputter += ",";
+ } else {
+ outputter += "]}\r\n";
}
- try {
- //println(serialMessage);
- this.serial_networking.write(serialMessage);
- } catch (Exception e) {
- println(e.getMessage());
+ }
+ try {
+ this.udp.send(outputter, this.ip, this.port);
+ } catch (Exception e) {
+ println(e.getMessage());
+ }
+ // LSL
+ } else if (this.protocol.equals("LSL")) {
+ for (int i = 0; i < NUM_ANALOG_READS; i++) {
+ dataToSend[i] = (int)lastSample[analogChannels[i]];
+ }
+ // Add timestamp to LSL Stream
+ outlet_data.push_sample(dataToSend);
+ } else if (this.protocol.equals("Serial")) {
+ // Data Format: 0001,0002,0003\n or 0001,0002\n depending if Wifi Shield is used
+ // 5 chars per pin, including \n char for Z
+ serialMessage = "";
+ for (int i = 0; i < NUM_ANALOG_READS; i++) {
+ int auxData = (int)lastSample[analogChannels[i]];
+ String auxData_formatted = String.format("%04d", auxData);
+ serialMessage += auxData_formatted;
+ if (i != NUM_ANALOG_READS - 1) {
+ serialMessage += ",";
+ } else {
+ serialMessage += "\n";
}
}
+ try {
+ //println(serialMessage);
+ this.serial_networking.write(serialMessage);
+ } catch (Exception e) {
+ println(e.getMessage());
+ }
}
}
@@ -1981,15 +1995,29 @@ class Stream extends Thread {
}
////////////////////////////////////// Stream pulse data from W_PulseSensor
+ //This data type is not affected by GUI filters
+ //JAN 2021 - Using this method to test refactoring Networking streaming
void sendPulseData() {
- if (this.filter==false || this.filter==true) {
+ //Get data from Board that
+ int numDataPoints = 3;
+ double[][] frameData = currentBoard.getFrameData();
+ int[] analogChannels = ((AnalogCapableBoard)currentBoard).getAnalogChannels();
+
+ //Check for state change in the available frameData. This works, but maybe checkIfEnoughDataToSend could be used instead and be more accurate...
+ if (!frameData.equals(previousFrameData)) {
+
+ previousFrameData = frameData;
+
// OSC
if (this.protocol.equals("OSC")) {
- //ADD BPM Data (BPM, Signal, IBI)
- for (int i = 0; i < (w_pulsesensor.PulseWaveY.length); i++) {//This works
+
+ for (int i = 0; i < frameData[0].length; i++)
+ {
+ int raw_signal = (int)(frameData[analogChannels[0]][i]);
+ //ADD BPM Data (BPM, Signal, IBI)
msg.clearArguments(); //This belongs here
msg.add(w_pulsesensor.BPM); //Add BPM first
- msg.add(w_pulsesensor.PulseWaveY[i]); //Add Raw Signal second
+ msg.add(raw_signal); //Add Raw Signal second
msg.add(w_pulsesensor.IBI); //Add IBI third
//Message received in Max via OSC is a list of three integers without commas: 75 512 600 : BPM Signal IBI
//println(" " + this.port + " ~~~~ " + w_pulsesensor.BPM + "," + w_pulsesensor.PulseWaveY[i] + "," + w_pulsesensor.IBI);
@@ -1999,12 +2027,16 @@ class Stream extends Thread {
println(e.getMessage());
}
}
+
// UDP
} else if (this.protocol.equals("UDP")) { //////////////////This needs to be checked
- String outputter = "{\"type\":\"pulse\",\"data\":";
- for (int i = 0; i < (w_pulsesensor.PulseWaveY.length); i++) {
+
+ for (int i = 0; i < frameData[0].length; i++)
+ {
+ String outputter = "{\"type\":\"pulse\",\"data\":[";
+ int raw_signal = (int)(frameData[analogChannels[0]][i]);
outputter += str(w_pulsesensor.BPM) + ","; //Comma separated string output (BPM,Raw Signal,IBI)
- outputter += str(w_pulsesensor.PulseWaveY[i]) + ",";
+ outputter += str(raw_signal) + ",";
outputter += str(w_pulsesensor.IBI);
outputter += "]}\r\n";
try {
@@ -2013,25 +2045,31 @@ class Stream extends Thread {
println(e.getMessage());
}
}
+
// LSL
} else if (this.protocol.equals("LSL")) { ///////////////////This needs to be checked
- for (int i = 0; i < (w_pulsesensor.PulseWaveY.length); i++) {
- dataToSend[0] = w_pulsesensor.BPM; //Array output
- dataToSend[1] = w_pulsesensor.PulseWaveY[i];
- dataToSend[2] = w_pulsesensor.IBI;
+
+ float[] _dataToSend = new float[frameData[0].length * numDataPoints];
+ for (int i = 0; i < frameData[0].length; i++)
+ {
+ int raw_signal = (int)(frameData[analogChannels[0]][i]);
+ _dataToSend[numDataPoints*i] = w_pulsesensor.BPM;
+ _dataToSend[numDataPoints*i+1] = raw_signal;
+ _dataToSend[numDataPoints*i+2] = w_pulsesensor.IBI;
}
- // Add timestamp to LSL Stream
- outlet_data.push_chunk(dataToSend);
+ // From LSLLink Library: The time stamps of other samples are automatically derived based on the sampling rate of the stream.
+ outlet_data.push_chunk(_dataToSend);
+
// Serial
} else if (this.protocol.equals("Serial")) { // Send Pulse Data (BPM,Signal,IBI) over Serial
- for (int i = 0; i < (w_pulsesensor.PulseWaveY.length); i++) {
+
+ for (int i = 0; i < frameData[0].length; i++)
+ {
serialMessage = ""; //clear message
- int BPM = (w_pulsesensor.BPM);
- int Signal = (w_pulsesensor.PulseWaveY[i]);
- int IBI = (w_pulsesensor.IBI);
- serialMessage += BPM + ","; //Comma separated string output (BPM,Raw Signal,IBI)
- serialMessage += Signal + ",";
- serialMessage += IBI;
+ int raw_signal = (int)(frameData[analogChannels[0]][i]);
+ serialMessage += w_pulsesensor.BPM + ","; //Comma separated string output (BPM,Raw Signal,IBI)
+ serialMessage += raw_signal + ",";
+ serialMessage += w_pulsesensor.IBI;
try {
println(serialMessage);
this.serial_networking.write(serialMessage);
diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde
index 25b67713a..0126d34ad 100644
--- a/OpenBCI_GUI/W_PulseSensor.pde
+++ b/OpenBCI_GUI/W_PulseSensor.pde
@@ -100,10 +100,17 @@ class W_PulseSensor extends Widget {
for (int i=0; i < PulseBuffSize; i++ ) {
int signal = (int)(allData.get(i)[analogChannels[0]]);
- processSignal(signal);
+ //processSignal(signal);
PulseWaveY[i] = signal;
}
+ double[][] frameData = currentBoard.getFrameData();
+ for (int i = 0; i < frameData[0].length; i++)
+ {
+ int signal = (int)(frameData[analogChannels[0]][i]);
+ processSignal(signal);
+ }
+
//ignore top left button interaction when widgetSelector dropdown is active
lockElementOnOverlapCheck(analogModeButton);
diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde
index c93b066c1..02a01321c 100644
--- a/OpenBCI_GUI/W_Spectrogram.pde
+++ b/OpenBCI_GUI/W_Spectrogram.pde
@@ -424,9 +424,6 @@ class W_Spectrogram extends Widget {
//triggered when there is an event in the Spectrogram Widget MaxFreq. Dropdown
void SpectrogramMaxFreq(int n) {
settings.spectMaxFrqSave = n;
- //Link the choices made in the FFT widget and the Spectrogram Widget for this parameter
- MaxFreq(n);
- w_fft.cp5_widget.getController("MaxFreq").getCaptionLabel().setText(settings.fftMaxFrqArray[n]);
//reset the vertical axis labelss
w_spectrogram.vertAxisLabel = w_spectrogram.vertAxisLabels[n];
//Resize the height of the data image