Skip to content

Commit c46fc0a

Browse files
committed
NPG Button added
1 parent 58a6c4c commit c46fc0a

File tree

3 files changed

+140
-45
lines changed

3 files changed

+140
-45
lines changed

app.py

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import os
55
import signal
66
import sys
7+
import atexit
8+
import threading
79

810
app = Flask(__name__)
911
lsl_process = None
12+
lsl_running = False
13+
npg_running = False
14+
npg_process = None
1015
app_processes = {}
1116

1217
def is_process_running(name):
@@ -21,36 +26,70 @@ def home():
2126

2227
@app.route("/start_lsl", methods=["POST"])
2328
def start_lsl():
24-
global lsl_process
25-
if lsl_process and lsl_process.poll() is None:
29+
global lsl_process, lsl_running
30+
31+
if lsl_running:
2632
return jsonify({"status": "LSL stream already running", "lsl_started": True})
33+
2734
try:
28-
# Start the LSL stream as a subprocess
2935
if sys.platform == "win32":
30-
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
36+
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW, text=True)
3137
else:
32-
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
33-
output = lsl_process.stderr.readline().decode().strip() # Read the initial stderr line
38+
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
3439

40+
output = lsl_process.stderr.readline().strip()
3541
print(output)
36-
if output == "No":
37-
return render_template("index.html", lsl_started=False, lsl_status="Failed to Start", lsl_color="red")
42+
43+
if "No" in output:
44+
lsl_running = False
45+
return jsonify({"lsl_started": False, "lsl_status": "Failed to Start", "lsl_color": "red"})
3846
else:
39-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green")
40-
except subprocess.TimeoutExpired:
41-
return render_template(
42-
"index.html", lsl_started=False, lsl_status="Timeout Error", lsl_color="red"
43-
)
47+
lsl_running = True
48+
return jsonify({"lsl_started": True, "lsl_status": "Running", "lsl_color": "green"})
49+
4450
except Exception as e:
45-
return render_template("index.html", lsl_started=False, lsl_status=f"Error: {e}", lsl_color="red")
51+
return jsonify({"lsl_started": False, "lsl_status": f"Error: {e}", "lsl_color": "red"})
52+
53+
def read_npg_output():
54+
global npg_process
55+
56+
if npg_process:
57+
for line in iter(npg_process.stdout.readline, ''):
58+
print(line.strip()) # Print npg.py output to the terminal
59+
60+
@app.route("/start_npg", methods=["POST"])
61+
def start_npg():
62+
global npg_process, npg_running
63+
64+
if npg_running:
65+
return jsonify({"status": "NPG already running", "npg_started": True})
66+
67+
try:
68+
if sys.platform == "win32":
69+
npg_process = subprocess.Popen(["python", "npg.py"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NO_WINDOW, text=True, bufsize=1)
70+
else:
71+
npg_process = subprocess.Popen(["python3", "npg.py"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
72+
73+
# Start a separate thread to read npg.py output
74+
threading.Thread(target=read_npg_output, daemon=True).start()
75+
76+
npg_running = True
77+
return render_template("index.html", npg_started=True, npg_status="Running", npg_color="green", apps_enabled=True)
78+
79+
except Exception as e:
80+
npg_running = False
81+
return render_template("index.html", npg_started=False, npg_status=f"Error: {e}", npg_color="red", apps_enabled=False)
4682

4783
@app.route("/run_app", methods=["POST"])
4884
def run_app():
85+
global lsl_running, npg_running
4986
app_name = request.form.get("app_name")
5087

51-
# Check if the app is already running
88+
if not (lsl_running or npg_running):
89+
return render_template("index.html", message="Start LSL or NPG first!", running_apps=app_processes.keys())
90+
5291
if app_name in app_processes and app_processes[app_name].poll() is None:
53-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"{app_name} is already Running", running_apps=app_processes.keys())
92+
return render_template("index.html", message=f"{app_name} is already running", running_apps=app_processes.keys())
5493

5594
try:
5695
# Start the app subprocess
@@ -60,19 +99,19 @@ def run_app():
6099
process = subprocess.Popen(["python", f"{app_name}.py"])
61100

62101
app_processes[app_name] = process
63-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", running_apps=app_processes.keys(), message=None)
64-
102+
return render_template("index.html", running_apps=app_processes.keys(), message=None)
65103
except Exception as e:
66-
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"Error starting {app_name}: {e}", running_apps=app_processes.keys())
104+
return render_template("index.html", message=f"Error starting {app_name}: {e}", running_apps=app_processes.keys())
67105

68106
@app.route("/app_status", methods=["GET"])
69107
def app_status():
70108
# Check the status of all apps
71109
try:
72110
statuses = {
73-
app_name: (process.poll() is None) # True if running, False if not
74-
for app_name, process in app_processes.items()
111+
"lsl_started": lsl_running,
112+
"npg_started": npg_running
75113
}
114+
statuses.update({app_name: (process.poll() is None) for app_name, process in app_processes.items()})
76115
return jsonify(statuses)
77116
except Exception as e:
78117
return jsonify({"error": str(e)}), 500
@@ -83,7 +122,7 @@ def stop_lsl():
83122
return jsonify({'status': 'LSL Stream and applications stopped and server is shutting down.'})
84123

85124
def stop_all_processes():
86-
global lsl_process, app_processes
125+
global lsl_process, npg_process, app_processes, lsl_running, npg_running
87126

88127
# Terminate LSL process
89128
if lsl_process and lsl_process.poll() is None:
@@ -92,25 +131,35 @@ def stop_all_processes():
92131
lsl_process.wait(timeout=3)
93132
except subprocess.TimeoutExpired:
94133
lsl_process.kill()
134+
lsl_running = False
135+
136+
if npg_process and npg_process.poll() is None:
137+
npg_process.terminate()
138+
try:
139+
npg_process.wait(timeout=3)
140+
except subprocess.TimeoutExpired:
141+
npg_process.kill()
142+
npg_running = False
95143

96-
# Terminate all app processes
97-
for app_name, process in app_processes.items():
144+
for app_name, process in list(app_processes.items()):
98145
if process.poll() is None:
99146
process.terminate()
100147
try:
101148
process.wait(timeout=3)
102149
except subprocess.TimeoutExpired:
103150
process.kill()
151+
del app_processes[app_name]
104152

105-
app_processes.clear()
106153
print("All processes terminated.")
107154

108155
def handle_sigint(signal_num, frame):
109156
print("\nCtrl+C pressed! Stopping all processes...")
110157
stop_all_processes()
111158
sys.exit(0)
112159

113-
signal.signal(signal.SIGINT, handle_sigint) # Register signal handler for Ctrl+C
160+
# Register signal handler for Ctrl+C
161+
signal.signal(signal.SIGINT, handle_sigint)
162+
atexit.register(stop_all_processes)
114163

115164
if __name__ == "__main__":
116165
app.run(debug=True)

static/style.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ body {
1010
padding: 20px;
1111
}
1212

13+
.controls-container {
14+
display: flex;
15+
justify-content: center;
16+
gap: 20px;
17+
}
18+
19+
.controls {
20+
display: flex;
21+
}
22+
1323
.header h1 {
1424
font-size: 48px;
1525
font-weight: bold;
@@ -104,6 +114,10 @@ button:disabled {
104114
cursor: not-allowed;
105115
}
106116

117+
.npg-disabled button {
118+
cursor: not-allowed;
119+
}
120+
107121
button.running {
108122
background-color: rgb(105, 206, 105);
109123
cursor: not-allowed;

templates/index.html

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,48 @@ <h1>Chords-Python Applications</h1>
2020
</div>
2121
{% endif %}
2222

23-
<div class="controls">
23+
<div class="controls-container">
24+
<div class="controls">
2425
{% if not lsl_started %}
2526
<form action="/start_lsl" method="POST">
2627
<button type="submit" id="start_lsl_button" class="lsl-not-running">Start LSL Stream</button>
2728
</form>
2829
{% else %}
2930
<button id="start_lsl_button" class="lsl-running" disabled>LSL Stream Running</button>
3031
{% endif %}
31-
</div>
32-
<div class="app-buttons {% if not lsl_started %}lsl-disabled{% endif %}">
32+
</div>
33+
<div class="controls">
34+
{% if not npg_started %}
35+
<form action="/start_npg" method="POST">
36+
<button type="submit" id="start_npg_button" class="npg-not-running">Start NPG Stream</button>
37+
</form>
38+
{% else %}
39+
<button id="start_npg_button" class="npg-running" disabled>NPG Stream Running</button>
40+
{% endif %}
41+
</div>
42+
</div>
43+
<div class="app-buttons {% if not (lsl_started or npg_started) %}disabled-apps{% endif %}">
3344
<!-- Row 1: ECG, EMG, EOG, EEG -->
3445
<div class="row">
3546
<form action="/run_app" method="POST">
3647
<button type="submit" name="app_name" value="heartbeat_ecg"
3748
class="{% if 'heartbeat_ecg' in running_apps %}running{% else %}not-running{% endif %}"
38-
{% if not lsl_started %}disabled{% endif %}>
49+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
3950
ECG with Heart Rate
4051
</button>
4152
<button type="submit" name="app_name" value="emgenvelope"
4253
class="{% if 'emgenvelope' in running_apps %}running{% else %}not-running{% endif %}"
43-
{% if not lsl_started %}disabled{% endif %}>
54+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
4455
EMG with Envelope
4556
</button>
4657
<button type="submit" name="app_name" value="eog"
4758
class="{% if 'eog' in running_apps %}running{% else %}not-running{% endif %}"
48-
{% if not lsl_started %}disabled{% endif %}>
59+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
4960
EOG with Blinks
5061
</button>
5162
<button type="submit" name="app_name" value="ffteeg"
5263
class="{% if 'ffteeg' in running_apps %}running{% else %}not-running{% endif %}"
53-
{% if not lsl_started %}disabled{% endif %}>
64+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
5465
EEG with FFT
5566
</button>
5667
</form>
@@ -61,46 +72,67 @@ <h1>Chords-Python Applications</h1>
6172
<form action="/run_app" method="POST">
6273
<button type="submit" name="app_name" value="game"
6374
class="{% if 'game' in running_apps %}running{% else %}not-running{% endif %}"
64-
{% if not lsl_started %}disabled{% endif %}>
75+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
6576
EEG Tug of War Game
6677
</button>
6778
<button type="submit" name="app_name" value="beetle"
6879
class="{% if 'beetle' in running_apps %}running{% else %}not-running{% endif %}"
69-
{% if not lsl_started %}disabled{% endif %}>
80+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
7081
EEG Beetle Game
7182
</button>
7283
<button type="submit" name="app_name" value="gui"
7384
class="{% if 'gui' in running_apps %}running{% else %}not-running{% endif %}"
74-
{% if not lsl_started %}disabled{% endif %}>
85+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
7586
GUI of Channels
7687
</button>
7788
<button type="submit" name="app_name" value="keystroke"
7889
class="{% if 'keystroke' in running_apps %}running{% else %}not-running{% endif %}"
79-
{% if not lsl_started %}disabled{% endif %}>
90+
{% if not (lsl_started or npg_started) %}disabled{% endif %}>
8091
Keystroke Emulator
8192
</button>
8293
<button type="submit" name="app_name" value="csvplotter"
8394
class="{% if 'csvplotter' in running_apps %}running{% else %}not-running{% endif %}"
84-
{% if not lsl_started %}disabled{% endif %}>
95+
{% if not lsl_started or npg_started %}disabled{% endif %}>
8596
CSV Plotter
8697
</button>
8798
</form>
8899
</div>
89100
</div>
90101
</div>
91-
<script> // For checking the running status of the apps
102+
<script>
103+
function updateButtons(lsl_started, npg_started) {
104+
let buttons = document.querySelectorAll('.app-buttons button');
105+
buttons.forEach(button => {
106+
button.disabled = !(lsl_started || npg_started);
107+
});
108+
109+
let appButtonsDiv = document.querySelector('.app-buttons');
110+
if (lsl_started || npg_started) {
111+
appButtonsDiv.classList.remove('disabled-apps');
112+
} else {
113+
appButtonsDiv.classList.add('disabled-apps');
114+
}
115+
}
116+
92117
function updateAppStatus() {
93118
fetch('/app_status')
94119
.then(response => response.json())
95120
.then(statuses => {
121+
const lslStarted = statuses.lsl_started || false;
122+
const npgStarted = statuses.npg_started || false;
123+
124+
updateButtons(lslStarted, npgStarted);
125+
96126
Object.keys(statuses).forEach(app => {
97127
const button = document.querySelector(`button[value="${app}"]`);
98-
if (statuses[app]) {
99-
button.classList.add("running");
100-
button.classList.remove("not-running");
101-
} else {
102-
button.classList.add("not-running");
103-
button.classList.remove("running");
128+
if (button) {
129+
if (statuses[app]) {
130+
button.classList.add("running");
131+
button.classList.remove("not-running");
132+
} else {
133+
button.classList.add("not-running");
134+
button.classList.remove("running");
135+
}
104136
}
105137
});
106138
})

0 commit comments

Comments
 (0)