Skip to content

Commit 46a7d47

Browse files
committed
--csv & --lsl Changes
1 parent 9d712fa commit 46a7d47

File tree

1 file changed

+138
-136
lines changed

1 file changed

+138
-136
lines changed

bioamptool.py

Lines changed: 138 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -31,183 +31,185 @@
3131
import serial
3232
import time
3333
import csv
34-
from collections import deque
34+
from datetime import datetime
3535
import serial.tools.list_ports
3636

3737
# Initialize global variables
38-
total_packet_count = 0 # Counter for packets received in the current minute
39-
start_time = None # Timestamp for the start of the current time
40-
total_data_received = 0 # Total number of data packets received in the 10-minute interval
41-
previous_sample_number = None # Variable to store the last sample number
42-
missing_samples = 0 # Counter for missing samples
43-
buffer = bytearray()
44-
PACKET_LENGTH = 17
45-
SYNC_BYTE1 = 0xA5
46-
SYNC_BYTE2 = 0x5A
47-
END_BYTE = 0x01
38+
total_packet_count = 0 # Total number of packets received
39+
start_time = None # Start time of data collection
40+
last_ten_minute_time = None # Time when the last 10-minute interval started
41+
total_data_received = 0 # Accumulated total number of data points received
42+
previous_sample_number = None # Last received sample number to detect missing samples
43+
missing_samples = 0 # Count of missing samples
44+
buffer = bytearray() # Buffer to accumulate incoming data bytes
45+
PACKET_LENGTH = 17 # Length of each data packet
46+
SYNC_BYTE1 = 0xA5 # First sync byte value
47+
SYNC_BYTE2 = 0x5A # Second sync byte value
48+
END_BYTE = 0x01 # End byte value for the packet
4849

4950
# LSL Stream Setup
50-
lsl_stream_info = StreamInfo('BioAmpDataStream', 'EXG', 6, 250, 'float32', 'UpsideDownLabs') # Define LSL stream info
51-
lsl_outlet = StreamOutlet(lsl_stream_info) # Create LSL outlet for streaming data
52-
53-
def auto_detect_arduino(baudrate, timeout=1):
54-
"""
55-
Auto-detect Arduino by checking all available serial ports.
56-
"""
57-
ports = serial.tools.list_ports.comports() # List all serial ports
51+
lsl_outlet = None # LSL outlet for streaming data
52+
53+
def auto_detect_arduino(baudrate, timeout=1): # Auto-detect Arduino by checking all available serial ports.
54+
ports = serial.tools.list_ports.comports() # List all available serial ports
5855
for port in ports:
5956
try:
6057
ser = serial.Serial(port.device, baudrate=baudrate, timeout=timeout) # Open serial port
61-
time.sleep(1) # Wait for Arduino to respond
62-
response = ser.readline().strip() # Read response from Arduino
58+
time.sleep(1) # Wait for Arduino to initialize
59+
response = ser.readline().strip() # Read data from Arduino
6360
if response:
64-
ser.close() # Close serial port
65-
print(f"Arduino detected at {port.device}") # Print detected port
61+
ser.close() # Close the serial port if response is found
62+
print(f"Arduino detected at {port.device}") # Print detected port
6663
return port.device # Return the detected port
67-
ser.close() # Close serial port if no response
64+
ser.close() # Close the serial port if no response
6865
except (OSError, serial.SerialException):
69-
pass # Handle errors in serial port communication
70-
print("Arduino not detected") # Print message if no Arduino is detected
66+
pass # Handle any exceptions and continue scanning other ports
67+
print("Arduino not detected") # Print message if no Arduino is found
7168
return None # Return None if no Arduino is detected
7269

73-
def read_arduino_data(ser, csv_writer):
74-
"""
75-
Read data from Arduino, process it, and write to CSV and LSL stream.
76-
"""
70+
def read_arduino_data(ser, csv_writer=None): #Read data from Arduino, process it, and optionally write to CSV and LSL stream.
7771
global total_packet_count, previous_sample_number, missing_samples, buffer
78-
raw_data = ser.read(ser.in_waiting or 1) # Read 17 bytes from the serial port
79-
buffer.extend(raw_data)
72+
raw_data = ser.read(ser.in_waiting or 1) # Read data from the serial port
73+
buffer.extend(raw_data) # Append the read data to the buffer
8074

81-
# Check for valid data packet structure
82-
while len(buffer) >= PACKET_LENGTH:
83-
sync_index = buffer.find(bytes([SYNC_BYTE1, SYNC_BYTE2]))
75+
while len(buffer) >= PACKET_LENGTH: # Process data if buffer has enough bytes
76+
sync_index = buffer.find(bytes([SYNC_BYTE1, SYNC_BYTE2])) # Find sync bytes in buffer
8477

85-
if sync_index == -1:
86-
buffer.clear
78+
if sync_index == -1: # If sync bytes are not found
79+
buffer.clear() # Clear the buffer and continue
8780
continue
8881

89-
if(len(buffer) >= sync_index + PACKET_LENGTH):
90-
packet = buffer[sync_index:sync_index+PACKET_LENGTH]
82+
if len(buffer) >= sync_index + PACKET_LENGTH: # Check if buffer has a complete packet
83+
packet = buffer[sync_index:sync_index + PACKET_LENGTH] # Extract the packet from the buffer
9184
if len(packet) == 17 and packet[0] == SYNC_BYTE1 and packet[1] == SYNC_BYTE2 and packet[-1] == END_BYTE:
92-
counter = packet[3] # Counter is at index 3
85+
counter = packet[3] # Extract the counter value from the packet
9386

94-
# Ensure counter number is exactly one more than the previous one
9587
if previous_sample_number is not None and counter != (previous_sample_number + 1) % 256:
88+
# Check for missing samples based on the counter
9689
missing_samples += (counter - previous_sample_number - 1) % 256
9790
print(f"Error: Expected counter {previous_sample_number + 1} but received {counter}. Missing samples: {missing_samples}")
9891

99-
previous_sample_number = counter # Update previous sample number to current counter
92+
previous_sample_number = counter # Update the previous sample number
93+
total_packet_count += 1 # Increment the total packet count
10094

101-
total_packet_count += 1 # Increment packet count only after initial samples are ignored
95+
channel_data = [] # List to store channel data
96+
for i in range(4, 16, 2): # Extract channel data from the packet
97+
high_byte = packet[i] # High byte of the data
98+
low_byte = packet[i + 1] # Low byte of the data
99+
value = (high_byte << 8) | low_byte # Combine high and low byte into a value
100+
channel_data.append(float(value)) # Append the value to the channel data list
102101

103-
# Merge high and low bytes to form channel data
104-
channel_data = []
105-
for i in range(4, 16, 2): # Indices for channel data
106-
high_byte = packet[i]
107-
low_byte = packet[i + 1]
108-
value = (high_byte << 8) | low_byte # Combine high and low bytes to form the 16-bit value
109-
channel_data.append(float(value))
102+
if csv_writer:
103+
csv_writer.writerow([counter] + channel_data) # Write data to CSV file
104+
if lsl_outlet:
105+
lsl_outlet.push_sample(channel_data) # Send data to LSL stream
110106

111-
# Write counter and channel data to CSV
112-
csv_writer.writerow([counter] + channel_data)
107+
del buffer[:sync_index + PACKET_LENGTH] # Remove processed packet from the buffer
108+
else:
109+
del buffer[:sync_index + 1] # Remove invalid data from the buffer
113110

114-
# Push channel data to LSL stream
115-
lsl_outlet.push_sample(channel_data)
111+
def start_timer(): # Initialize timers for minute and ten-minute intervals and reset packet count.
112+
global start_time, last_ten_minute_time, total_packet_count
113+
time.sleep(0.5) # Ensure LSL stream setup is complete
114+
current_time = time.time() # Get the current time
115+
start_time = current_time # Set the start time
116+
last_ten_minute_time = current_time # Set the time for the last ten-minute interval
117+
total_packet_count = 0 # Reset total packet count
116118

117-
del buffer[:sync_index + PACKET_LENGTH]
118-
else:
119-
del buffer[:sync_index + 1]
120-
121-
def start_timer():
122-
"""
123-
Initialize timers for minute and ten-minute intervals and reset packet count.
124-
"""
125-
global start_time, total_packet_count
126-
current_time = time.time() # Get current timestamp
127-
start_time = current_time # Set start time
128-
total_packet_count = 0 # Reset packet count
129-
130-
def log_minute_data():
131-
"""
132-
Logs and resets data count per minute.
133-
"""
119+
def log_minute_data(): #Logs and resets data count per minute
134120
global total_packet_count
135-
count_for_minute = total_packet_count # Get the count for the current minute
136-
print(f"Data count for this minute: {count_for_minute} samples") # Print count for the current minute
137-
total_packet_count = 0 # Reset packet count for the next minute
138-
return count_for_minute # Return the count for further processing
139-
140-
def log_ten_minute_data():
141-
"""
142-
Logs data count for every 10 minutes and computes sampling rate and drift.
143-
"""
144-
global total_data_received, start_time
145-
146-
# Calculate total data count and sampling rate
147-
print(f"Total data count after 10 minutes: {total_data_received} samples") # Print total data count for the last 10 minutes
148-
sampling_rate = total_data_received / (10 * 60) # Calculate sampling rate
149-
print(f"Sampling rate: {sampling_rate:.2f} samples/second") # Print sampling rate
150-
151-
# Calculate drift
152-
expected_sampling_rate = 250 # Expected sampling rate
153-
drift = ((sampling_rate - expected_sampling_rate) / expected_sampling_rate) * 3600 # Calculate drift in seconds/hour
154-
print(f"Drift: {drift:.2f} seconds/hour") # Print drift
121+
count_for_minute = total_packet_count # Get the data count for the current minute
122+
print(f"Data count for this minute: {count_for_minute} samples") # Print the data count
123+
total_packet_count = 0 # Reset total packet count for the next minute
124+
return count_for_minute # Return the count for further use
155125

156-
# Reset for the next 10-minute interval
157-
total_data_received = 0 # Reset total data received
158-
start_time = time.time() # Update start time for the next 10-minute interval
126+
def log_ten_minute_data(): #Logs data count for every 10 minutes and computes sampling rate and drift.
127+
global total_data_received, last_ten_minute_time
159128

160-
def parse_data(port, baudrate):
161-
"""
162-
Main function to process data from the Arduino.
163-
"""
164-
global total_packet_count, start_time, start_time, total_data_received
129+
print(f"Total data count after 10 minutes: {total_data_received} samples") # Print total data count
130+
sampling_rate = total_data_received / (10 * 60) # Calculate the sampling rate
131+
print(f"Sampling rate: {sampling_rate:.2f} samples/second") # Print the sampling rate
165132

166-
with serial.Serial(port, baudrate, timeout=0.1) as ser:
167-
with open('packet_data.csv', mode='w', newline='') as csv_file:
168-
csv_writer = csv.writer(csv_file)
169-
csv_writer.writerow(['Counter', 'Channel1', 'Channel2', 'Channel3', 'Channel4', 'Channel5', 'Channel6']) # Write the CSV header
133+
expected_sampling_rate = 250 # Expected sampling rate
134+
drift = ((sampling_rate - expected_sampling_rate) / expected_sampling_rate) * 3600 # Calculate drift in seconds per hour
135+
print(f"Drift: {drift:.2f} seconds/hour") # Print drift
170136

171-
try:
172-
time.sleep(2) # Allow time for Arduino to initialize
137+
total_data_received = 0 # Reset total data received for the next 10-minute interval
138+
last_ten_minute_time = time.time() # Update the last ten-minute interval start
139+
140+
def parse_data(port, baudrate, lsl_flag=False, csv_flag=False): # Main function to process data from the Arduino.
141+
global total_packet_count, start_time, total_data_received, lsl_outlet, last_ten_minute_time
142+
143+
csv_writer = None # CSV writer is initially None
144+
csv_filename = None
145+
146+
# Check if LSL stream is enabled
147+
if lsl_flag:
148+
lsl_stream_info = StreamInfo('BioAmpDataStream', 'EXG', 6, 250, 'float32', 'UpsideDownLabs') # Define LSL stream info
149+
lsl_outlet = StreamOutlet(lsl_stream_info) # Create LSL outlet
150+
print("LSL stream started") # Print message indicating LSL stream has started
151+
time.sleep(0.5) # Delay to ensure that LSL stream setup is complete
152+
153+
# If CSV logging is requested
154+
if csv_flag:
155+
# Generate the filename dynamically based on current date and time
156+
csv_filename = f"data_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv"
157+
print(f"CSV recording started. Data will be saved to {csv_filename}") # Print CSV recording message
158+
159+
# Open the serial port and CSV file
160+
with serial.Serial(port, baudrate, timeout=0.1) as ser: # Open serial port
161+
csv_file = open(csv_filename, mode='w', newline='') if csv_flag else None # Open CSV file if specified
162+
163+
if csv_file:
164+
csv_writer = csv.writer(csv_file) # Create CSV writer
165+
csv_writer.writerow(['Counter', 'Channel1', 'Channel2', 'Channel3', 'Channel4', 'Channel5', 'Channel6']) # Write CSV header
173166

174-
start_timer() # Initialize timer
167+
# Delay to account for initial data and ensure accurate timing
168+
start_timer()
175169

176-
while True:
177-
read_arduino_data(ser, csv_writer) # Read data from Arduino
170+
try:
171+
while True:
172+
read_arduino_data(ser, csv_writer) # Read and process data from Arduino
178173

179-
# if initial_samples_ignored:
180-
current_time = time.time() # Get current timestamp
174+
current_time = time.time() # Get the current time
181175

182-
# Handle minute interval
183-
if current_time - start_time >= 60:
184-
total_data_received += log_minute_data() # Log minute data and add to total
185-
start_time = current_time # Reset minute timer
176+
# Check if a minute has passed and log minute data
177+
if current_time - start_time >= 60:
178+
total_data_received += log_minute_data() # Update total data received
179+
start_time += 60 # Adjust the start time to handle next interval accurately
186180

187-
# Handle 10-minute interval
188-
if current_time - start_time >= 600:
189-
total_data_received += log_minute_data() # Log last minute before the 10-minute interval ends
190-
log_ten_minute_data() # Log data for the 10-minute interval
191-
start_timer() # Reset timers to prevent a partial minute log after the 10-minute interval
181+
# Check if 10 minutes have passed and log ten-minute data
182+
if current_time - last_ten_minute_time >= 600:
183+
total_data_received += log_minute_data() # Update total data received
184+
log_ten_minute_data() # Log ten-minute data
185+
start_timer() # Restart timers
192186

193-
except KeyboardInterrupt:
194-
# Handle keyboard interrupt
195-
print(f"Exiting. \nTotal missing samples: {missing_samples}") # Print missing samples and exit
187+
except KeyboardInterrupt:
188+
if csv_file:
189+
csv_file.close() # Close CSV file
190+
print(f"CSV recording stopped. Data saved to {csv_filename}.") # Print message indicating CSV recording stopped
191+
print(f"Exiting. \nTotal missing samples: {missing_samples}") # Print total missing samples
196192

197193
if __name__ == "__main__":
194+
# Argument parser for command-line interface
198195
parser = argparse.ArgumentParser(description="Upside Down Labs - BioAmp Tool")
199-
parser.add_argument('-d', '--detect', action='store_true', help="Auto-detect Arduino") # Argument to auto-detect Arduino
200-
parser.add_argument('-p', '--port', type=str, help="Specify the COM port") # Argument to specify COM port
201-
parser.add_argument('-b', '--baudrate', type=int, default=57600, help="Set baud rate for the serial communication") # Argument for baud rate
202-
203-
args = parser.parse_args() # Parse command-line arguments
204-
205-
if args.detect:
206-
port = auto_detect_arduino(baudrate=args.baudrate) # Auto-detect Arduino if specified
207-
else:
208-
port = args.port # Use specified port
209-
210-
if port is None:
211-
print("Arduino port not specified or detected. Exiting.")
196+
parser.add_argument('-p', '--port', type=str, help="Specify the COM port")
197+
parser.add_argument('-b', '--baudrate', type=int, default=57600, help="Set baud rate for the serial communication")
198+
parser.add_argument('--csv', action='store_true', help="Create and write to a CSV file")
199+
parser.add_argument('--lsl', action='store_true', help="Start LSL stream")
200+
201+
args = parser.parse_args()
202+
203+
# Determine port and start data parsing based on arguments
204+
if args.lsl:
205+
if args.port:
206+
port = args.port # Use specified port
207+
else:
208+
port = auto_detect_arduino(baudrate=args.baudrate) # Auto-detect Arduino port
209+
210+
if port is None:
211+
print("Arduino port not specified or detected. Exiting.") # Print message if no port detected
212+
else:
213+
parse_data(port, args.baudrate, lsl_flag=args.lsl, csv_flag=args.csv) # Start data parsing
212214
else:
213-
parse_data(port, args.baudrate) # Start processing data
215+
parser.print_help() # Print help message if no valid arguments are provided

0 commit comments

Comments
 (0)