4
4
from pylsl import StreamInfo , StreamOutlet
5
5
import sys
6
6
import argparse
7
+ import os
7
8
8
9
# BLE parameters (must match your firmware)
9
10
DEVICE_NAME_PREFIX = "NPG"
22
23
start_time = None
23
24
total_missing_samples = 0
24
25
outlet = None
26
+ last_received_time = None
27
+ DATA_TIMEOUT = 2.0
28
+ monitor_task = None
29
+ client = None
25
30
26
31
def parse_args ():
27
32
parser = argparse .ArgumentParser ()
@@ -43,7 +48,8 @@ async def scan_devices():
43
48
print (f"DEVICE:{ dev .name } |{ dev .address } " )
44
49
45
50
def process_sample (sample_data : bytearray ):
46
- global prev_unrolled_counter , samples_received , start_time , total_missing_samples , outlet
51
+ global prev_unrolled_counter , samples_received , start_time , total_missing_samples , outlet , last_received_time
52
+ last_received_time = time .time ()
47
53
48
54
if len (sample_data ) != SINGLE_SAMPLE_LEN :
49
55
print ("Unexpected sample length:" , len (sample_data ))
@@ -73,9 +79,9 @@ def process_sample(sample_data: bytearray):
73
79
74
80
# Process channels
75
81
channels = [
76
- int .from_bytes (sample_data [1 :3 ]), # Channel 0
77
- int .from_bytes (sample_data [3 :5 ]), # Channel 1
78
- int .from_bytes (sample_data [5 :7 ])]
82
+ int .from_bytes (sample_data [1 :3 ], byteorder = 'little' ), # Channel 0
83
+ int .from_bytes (sample_data [3 :5 ], byteorder = 'little' ), # Channel 1
84
+ int .from_bytes (sample_data [5 :7 ], byteorder = 'little' )] # Channel 2
79
85
80
86
# Push to LSL
81
87
if outlet :
@@ -91,29 +97,39 @@ def process_sample(sample_data: bytearray):
91
97
def notification_handler (sender , data : bytearray ):
92
98
try :
93
99
if len (data ) == NEW_PACKET_LEN :
94
- # Process batched samples
95
100
for i in range (0 , NEW_PACKET_LEN , SINGLE_SAMPLE_LEN ):
96
101
process_sample (data [i :i + SINGLE_SAMPLE_LEN ])
97
102
elif len (data ) == SINGLE_SAMPLE_LEN :
98
- # Process single sample
99
103
process_sample (data )
100
104
else :
101
105
print (f"Unexpected packet length: { len (data )} bytes" )
102
106
except Exception as e :
103
107
print (f"Error processing data: { e } " )
104
108
109
+ async def monitor_connection ():
110
+ global last_received_time , client
111
+
112
+ while True :
113
+ if last_received_time and (time .time () - last_received_time ) > DATA_TIMEOUT :
114
+ print ("\n Data Interrupted" )
115
+ os ._exit (1 )
116
+ if client and not client .is_connected :
117
+ print ("\n Data Interrupted (Bluetooth disconnected)" )
118
+ os ._exit (1 )
119
+
120
+ await asyncio .sleep (0.5 )
121
+
105
122
async def connect_to_device (device_address ):
106
- global outlet
123
+ global outlet , last_received_time , monitor_task , client
107
124
108
125
print (f"Attempting to connect to { device_address } ..." , file = sys .stderr )
109
126
110
127
# Set up LSL stream (500Hz sampling rate)
111
128
info = StreamInfo ("NPG" , "EXG" , 3 , 500 , "int16" , "npg1234" )
112
129
outlet = StreamOutlet (info )
113
130
114
- client = None
131
+ client = BleakClient ( device_address )
115
132
try :
116
- client = BleakClient (device_address )
117
133
await client .connect ()
118
134
119
135
if not client .is_connected :
@@ -122,6 +138,9 @@ async def connect_to_device(device_address):
122
138
123
139
print (f"Connected to { device_address } " )
124
140
141
+ last_received_time = time .time ()
142
+ monitor_task = asyncio .create_task (monitor_connection ())
143
+
125
144
# Send start command
126
145
await client .write_gatt_char (CONTROL_CHAR_UUID , b"START" , response = True )
127
146
print ("Sent START command" )
@@ -140,16 +159,25 @@ async def connect_to_device(device_address):
140
159
print (f"Connection error: { str (e )} " , file = sys .stderr )
141
160
return False
142
161
finally :
162
+ if monitor_task :
163
+ monitor_task .cancel ()
143
164
if client and client .is_connected :
144
165
await client .disconnect ()
145
166
146
167
if __name__ == "__main__" :
147
168
args = parse_args ()
148
169
149
- if args .scan :
150
- asyncio .run (scan_devices ())
151
- elif args .connect :
152
- asyncio .run (connect_to_device (args .connect ))
153
- else :
154
- print ("Please specify --scan or --connect" , file = sys .stderr )
170
+ try :
171
+ if args .scan :
172
+ asyncio .run (scan_devices ())
173
+ elif args .connect :
174
+ asyncio .run (connect_to_device (args .connect ))
175
+ else :
176
+ print ("Please specify --scan or --connect" , file = sys .stderr )
177
+ sys .exit (1 )
178
+ except KeyboardInterrupt :
179
+ print ("\n Script terminated by user" )
180
+ sys .exit (0 )
181
+ except Exception as e :
182
+ print (f"\n Error: { str (e )} " , file = sys .stderr )
155
183
sys .exit (1 )
0 commit comments