Skip to content

Commit da83515

Browse files
committed
Added Comments in app.py file
1 parent 4677d16 commit da83515

File tree

1 file changed

+83
-66
lines changed

1 file changed

+83
-66
lines changed

app.py

Lines changed: 83 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,32 @@
1111
from flask import Response
1212

1313
app = Flask(__name__)
14-
app.secret_key = 'your_secret_key_here'
14+
app.secret_key = '--'
1515

16-
lsl_process = None
17-
lsl_running = False
18-
npg_running = False
19-
npg_process = None
20-
app_processes = {}
21-
current_message = None
22-
discovered_devices = []
23-
npg_connection_thread = None
16+
lsl_process = None # Process for LSL
17+
lsl_running = False # Flag to check if LSL is running
18+
npg_running = False # Flag to check if NPG is running
19+
npg_process = None # Process for NPG
20+
app_processes = {} # Dictionary to hold other app processes
21+
current_message = None # Message to display in the UI
22+
discovered_devices = [] # List for all discovered devices
23+
npg_connection_thread = None # Thread for NPG connection
2424

2525
def is_process_running(name):
26-
for proc in psutil.process_iter(['pid', 'name']):
27-
if name in proc.info['name']:
28-
return True
29-
return False
26+
"""Function to check if a process is running by name."""
27+
for proc in psutil.process_iter(['pid', 'name']): # Iterate through all processes
28+
if name in proc.info['name']: # Check if the process name matches
29+
return True # Returns True if process found
30+
return False # Returns False if process not found
3031

3132
@app.route("/")
3233
def home():
34+
"""Render the home page with the current status of LSL Stream , NPG stream, running applications, messages."""
3335
return render_template("index.html", lsl_started=lsl_running, npg_started=npg_running, running_apps=[k for k,v in app_processes.items() if v.poll() is None], message=current_message, devices=session.get('devices', []), selected_device=session.get('selected_device'))
3436

3537
@app.route("/scan_devices", methods=["POST"])
3638
def scan_devices():
39+
"""Scan for available devices using the npg-ble.py --scan script."""
3740
global discovered_devices
3841

3942
try:
@@ -57,8 +60,8 @@ def scan_devices():
5760
"address": parts[1]
5861
})
5962

60-
session['devices'] = devices
61-
discovered_devices = devices
63+
session['devices'] = devices # Store devices in session
64+
discovered_devices = devices # Update devices list to global variable
6265
return jsonify({"status": "success", "devices": devices})
6366

6467
except subprocess.TimeoutExpired:
@@ -70,48 +73,50 @@ def scan_devices():
7073

7174
@app.route("/connect_device", methods=["POST"])
7275
def connect_device():
76+
"""Handle POST request to connect to a Bluetooth device"""
7377
global npg_process, npg_running, npg_connection_thread, current_message
7478

75-
device_address = request.form.get("device_address")
76-
if not device_address:
79+
device_address = request.form.get("device_address") # Get device address from the POST form data
80+
if not device_address: # Check if device address was provided
7781
return jsonify({"status": "error", "message": "No device selected"})
7882

79-
session['selected_device'] = device_address
83+
session['selected_device'] = device_address # Store selected device in session
8084

81-
if npg_connection_thread and npg_connection_thread.is_alive():
82-
if npg_process and npg_process.poll() is None:
85+
if npg_connection_thread and npg_connection_thread.is_alive(): # Check if there's an existing connection thread running
86+
if npg_process and npg_process.poll() is None: # If any active NPG process, terminate it
8387
npg_process.terminate()
8488
try:
85-
npg_process.wait(timeout=2)
89+
npg_process.wait(timeout=2) # Wait for process to terminate (with timeout)
8690
except subprocess.TimeoutExpired:
87-
npg_process.kill()
91+
npg_process.kill() # Force kill if process doesn't terminate smoothly within timeout
8892

8993
def connect_and_monitor():
94+
"""Connect to the device and monitor the output."""
9095
global npg_process, npg_running, current_message
9196

9297
try:
93-
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "npg-ble.py")
98+
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "npg-ble.py") # Get the path of the npg.ble script to run
9499
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
95-
npg_process = subprocess.Popen([sys.executable, script_path, "--connect", device_address], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, creationflags=creation_flags)
100+
npg_process = subprocess.Popen([sys.executable, script_path, "--connect", device_address], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, creationflags=creation_flags) # Start the process with no window
96101
time.sleep(1)
97102

98103
# Monitor the output for connection status
99104
connected = False
100-
for line in iter(npg_process.stdout.readline, ''):
101-
if "Connected to" in line:
102-
connected = True
103-
if npg_process.poll() is not None:
105+
for line in iter(npg_process.stdout.readline, ''): # Read lines from the process output
106+
if "Connected to" in line: # Check for the required message showing successful connection
107+
connected = True # Set connected flag to True
108+
if npg_process.poll() is not None: # If process has terminated, break the loop
104109
break
105110

106-
if connected:
111+
if connected: # If connected successfully, update the global variables
107112
current_message = f"Connected to {device_address}"
108-
npg_running = True
109-
monitor_thread = Thread(target=monitor_process_output, args=(npg_process, "npg"), daemon=True)
110-
monitor_thread.start()
113+
npg_running = True # Set npg_running to True
114+
monitor_thread = Thread(target=monitor_process_output, args=(npg_process, "npg"), daemon=True) # Create a new thread to monitor the process output
115+
monitor_thread.start() # Start the monitoring thread that will handle the output of the process
111116
else:
112-
current_message = f"Failed to connect to {device_address}"
113-
npg_running = False
114-
if npg_process.poll() is None:
117+
current_message = f"Failed to connect to {device_address}" # If not connected, update the message
118+
npg_running = False # Set npg_running to False
119+
if npg_process.poll() is None: # If process is still running, terminate it
115120
npg_process.terminate()
116121

117122
except Exception as e:
@@ -128,6 +133,7 @@ def connect_and_monitor():
128133

129134
@app.route("/check_connection", methods=["GET"])
130135
def check_connection():
136+
"""Check the connection status of the NPG process"""
131137
global npg_running, current_message, npg_process
132138

133139
if npg_process is None or npg_process.poll() is not None:
@@ -150,15 +156,17 @@ def check_connection():
150156
return jsonify({"connected": True, "message": current_message})
151157
elif "Data Interrupted" in line or "Data Interrupted (Bluetooth disconnected)" in line:
152158
npg_running = False
159+
stop_dependent_apps("npg")
153160
current_message = line.strip()
154161
return jsonify({"connected": False, "message": current_message})
155162

156163
return jsonify({"connected": npg_running, "message": current_message or "Connecting..."})
157164

158165
def monitor_process_output(process, process_type):
166+
"""Monitor the output of a subprocess(LSL/NPG) and handle termination events."""
159167
global lsl_running, npg_running, current_message, app_processes
160168

161-
while True:
169+
while True: # Infinite loop to keep checking the process output
162170
if process.poll() is not None: # Process has terminated
163171
if process_type == "lsl":
164172
lsl_running = False
@@ -168,27 +176,27 @@ def monitor_process_output(process, process_type):
168176
current_message = "NPG stream terminated"
169177
break
170178

171-
line = process.stdout.readline()
179+
line = process.stdout.readline() # Read a line from the process output
172180
if not line:
173181
time.sleep(0.1)
174182
continue
175183

176184
print(f"{process_type} output:", line.strip()) # Debug logging
177185

178-
if process_type == "npg" and ("Data Interrupted" in line or "Data Interrupted (Bluetooth disconnected)" in line):
186+
if process_type == "npg" and ("Data Interrupted" in line or "Data Interrupted (Bluetooth disconnected)" in line.lower()): # Handle NPG data interruption
179187
current_message = "NPG connection lost - stopping all applications"
180-
npg_running = False
181-
stop_dependent_apps("npg")
188+
npg_running = False # Set npg_running to False
189+
stop_dependent_apps("npg") # Stop all dependent applications
182190

183-
if process.poll() is None:
191+
if process.poll() is None: # If process is still running, terminate it
184192
process.terminate()
185193
try:
186194
process.wait(timeout=0.5)
187195
except subprocess.TimeoutExpired:
188196
process.kill()
189197
break
190198

191-
elif process_type == "lsl" and ("Error while closing serial connection" in line or "disconnected" in line.lower()):
199+
elif process_type == "lsl" and ("Error while closing serial connection" in line or "disconnected" in line.lower()): # Handle LSL stream interruption
192200
current_message = "LSL stream error - connection closed"
193201
lsl_running = False
194202
stop_dependent_apps("lsl")
@@ -197,12 +205,13 @@ def monitor_process_output(process, process_type):
197205
break
198206

199207
def stop_dependent_apps(stream_type):
208+
"""Stop all dependent applications based on the stream type(LSL/NPG)"""
200209
global app_processes, current_message, lsl_running, npg_running
201210

202-
apps_to_stop = []
211+
apps_to_stop = [] # Track which apps we're stopping for status message
203212
for app_name, process in list(app_processes.items()):
204-
if process.poll() is None: # If process is running
205-
apps_to_stop.append(app_name)
213+
if process.poll() is None: # If process is running
214+
apps_to_stop.append(app_name) # Add to stopped apps list
206215
process.terminate()
207216
try:
208217
process.wait(timeout=2)
@@ -225,27 +234,28 @@ def stop_dependent_apps(stream_type):
225234

226235
@app.route("/start_lsl", methods=["POST"])
227236
def start_lsl():
237+
"""Start the LSL stream and handle CSV saving option."""
228238
global lsl_process, lsl_running, current_message
229239
save_csv = request.form.get('csv', 'false').lower() == 'true'
230240

231-
if npg_running:
241+
if npg_running: # Check for conflicting NPG stream
232242
current_message = "Please stop NPG stream first"
233243
return redirect(url_for('home'))
234244

235-
if lsl_running:
245+
if lsl_running: # Prevent duplicate LSL streams
236246
current_message = "LSL stream already running"
237247
return redirect(url_for('home'))
238248

239249
try:
240-
command = ["python", "chords.py", "--lsl"]
241-
if save_csv:
250+
command = ["python", "chords.py", "--lsl"] # Command to start chords.py with LSL streaming
251+
if save_csv: # Check if CSV saving is requested
242252
command.append("--csv")
243253

244254
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
245255
lsl_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=creation_flags, text=True, bufsize=1)
246256

247257
output = lsl_process.stdout.readline().strip()
248-
if "No" in output:
258+
if "No" in output: # Check for failure messages in output
249259
current_message = "Failed to start LSL stream"
250260
lsl_running = False
251261
else:
@@ -258,7 +268,7 @@ def start_lsl():
258268
lsl_process.terminate()
259269
return redirect(url_for('home'))
260270

261-
monitor_thread = Thread(target=monitor_process_output, args=(lsl_process, "lsl"), daemon=True)
271+
monitor_thread = Thread(target=monitor_process_output, args=(lsl_process, "lsl"), daemon=True) # Create a new thread to monitor the LSL process output
262272
monitor_thread.start()
263273

264274
except Exception as e:
@@ -269,18 +279,19 @@ def start_lsl():
269279

270280
@app.route("/start_npg", methods=["POST"])
271281
def start_npg():
282+
""" Start the NPG stream and handle connection to the selected device."""
272283
global npg_process, npg_running, current_message
273284

274-
if lsl_running:
285+
if lsl_running: # Check for conflicting LSL stream
275286
current_message = "Please stop LSL stream first"
276287
return jsonify({"status": "error", "message": current_message})
277288

278289
device_address = session.get('selected_device')
279-
if not device_address:
290+
if not device_address: # Check if a device is selected
280291
current_message = "No device selected"
281292
return jsonify({"status": "error", "message": current_message})
282293

283-
if npg_running:
294+
if npg_running: # Prevent duplicate NPG streams
284295
current_message = "NPG already running"
285296
return jsonify({"status": "error", "message": current_message})
286297

@@ -301,27 +312,28 @@ def start_npg():
301312

302313
@app.route("/run_app", methods=["POST"])
303314
def run_app():
315+
"""Start a specified application process."""
304316
global current_message
305317
app_name = request.form.get("app_name")
306318
valid_apps = ["heartbeat_ecg", "emgenvelope", "eog", "ffteeg", "game", "beetle", "gui", "keystroke", "csvplotter"]
307319

308-
if not (lsl_running or npg_running):
320+
if not (lsl_running or npg_running): # Verify either one of the streams is running
309321
current_message = "Start LSL or NPG first!"
310322
return redirect(url_for('home'))
311323

312324
if app_name not in valid_apps:
313325
current_message = "Invalid application"
314326
return redirect(url_for('home'))
315327

316-
if app_name in app_processes and app_processes[app_name].poll() is None:
328+
if app_name in app_processes and app_processes[app_name].poll() is None: # Check if application is already running
317329
current_message = f"{app_name} is already running"
318330
return redirect(url_for('home'))
319331

320332
try:
321333
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
322-
process = subprocess.Popen(["python", f"{app_name}.py"], creationflags=creation_flags)
334+
process = subprocess.Popen(["python", f"{app_name}.py"], creationflags=creation_flags) # Start the application process
323335

324-
app_processes[app_name] = process
336+
app_processes[app_name] = process # Track running process in global dictionary
325337
current_message = f"{app_name} started successfully"
326338
except Exception as e:
327339
current_message = f"Error starting {app_name}: {str(e)}"
@@ -330,11 +342,13 @@ def run_app():
330342

331343
@app.route("/stream_events")
332344
def stream_events():
345+
"""Stream events to the client using Server-Sent Events (SSE)."""
333346
def event_stream():
334-
last_state = None
347+
"""Generator function that yields system state updates when changes occur."""
348+
last_state = None # Initialize last_state to None
335349

336350
while True:
337-
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, "stream_interrupted": ("Data Interrupted" in current_message if current_message else False)}
351+
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, "stream_interrupted": (("Data Interrupted (Bluetooth Disconnected)" or "Error while closing serial connection" in current_message) if current_message else False)}
338352
if current_state != last_state:
339353
yield f"data: {json.dumps(current_state)}\n\n"
340354
last_state = current_state.copy()
@@ -345,12 +359,14 @@ def event_stream():
345359

346360
@app.route("/stop_all", methods=['POST'])
347361
def stop_all():
362+
""" Stop all running processes and clear the current message."""
348363
global current_message
349364
stop_all_processes()
350365
current_message = "All processes stopped"
351366
return redirect(url_for('home'))
352367

353368
def cleanup_processes():
369+
""" Remove finished processes from the app_processes dictionary."""
354370
global app_processes
355371
app_processes = {
356372
k: v for k, v in app_processes.items()
@@ -359,10 +375,12 @@ def cleanup_processes():
359375

360376
@app.route("/check_app_status", methods=["GET"])
361377
def check_app_status():
378+
""" Check the status of running applications and return a JSON response."""
362379
cleanup_processes() # Remove finished processes
363380
return jsonify({"running_apps": list(app_processes.keys())})
364381

365382
def stop_all_processes():
383+
"""Stop all running processes and clear the current message."""
366384
global lsl_process, npg_process, app_processes, lsl_running, npg_running, current_message
367385

368386
# Terminate LSL process
@@ -373,24 +391,23 @@ def stop_all_processes():
373391
except subprocess.TimeoutExpired:
374392
lsl_process.kill()
375393
lsl_running = False
394+
stop_dependent_apps("lsl")
376395

377396
if npg_process and npg_process.poll() is None:
378-
npg_process.send_signal(signal.SIGINT)
397+
npg_process.terminate()
379398
try:
380399
npg_process.wait(timeout=3)
381400
except subprocess.TimeoutExpired:
382-
npg_process.terminate()
383-
try:
384-
npg_process.wait(timeout=2)
385-
except subprocess.TimeoutExpired:
386-
npg_process.kill()
401+
npg_process.kill()
387402
npg_running = False
403+
stop_dependent_apps("npg")
388404

389405
stop_dependent_apps("all")
390406
current_message = "All processes stopped"
391407
print("All processes terminated.")
392408

393409
def handle_sigint(signal_num, frame):
410+
"""Handle Ctrl+C signal to stop all processes gracefully."""
394411
print("\nCtrl+C pressed! Stopping all processes...")
395412
stop_all_processes()
396413
sys.exit(0)

0 commit comments

Comments
 (0)