Skip to content

Commit 836b1a7

Browse files
committed
Closing apps and LSL when serial port is disconnected
1 parent fdb9ba4 commit 836b1a7

File tree

2 files changed

+204
-160
lines changed

2 files changed

+204
-160
lines changed

app.py

Lines changed: 106 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import json
1010
from threading import Thread
11+
from flask import Response, stream_with_context
1112

1213
app = Flask(__name__)
1314
app.secret_key = 'your_secret_key_here'
@@ -117,6 +118,69 @@ def connect_and_monitor():
117118
def check_connection():
118119
return jsonify({"connected": npg_running, "message": current_message})
119120

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+
120184
@app.route("/start_lsl", methods=["POST"])
121185
def start_lsl():
122186
global lsl_process, lsl_running, current_message
@@ -136,10 +200,13 @@ def start_lsl():
136200
command.append("--csv")
137201

138202
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()
140207

141208
time.sleep(2)
142-
output = lsl_process.stderr.readline().strip()
209+
output = lsl_process.stdout.readline().strip()
143210
if "No" in output:
144211
current_message = "Failed to start LSL stream"
145212
lsl_running = False
@@ -159,52 +226,32 @@ def start_npg():
159226

160227
if lsl_running:
161228
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})
163235

164236
if npg_running:
165237
current_message = "NPG already running"
166-
return redirect(url_for('home'))
238+
return jsonify({"status": "error", "message": current_message})
167239

168240
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")
170242
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
171243

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..."})
199249

200250
except Exception as e:
201251
current_message = f"Error starting NPG: {str(e)}"
202252
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+
208255
@app.route("/run_app", methods=["POST"])
209256
def run_app():
210257
global current_message
@@ -234,6 +281,21 @@ def run_app():
234281

235282
return redirect(url_for('home'))
236283

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+
237299
@app.route("/stop_all", methods=['POST'])
238300
def stop_all():
239301
global current_message
@@ -266,22 +328,19 @@ def stop_all_processes():
266328
lsl_running = False
267329

268330
if npg_process and npg_process.poll() is None:
269-
npg_process.terminate()
331+
npg_process.send_signal(signal.SIGINT)
270332
try:
271333
npg_process.wait(timeout=3)
272334
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()
279336
try:
280-
process.wait(timeout=3)
337+
npg_process.wait(timeout=2)
281338
except subprocess.TimeoutExpired:
282-
process.kill()
283-
del app_processes[app_name]
339+
npg_process.kill()
340+
npg_running = False
284341

342+
stop_dependent_apps("all")
343+
current_message = "All processes stopped"
285344
print("All processes terminated.")
286345

287346
def handle_sigint(signal_num, frame):

0 commit comments

Comments
 (0)