8
8
import os
9
9
import json
10
10
from threading import Thread
11
+ from flask import Response , stream_with_context
11
12
12
13
app = Flask (__name__ )
13
14
app .secret_key = 'your_secret_key_here'
@@ -117,6 +118,69 @@ def connect_and_monitor():
117
118
def check_connection ():
118
119
return jsonify ({"connected" : npg_running , "message" : current_message })
119
120
121
+ def monitor_process_output (process , process_type ):
122
+ global lsl_running , npg_running , current_message , app_processes
123
+
124
+ while True :
125
+ if process .poll () is not None : # Process has terminated
126
+ if process_type == "lsl" :
127
+ lsl_running = False
128
+ current_message = "LSL stream terminated"
129
+ elif process_type == "npg" :
130
+ npg_running = False
131
+ current_message = "NPG stream terminated"
132
+ break
133
+
134
+ line = process .stdout .readline ()
135
+ if not line :
136
+ time .sleep (0.1 )
137
+ continue
138
+
139
+ print (f"{ process_type } output:" , line .strip ()) # Debug logging
140
+
141
+ if process_type == "lsl" and ("Error while closing serial connection" in line or "disconnected" in line .lower ()):
142
+ lsl_running = False
143
+ current_message = "LSL stream error - connection closed"
144
+ stop_dependent_apps ("lsl" )
145
+ if process .poll () is None :
146
+ process .terminate ()
147
+ break
148
+
149
+ elif process_type == "npg" and ("Data Interrupted" in line or "Data Interrupted (Bluetooth disconnected)" in line ):
150
+ npg_running = False
151
+ current_message = "NPG stream error - data interrupted"
152
+ stop_dependent_apps ("npg" )
153
+ if process .poll () is None :
154
+ process .terminate ()
155
+ break
156
+
157
+ def stop_dependent_apps (stream_type ):
158
+ global app_processes , current_message , lsl_running , npg_running
159
+
160
+ apps_to_stop = []
161
+ for app_name , process in list (app_processes .items ()):
162
+ if process .poll () is None : # If process is running
163
+ apps_to_stop .append (app_name )
164
+ process .terminate ()
165
+ try :
166
+ process .wait (timeout = 2 )
167
+ except subprocess .TimeoutExpired :
168
+ process .kill ()
169
+ del app_processes [app_name ]
170
+
171
+ if stream_type == "lsl" :
172
+ lsl_running = False
173
+ elif stream_type == "npg" :
174
+ npg_running = False
175
+ elif stream_type == "all" :
176
+ lsl_running = False
177
+ npg_running = False
178
+
179
+ if apps_to_stop :
180
+ current_message = f"Stopped { ', ' .join (apps_to_stop )} due to { stream_type .upper ()} stream termination"
181
+ elif stream_type in ["lsl" , "npg" ]:
182
+ current_message = f"{ stream_type .upper ()} stream terminated - dependent apps stopped"
183
+
120
184
@app .route ("/start_lsl" , methods = ["POST" ])
121
185
def start_lsl ():
122
186
global lsl_process , lsl_running , current_message
@@ -136,10 +200,13 @@ def start_lsl():
136
200
command .append ("--csv" )
137
201
138
202
creation_flags = subprocess .CREATE_NO_WINDOW if sys .platform == "win32" else 0
139
- lsl_process = subprocess .Popen (command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , creationflags = creation_flags , text = True , bufsize = 1 )
203
+ lsl_process = subprocess .Popen (command , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , creationflags = creation_flags , text = True , bufsize = 1 )
204
+
205
+ monitor_thread = Thread (target = monitor_process_output , args = (lsl_process , "lsl" ), daemon = True )
206
+ monitor_thread .start ()
140
207
141
208
time .sleep (2 )
142
- output = lsl_process .stderr .readline ().strip ()
209
+ output = lsl_process .stdout .readline ().strip ()
143
210
if "No" in output :
144
211
current_message = "Failed to start LSL stream"
145
212
lsl_running = False
@@ -159,52 +226,32 @@ def start_npg():
159
226
160
227
if lsl_running :
161
228
current_message = "Please stop LSL stream first"
162
- return redirect (url_for ('home' ))
229
+ return jsonify ({"status" : "error" , "message" : current_message })
230
+
231
+ device_address = session .get ('selected_device' )
232
+ if not device_address :
233
+ current_message = "No device selected"
234
+ return jsonify ({"status" : "error" , "message" : current_message })
163
235
164
236
if npg_running :
165
237
current_message = "NPG already running"
166
- return redirect ( url_for ( 'home' ) )
238
+ return jsonify ({ "status" : "error" , "message" : current_message } )
167
239
168
240
try :
169
- script_path = os .path .join (os .path .dirname (os .path .abspath (__file__ )), "one .py" )
241
+ script_path = os .path .join (os .path .dirname (os .path .abspath (__file__ )), "npg-ble .py" )
170
242
creation_flags = subprocess .CREATE_NO_WINDOW if sys .platform == "win32" else 0
171
243
172
- npg_process = subprocess .Popen ([sys .executable , script_path ], stdout = subprocess .PIPE , stderr = subprocess .STDOUT , creationflags = creation_flags , text = True , bufsize = 1 , cwd = os .path .dirname (os .path .abspath (__file__ )))
173
-
174
- start_time = time .time ()
175
- connected = False
176
- while time .time () - start_time < 10 : # 10 second timeout
177
- line = npg_process .stdout .readline ()
178
- if not line :
179
- break
180
- if "Connected to NPG-30:30:f9:f9:db:76" in line .strip ():
181
- current_message = "NPG stream started successfully"
182
- npg_running = True
183
- connected = True
184
- break
185
-
186
- if not connected :
187
- current_message = "Failed to connect NPG stream (timeout)"
188
- npg_process .terminate ()
189
- npg_running = False
190
- return redirect (url_for ('home' ))
191
-
192
- def consume_output ():
193
- while npg_process .poll () is None : # While process is running
194
- npg_process .stdout .readline () # Keep reading to prevent buffer fill
195
-
196
- import threading
197
- output_thread = threading .Thread (target = consume_output , daemon = True )
198
- output_thread .start ()
244
+ npg_process = subprocess .Popen ([sys .executable , script_path , "--connect" , device_address ], stdout = subprocess .PIPE , stderr = subprocess .STDOUT , creationflags = creation_flags , text = True , bufsize = 1 )
245
+
246
+ monitor_thread = Thread (target = monitor_process_output , args = (npg_process , "npg" ), daemon = True )
247
+ monitor_thread .start ()
248
+ return jsonify ({"status" : "pending" , "message" : "Attempting to connect to NPG device..." })
199
249
200
250
except Exception as e :
201
251
current_message = f"Error starting NPG: { str (e )} "
202
252
npg_running = False
203
- if 'npg_process' in globals () and npg_process .poll () is None :
204
- npg_process .terminate ()
205
-
206
- return redirect (url_for ('home' ))
207
-
253
+ return jsonify ({"status" : "error" , "message" : current_message })
254
+
208
255
@app .route ("/run_app" , methods = ["POST" ])
209
256
def run_app ():
210
257
global current_message
@@ -234,6 +281,21 @@ def run_app():
234
281
235
282
return redirect (url_for ('home' ))
236
283
284
+ @app .route ("/stream_events" )
285
+ def stream_events ():
286
+ def event_stream ():
287
+ last_state = {"lsl_running" : False , "npg_running" : False , "running_apps" : [], "message" : "" }
288
+
289
+ while True :
290
+ current_state = {"lsl_running" : lsl_running , "npg_running" : npg_running , "running_apps" : [k for k ,v in app_processes .items () if v .poll () is None ], "message" : current_message }
291
+ if current_state != last_state :
292
+ yield f"data: { json .dumps (current_state )} \n \n "
293
+ last_state = current_state .copy ()
294
+
295
+ time .sleep (0.5 )
296
+
297
+ return Response (event_stream (), mimetype = "text/event-stream" )
298
+
237
299
@app .route ("/stop_all" , methods = ['POST' ])
238
300
def stop_all ():
239
301
global current_message
@@ -266,22 +328,19 @@ def stop_all_processes():
266
328
lsl_running = False
267
329
268
330
if npg_process and npg_process .poll () is None :
269
- npg_process .terminate ( )
331
+ npg_process .send_signal ( signal . SIGINT )
270
332
try :
271
333
npg_process .wait (timeout = 3 )
272
334
except subprocess .TimeoutExpired :
273
- npg_process .kill ()
274
- npg_running = False
275
-
276
- for app_name , process in list (app_processes .items ()):
277
- if process .poll () is None :
278
- process .terminate ()
335
+ npg_process .terminate ()
279
336
try :
280
- process .wait (timeout = 3 )
337
+ npg_process .wait (timeout = 2 )
281
338
except subprocess .TimeoutExpired :
282
- process .kill ()
283
- del app_processes [ app_name ]
339
+ npg_process .kill ()
340
+ npg_running = False
284
341
342
+ stop_dependent_apps ("all" )
343
+ current_message = "All processes stopped"
285
344
print ("All processes terminated." )
286
345
287
346
def handle_sigint (signal_num , frame ):
0 commit comments