Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Interface is created to run all applications by starting lsl stream #24

Merged
merged 6 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from flask import Flask, render_template, jsonify, request
import subprocess
import psutil
import os
import signal
import sys

app = Flask(__name__)
lsl_process = None
app_processes = {}

def is_process_running(name):
for proc in psutil.process_iter(['pid', 'name']):
if name in proc.info['name']:
return True
return False

@app.route("/")
def home():
return render_template("index.html", lsl_started=False, lsl_status="Stopped", lsl_color="red")

@app.route("/start_lsl", methods=["POST"])
def start_lsl():
global lsl_process
if lsl_process and lsl_process.poll() is None:
return jsonify({"status": "LSL stream already running", "lsl_started": True})
try:
# Start the LSL stream as a subprocess
if sys.platform == "win32":
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"], creationflags=subprocess.CREATE_NO_WINDOW)
else:
lsl_process = subprocess.Popen(["python", "chords.py", "--lsl"])

if lsl_process.poll() is None:
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green")
else:
return render_template("index.html", lsl_started=False, lsl_status="Failed to Start", lsl_color="red")
except Exception as e:
return render_template("index.html", lsl_started=False, lsl_status=f"Error: {e}", lsl_color="red")

@app.route("/run_app", methods=["POST"])
def run_app():
app_name = request.form.get("app_name")

# Check if the app is already running
if app_name in app_processes and app_processes[app_name].poll() is None:
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"{app_name} is already Running")

try:
# Start the app subprocess
if sys.platform == "win32":
process = subprocess.Popen(["python", f"{app_name}.py"], creationflags=subprocess.CREATE_NO_WINDOW)
else:
process = subprocess.Popen(["python", f"{app_name}.py"])

app_processes[app_name] = process
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=None)
except Exception as e:
return render_template("index.html", lsl_started=True, lsl_status="Running", lsl_color="green", message=f"Error starting {app_name}: {e}")

@app.route("/stop_lsl", methods=['POST'])
def stop_lsl():
# Terminate LSL process
if lsl_process and lsl_process.poll() is None:
lsl_process.terminate()

# Terminate all app processes
for app_name, process in app_processes.items():
if process.poll() is None:
process.terminate()

# Shutdown the server gracefully
os._exit(0)
return jsonify({'status': 'LSL Stream and applications stopped and server is shutting down.'})

if __name__ == "__main__":
app.run(debug=True)
3 changes: 2 additions & 1 deletion app_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pygame==2.6.1
neurokit2==0.2.10
plotly==5.24.1
pandas==2.2.3
tk==0.1.0
tk==0.1.0
PyAutoGUI==0.9.54
3 changes: 2 additions & 1 deletion chords.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@
board = "" # Variable for Connected Arduino Board
supported_boards = {
"UNO-R3": {"sampling_rate": 250, "Num_channels": 6},
"UNO-CLONE": {"sampling_rate": 250, "Num_channels": 6},
"UNO-CLONE": {"sampling_rate": 250, "Num_channels": 6}, # Baud Rate 115200
"UNO-R4": {"sampling_rate": 500, "Num_channels": 6},
"RPI-PICO-RP2040": {"sampling_rate": 500, "Num_channels": 3},
"NANO-CLONE": {"sampling_rate": 250, "Num_channels": 8}, # Baud Rate 115200
}

# Initialize gloabal variables for Incoming Data
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion applications/gui.py → gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def init_gui():
global plots, curves
plots = []
curves = []
colors = ['#FF3B3B', '#00FF66', '#FF1493', '#007BFF', '#FFA500', '#FF00FF'] # Different colors for each channel
colors = ['#D10054', '#007A8C', '#0A6847', '#674188', '#E65C19', '#2E073F' ] # Different colors for each channel
for i in range(6):
plot = pg.PlotWidget(title=f"Channel {i + 1}") # Create a plot widget for each channel
layout.addWidget(plot) # Add the plot to the layout
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion applications/keystroke.py → keystroke.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def move(event):
horizontal_frame = tk.Frame(popup)
horizontal_frame.pack(expand=True, pady=10)

eye_icon = PhotoImage(file="media/icons8-eye-30.png")
eye_icon = PhotoImage(file="media\\icons8-eye-30.png")

blink_button = tk.Button(horizontal_frame, image=eye_icon, width=70, height=38, bg="#FFFFFF")
blink_button.image = eye_icon
Expand Down
File renamed without changes
125 changes: 125 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}

.container {
text-align: center;
padding: 20px;
}

.header h1 {
font-size: 2.5em;
margin-bottom: 20px;
color: #333;
}

.controls button {
padding: 15px 30px;
margin: 10px;
font-size: 18px;
cursor: pointer;
border: none;
border-radius: 25px; /* Rounded shape */
background-color: #d1a56c; /* Beige color */
color: #333; /* Dark text */
transition: transform 0.3s ease, background-color 0.3s ease;
}

.controls button:hover {
background-color: hsl(34, 47%, 40%); /* Dark beige on hover */
}

button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}

/* App button layout */
.app-buttons {
margin-top: 20px;
}

.row {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 20px;
}

.app-buttons form {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px; /* Spacing between buttons */
}

/* App button styles */
.app-buttons button {
width: 200px;
height: 80px;
margin: 10px;
font-size: 20px; /* Larger font size */
font-weight: bold;
text-transform: uppercase; /* Stylish text */
border-radius: 25px; /* Rounded edges */
border: none;
color: white; /* Font color */
cursor: pointer;
background-color:#9ba59c; /* gray color */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: transform 0.3s, background-color 0.3s;
}

/* Hover effect for app buttons */
.app-buttons button:hover {
background-color: #656d67; /* Darker shade on hover */
transform: scale(1.1);
opacity: 0.9;
}

/* Button active (on click) effect */
.app-buttons button:active {
transform: scale(1.05);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

button:disabled {
cursor: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><text x="0" y="16" font-size="16">🚫</text></svg>'), not-allowed;
}

.popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.9);
border: 2px solid #333;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
text-align: center;
animation: fade-out 3s forwards;
}

.popup p {
font-size: 18px;
font-weight: bold;
color: #333;
}

@keyframes fade-out {
0% {
opacity: 1;
}
80% {
opacity: 1;
}
100% {
opacity: 0;
visibility: hidden;
}
}
55 changes: 55 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chords-Python</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<div class="header">
<h1>Chords-Python</h1>
</div>

<!-- Pop-up message -->
{% if message %}
<div class="popup fade-out">
<p>{{ message }}</p>
</div>
{% endif %}

<div class="controls">
{% if not lsl_started %}
<form action="/start_lsl" method="POST">
<button type="submit" id="start_lsl_button">Start LSL Stream</button>
</form>
{% else %}
<button id="start_lsl_button" disabled>LSL Stream Running</button>
{% endif %}
</div>

<div class="app-buttons">
<!-- Row 1: ECG, EMG, EOG, EEG -->
<div class="row">
<form action="/run_app" method="POST">
<button type="submit" name="app_name" value="heartbeat_ecg" {% if not lsl_started %}disabled{% endif %}>ECG with Heart Rate</button>
<button type="submit" name="app_name" value="emgenvelope" {% if not lsl_started %}disabled{% endif %}>EMG with Envelope</button>
<button type="submit" name="app_name" value="eog" {% if not lsl_started %}disabled{% endif %}>EOG with Blinks</button>
<button type="submit" name="app_name" value="ffteeg" {% if not lsl_started %}disabled{% endif %}>EEG with FFT</button>
</form>
</div>

<!-- Row 2: Game, GUI, Keystroke, CSVPlotter -->
<div class="row">
<form action="/run_app" method="POST">
<button type="submit" name="app_name" value="game" {% if not lsl_started %}disabled{% endif %}>Force Ball Game</button>
<button type="submit" name="app_name" value="gui" {% if not lsl_started %}disabled{% endif %}>GUI of 6 Channels</button>
<button type="submit" name="app_name" value="keystroke" {% if not lsl_started %}disabled{% endif %}>Keystroke Emulator</button>
<button type="submit" name="app_name" value="csvplotter" {% if not lsl_started %}disabled{% endif %}>CSV Plotter</button>
</form>
</div>
</div>
</div>
</body>
</html>