38
38
import numpy as np # For handling numeric arrays
39
39
import pyqtgraph as pg # For real-time plotting
40
40
from pyqtgraph .Qt import QtWidgets , QtCore # PyQt components for GUI
41
+ import msvcrt #For keyboard interruptions
41
42
42
43
# Initialize global variables for tracking and processing data
43
44
total_packet_count = 0 # Total packets received in the last second
47
48
previous_sample_number = None # Store the previous sample number for detecting missing samples
48
49
missing_samples = 0 # Count of missing samples due to packet loss
49
50
buffer = bytearray () # Buffer for storing incoming raw data from Arduino
50
- PACKET_LENGTH = 17 # Expected length of each data packet
51
- SYNC_BYTE1 = 0xA5 # First byte of sync marker
52
- SYNC_BYTE2 = 0x5A # Second byte of sync marker
51
+ NUM_CHANNELS = 6 #Number of Channels being received
52
+ data = np .zeros ((6 , 2000 )) # 2D array to store data for real-time plotting (6 channels, 2000 data points)
53
+ samples_per_second = 0 # Number of samples received per second
54
+
55
+ # Initialize gloabal variables for Arduino Board
56
+ board = "" # Variable for Connected Arduino Board
57
+ boards_sample_rate = {"UNO-R3" :250 , "UNO-R4" :500 } #Standard Sample rate for Arduino Boards Different Firmware
58
+
59
+ # Initialize gloabal variables for Incoming Data
60
+ PACKET_LENGTH = 16 # Expected length of each data packet
61
+ SYNC_BYTE1 = 0xc7 # First byte of sync marker
62
+ SYNC_BYTE2 = 0x7c # Second byte of sync marker
53
63
END_BYTE = 0x01 # End byte marker
64
+ HEADER_LENGTH = 3 #Length of the Packet Header
65
+
66
+ ## Initialize gloabal variables for Output
54
67
lsl_outlet = None # Placeholder for LSL stream outlet
55
68
verbose = False # Flag for verbose output mode
56
- data = np .zeros ((6 , 2000 )) # 2D array to store data for real-time plotting (6 channels, 2000 data points)
57
69
csv_filename = None # Store CSV filename
58
- samples_per_second = 0 # Number of samples received per second
59
70
60
71
# Function to automatically detect the Arduino's serial port
61
72
def auto_detect_arduino (baudrate , timeout = 1 ):
62
73
ports = serial .tools .list_ports .comports () # List available serial ports
63
74
for port in ports : # Iterate through each port
64
75
try :
65
76
ser = serial .Serial (port .device , baudrate = baudrate , timeout = timeout ) # Try opening the port
77
+ ser .write (b'WHORU\n ' )
66
78
time .sleep (1 ) # Wait for the device to initialize
67
- response = ser .readline ().strip () # Try reading from the port
79
+ response = ser .readline ().strip (). decode () # Try reading from the port
68
80
if response : # If response is received, assume it's the Arduino
81
+ global board
69
82
ser .close () # Close the serial connection
70
- print (f"Arduino detected at { port .device } " ) # Notify the user
83
+ print (f"{ response } detected at { port .device } " ) # Notify the user
84
+ board = response
71
85
return port .device # Return the port name
72
86
ser .close () # Close the port if no response
73
87
except (OSError , serial .SerialException ): # Handle exceptions if the port can't be opened
@@ -81,7 +95,6 @@ def read_arduino_data(ser, csv_writer=None):
81
95
82
96
raw_data = ser .read (ser .in_waiting or 1 ) # Read available data from the serial port
83
97
buffer .extend (raw_data ) # Add received data to the buffer
84
-
85
98
while len (buffer ) >= PACKET_LENGTH : # Continue processing if the buffer contains at least one full packet
86
99
sync_index = buffer .find (bytes ([SYNC_BYTE1 , SYNC_BYTE2 ])) # Search for the sync marker
87
100
@@ -93,7 +106,7 @@ def read_arduino_data(ser, csv_writer=None):
93
106
packet = buffer [sync_index :sync_index + PACKET_LENGTH ] # Extract the packet
94
107
if len (packet ) == PACKET_LENGTH and packet [0 ] == SYNC_BYTE1 and packet [1 ] == SYNC_BYTE2 and packet [- 1 ] == END_BYTE :
95
108
# Extract the packet if it is valid (correct length, sync bytes, and end byte)
96
- counter = packet [3 ] # Read the counter byte (for tracking sample order)
109
+ counter = packet [2 ] # Read the counter byte (for tracking sample order)
97
110
98
111
# Check for missing samples by comparing the counter values
99
112
if previous_sample_number is not None and counter != (previous_sample_number + 1 ) % 256 :
@@ -107,9 +120,9 @@ def read_arduino_data(ser, csv_writer=None):
107
120
108
121
# Extract channel data (6 channels, 2 bytes per channel)
109
122
channel_data = []
110
- for i in range (4 , 16 , 2 ): # Loop through channel data bytes
111
- high_byte = packet [i ]
112
- low_byte = packet [i + 1 ]
123
+ for channel in range (NUM_CHANNELS ): # Loop through channel data bytes
124
+ high_byte = packet [2 * channel + HEADER_LENGTH ]
125
+ low_byte = packet [2 * channel + HEADER_LENGTH + 1 ]
113
126
value = (high_byte << 8 ) | low_byte # Combine high and low bytes
114
127
channel_data .append (float (value )) # Convert to float and add to channel data
115
128
@@ -208,7 +221,7 @@ def log_ten_minute_data(verbose=False):
208
221
print (f"Total data count after 10 minutes: { cumulative_packet_count } " ) # Print cumulative data count
209
222
sampling_rate = cumulative_packet_count / (10 * 60 ) # Calculate sampling rate
210
223
print (f"Sampling rate: { sampling_rate :.2f} samples/second" ) # Print sampling rate
211
- expected_sampling_rate = 250 # Expected sampling rate
224
+ expected_sampling_rate = boards_sample_rate [ board ] # Expected sampling rate
212
225
drift = ((sampling_rate - expected_sampling_rate ) / expected_sampling_rate ) * 3600 # Calculate drift
213
226
print (f"Drift: { drift :.2f} seconds/hour" ) # Print drift
214
227
cumulative_packet_count = 0 # Reset cumulative packet count
@@ -222,7 +235,7 @@ def parse_data(port, baudrate, lsl_flag=False, csv_flag=False, gui_flag=False, v
222
235
223
236
# Start LSL streaming if requested
224
237
if lsl_flag :
225
- lsl_stream_info = StreamInfo ('BioAmpDataStream' , 'EXG' , 6 , 250 , 'float32' , 'UpsideDownLabs' ) # Define LSL stream info
238
+ lsl_stream_info = StreamInfo ('BioAmpDataStream' , 'EXG' , 6 , boards_sample_rate [ board ] , 'float32' , 'UpsideDownLabs' ) # Define LSL stream info
226
239
lsl_outlet = StreamOutlet (lsl_stream_info ) # Create LSL outlet
227
240
print ("LSL stream started" ) # Notify user
228
241
time .sleep (0.5 ) # Wait for the LSL stream to start
@@ -249,6 +262,7 @@ def parse_data(port, baudrate, lsl_flag=False, csv_flag=False, gui_flag=False, v
249
262
start_timer () # Start timers for logging
250
263
251
264
try :
265
+ ser .write (b'START\n ' )
252
266
while True :
253
267
read_arduino_data (ser , csv_writer ) # Read and process data from Arduino
254
268
current_time = time .time () # Get the current time
@@ -262,13 +276,22 @@ def parse_data(port, baudrate, lsl_flag=False, csv_flag=False, gui_flag=False, v
262
276
log_ten_minute_data (verbose ) # Log data for the last 10 minutes
263
277
if gui_flag :
264
278
QtWidgets .QApplication .processEvents () # Process GUI events if GUI is enabled
265
-
266
- except KeyboardInterrupt : # Handle interruption (Ctrl+C)
279
+
280
+ if msvcrt .kbhit () and msvcrt .getch () == b'q' : # Exit the loop if 'q' is pressed
281
+ ser .write (b'STOP\n ' )
282
+ print ("Process interrupted by user" )
283
+ break
284
+
285
+ except KeyboardInterrupt :
286
+ ser .write (b'STOP\n ' )
287
+ print ("Process interrupted by user" )
288
+
289
+ finally :
267
290
if csv_file :
268
- csv_file .close () # Close CSV file
269
- print (f"CSV recording stopped. Data saved to { csv_filename } ." ) # Notify user
291
+ csv_file .close ()
292
+ print (f"CSV recording saved as { csv_filename } " )
270
293
print (f"Exiting.\n Total missing samples: { missing_samples } " ) # Print final missing samples count
271
-
294
+
272
295
# Main entry point of the script
273
296
def main ():
274
297
global verbose
0 commit comments