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 9446a351c..442eea2a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,10 @@
# 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
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 7133e6279..51109281c 100755
--- a/Networking-Test-Kit/UDP/udp_receive.py
+++ b/Networking-Test-Kit/UDP/udp_receive.py
@@ -79,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/Info.plist.tmpl b/OpenBCI_GUI/Info.plist.tmpl
index abc917b9d..9de05e0c6 100644
--- a/OpenBCI_GUI/Info.plist.tmpl
+++ b/OpenBCI_GUI/Info.plist.tmpl
@@ -23,7 +23,7 @@
CFBundleShortVersionString
4
CFBundleVersion
- 5.0.3-alpha.2
+ 5.0.3-alpha.3
CFBundleSignature
????
NSHumanReadableCopyright
diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde
index f4557570f..abc488e47 100644
--- a/OpenBCI_GUI/OpenBCI_GUI.pde
+++ b/OpenBCI_GUI/OpenBCI_GUI.pde
@@ -64,7 +64,7 @@ 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.3-alpha.2";
+String localGUIVersionString = "v5.0.3-alpha.3";
String localGUIVersionDate = "January 2020";
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/W_Networking.pde b/OpenBCI_GUI/W_Networking.pde
index cc34139d9..e82ded28b 100644
--- a/OpenBCI_GUI/W_Networking.pde
+++ b/OpenBCI_GUI/W_Networking.pde
@@ -1210,6 +1210,7 @@ class Stream extends Thread {
// Data buffers set dynamically in updateNumChan()
int start;
float[] dataToSend;
+ private double[][] previousFrameData;
//OSC Objects
OscP5 osc;
@@ -1348,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 {
@@ -1368,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")) {
@@ -1387,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;
}
@@ -1866,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 = fourLeadingPlaces.format(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());
+ }
}
}
@@ -1994,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);
@@ -2012,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 {
@@ -2026,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);