1
1
from flask import Flask , render_template , request , jsonify
2
- import asyncio
3
2
from connection import Connection
4
- from threading import Thread
5
- import webbrowser
6
- from pylsl import resolve_streams
7
- import time
8
- from chords_ble import Chords_BLE
9
- import subprocess
10
- import os
11
- import sys
3
+ import threading
4
+ import asyncio
5
+ import logging
6
+ from bleak import BleakScanner
7
+ from flask import Response
8
+ import queue
9
+ import threading
12
10
11
+ console_queue = queue .Queue ()
13
12
app = Flask (__name__ )
13
+ logging .basicConfig (level = logging .INFO )
14
+
15
+ # Global variables
14
16
connection_manager = None
17
+ connection_thread = None
15
18
ble_devices = []
16
- active_connection = None
17
- lsl_stream_active = False
18
- csv_logging_enabled = False # Global CSV logging state
19
- running_apps = {} # Dictionary to keep track of running applications
20
19
21
- @app .route ('/set_csv' , methods = ['POST' ])
22
- def set_csv ():
23
- global csv_logging_enabled
24
- data = request .get_json ()
25
- csv_logging_enabled = data .get ('enabled' , False )
26
- return jsonify ({'status' : 'success' , 'csv_logging' : csv_logging_enabled })
20
+ def run_async (coro ):
21
+ def wrapper (* args , ** kwargs ):
22
+ loop = asyncio .new_event_loop ()
23
+ asyncio .set_event_loop (loop )
24
+ try :
25
+ return loop .run_until_complete (coro (* args , ** kwargs ))
26
+ finally :
27
+ loop .close ()
28
+ return wrapper
27
29
28
30
@app .route ('/' )
29
31
def index ():
30
- colors_list = ['#a855f7' , '#93c5fd' , '#a7f3d0' , '#10b981' , '#b91c1c' , '#1d4ed8' ]
31
- return render_template ('index.html' , colors = colors_list )
32
-
33
- def check_lsl_stream ():
34
- global lsl_stream_active
35
- while True :
36
- streams = resolve_streams ()
37
- if any (s .name () == "BioAmpDataStream" for s in streams ):
38
- lsl_stream_active = True
39
- break
40
- time .sleep (0.5 )
32
+ return render_template ('index.html' )
41
33
42
- @app .route ('/connect' , methods = ['POST' ])
43
- def connect ():
44
- global active_connection , lsl_stream_active
45
- protocol = request .form .get ('protocol' )
46
-
47
- if active_connection :
48
- return jsonify ({'status' : 'error' , 'message' : 'A connection is already active' })
49
-
50
- lsl_stream_active = False
51
- Thread (target = check_lsl_stream ).start ()
52
-
53
- if protocol == 'usb' :
54
- thread = Thread (target = connect_usb )
55
- thread .start ()
56
- return jsonify ({'status' : 'connecting' , 'message' : 'Connecting via USB...' })
57
-
58
- elif protocol == 'wifi' :
59
- thread = Thread (target = connect_wifi )
60
- thread .start ()
61
- return jsonify ({'status' : 'connecting' , 'message' : 'Connecting via WiFi...' })
62
-
63
- elif protocol == 'ble' :
64
- return jsonify ({'status' : 'scanning' , 'message' : 'Scanning for BLE devices...' })
65
-
66
- return jsonify ({'status' : 'error' , 'message' : 'Invalid protocol' })
67
-
68
- @app .route ('/check_connection' , methods = ['GET' ])
69
- def check_connection ():
70
- global lsl_stream_active
71
- if lsl_stream_active :
72
- return jsonify ({'status' : 'success' , 'message' : 'Connection established! LSL stream started.' })
73
- return jsonify ({'status' : 'connecting' , 'message' : 'Connecting...' })
74
-
75
- @app .route ('/scan_ble' , methods = ['GET' ])
76
- def scan_ble ():
34
+ @app .route ('/scan_ble' )
35
+ @run_async
36
+ async def scan_ble_devices ():
77
37
global ble_devices
78
38
try :
79
- ble_scanner = Chords_BLE ()
80
- loop = asyncio .new_event_loop ()
81
- asyncio .set_event_loop (loop )
82
- devices = loop .run_until_complete (ble_scanner .scan_devices ())
83
- loop .close ()
84
-
85
- if not devices :
86
- return jsonify ({'status' : 'error' , 'message' : 'No BLE devices found' })
87
-
88
- ble_devices = devices
89
- devices_list = [{'name' : device .name , 'address' : device .address } for device in devices ]
90
- return jsonify ({'status' : 'success' , 'devices' : devices_list })
39
+ devices = await BleakScanner .discover (timeout = 5 )
40
+ ble_devices = [{'name' : d .name or 'Unknown' , 'address' : d .address }
41
+ for d in devices if d .name and d .name .startswith (('NPG' , 'npg' ))]
42
+ return jsonify ({'status' : 'success' , 'devices' : ble_devices })
91
43
except Exception as e :
92
- return jsonify ({'status' : 'error' , 'message' : str (e )})
93
-
94
- @app .route ('/connect_ble' , methods = ['POST' ])
95
- def connect_ble_device ():
96
- device_address = request .form .get ('address' )
97
- if not device_address :
98
- return jsonify ({'status' : 'error' , 'message' : 'No device address provided' })
99
-
100
- thread = Thread (target = connect_ble , args = (device_address ,))
101
- thread .start ()
102
- return jsonify ({'status' : 'connecting' , 'message' : f'Connecting to BLE device { device_address } ...' })
44
+ logging .error (f"BLE scan error: { str (e )} " )
45
+ return jsonify ({'status' : 'error' , 'message' : str (e )}), 500
103
46
104
- def connect_usb ():
105
- global active_connection , lsl_stream_active , csv_logging_enabled
106
- active_connection = Connection (csv_logging = csv_logging_enabled )
107
- active_connection .connect_usb ()
47
+ @app .route ('/check_stream' )
48
+ def check_stream ():
49
+ if connection_manager and connection_manager .lsl_connection :
50
+ return jsonify ({'connected' : True })
51
+ return jsonify ({'connected' : False })
108
52
109
- def connect_wifi ():
110
- global active_connection , lsl_stream_active , csv_logging_enabled
111
- active_connection = Connection (csv_logging = csv_logging_enabled )
112
- active_connection .connect_wifi ()
53
+ def post_console_message (message ):
54
+ if connection_manager :
55
+ if "LSL stream started" in message :
56
+ connection_manager .stream_active = True
57
+ elif "Connection error" in message or "disconnected" in message :
58
+ connection_manager .stream_active = False
59
+ console_queue .put (message )
113
60
114
- def connect_ble (address ):
115
- global active_connection , lsl_stream_active , csv_logging_enabled
116
- lsl_stream_active = False
117
- Thread (target = check_lsl_stream ).start () # Add this line
118
- active_connection = Connection (csv_logging = csv_logging_enabled )
119
- active_connection .connect_ble (address ) # No duplicate device selection
61
+ @app .route ('/console_updates' )
62
+ def console_updates ():
63
+ def event_stream ():
64
+ while True :
65
+ message = console_queue .get ()
66
+ yield f"data: { message } \n \n "
67
+
68
+ return Response (event_stream (), mimetype = "text/event-stream" )
120
69
121
- @app .route ('/run_application' , methods = ['POST' ])
122
- def run_application ():
123
- global lsl_stream_active , running_apps
124
-
125
- if not lsl_stream_active :
126
- return jsonify ({'status' : 'error' , 'message' : 'LSL stream is not active. Please connect first.' })
127
-
128
- app_name = request .form .get ('app_name' )
129
- if not app_name :
130
- return jsonify ({'status' : 'error' , 'message' : 'No application specified' })
131
-
132
- if app_name in running_apps :
133
- return jsonify ({'status' : 'error' , 'message' : f'{ app_name } is already running' })
134
-
135
- app_mapping = {
136
- 'ECG with Heart Rate' : 'heartbeat_ecg.py' ,
137
- 'EMG with Envelope' : 'emgenvelope.py' ,
138
- 'EOG with Blinks' : 'eog.py' ,
139
- 'EEG with FFT' : 'ffteeg.py' ,
140
- 'EEG Tug of War' : 'game.py' ,
141
- 'EEG Beetle Game' : 'beetle.py' ,
142
- 'EOG Keystroke Emulator' : 'keystroke.py' ,
143
- 'GUI Visualization' : 'gui.py' ,
144
- 'CSV Plotter' : 'csvplotter.py'
145
- }
146
-
147
- script_name = app_mapping .get (app_name )
148
- if not script_name :
149
- return jsonify ({'status' : 'error' , 'message' : 'Invalid application name' })
150
-
151
- if not os .path .exists (script_name ):
152
- return jsonify ({'status' : 'error' , 'message' : f'Script { script_name } not found' })
153
-
154
- try :
155
- process = subprocess .Popen ([sys .executable , script_name ])
156
- running_apps [app_name ] = process
157
- return jsonify ({'status' : 'success' , 'message' : f'{ app_name } started successfully' })
158
- except Exception as e :
159
- return jsonify ({'status' : 'error' , 'message' : str (e )})
160
-
161
- @app .route ('/stop_application' , methods = ['POST' ])
162
- def stop_application ():
163
- global running_apps
164
-
165
- app_name = request .form .get ('app_name' )
166
- if not app_name :
167
- return jsonify ({'status' : 'error' , 'message' : 'No application specified' })
168
-
169
- if app_name not in running_apps :
170
- return jsonify ({'status' : 'error' , 'message' : f'{ app_name } is not running' })
70
+ @app .route ('/connect' , methods = ['POST' ])
71
+ def connect_device ():
72
+ global connection_manager , connection_thread , stream_active
171
73
172
- try :
173
- running_apps [app_name ].terminate ()
174
- del running_apps [app_name ]
175
- return jsonify ({'status' : 'success' , 'message' : f'{ app_name } stopped successfully' })
176
- except Exception as e :
177
- return jsonify ({'status' : 'error' , 'message' : str (e )})
74
+ data = request .get_json ()
75
+ protocol = data .get ('protocol' )
76
+ device_address = data .get ('device_address' )
77
+ csv_logging = data .get ('csv_logging' , False )
78
+
79
+ # Reset stream status
80
+ stream_active = False
81
+
82
+ # Clean up any existing connection
83
+ if connection_manager :
84
+ connection_manager .cleanup ()
85
+ if connection_thread and connection_thread .is_alive ():
86
+ connection_thread .join ()
87
+
88
+ # Create new connection
89
+ connection_manager = Connection (csv_logging = csv_logging )
90
+
91
+ def run_connection ():
92
+ try :
93
+ if protocol == 'usb' :
94
+ success = connection_manager .connect_usb ()
95
+ elif protocol == 'wifi' :
96
+ success = connection_manager .connect_wifi ()
97
+ elif protocol == 'ble' :
98
+ if not device_address :
99
+ logging .error ("No BLE device address provided" )
100
+ return
101
+
102
+ # For BLE, we need to run in an event loop
103
+ loop = asyncio .new_event_loop ()
104
+ asyncio .set_event_loop (loop )
105
+ success = connection_manager .connect_ble (device_address )
106
+
107
+ if success :
108
+ post_console_message ("LSL stream started" )
109
+ else :
110
+ post_console_message ("Connection failed" )
111
+ except Exception as e :
112
+ logging .error (f"Connection error: { str (e )} " )
113
+ post_console_message (f"Connection error: { str (e )} " )
114
+
115
+ # Start connection in a separate thread
116
+ connection_thread = threading .Thread (target = run_connection , daemon = True )
117
+ connection_thread .start ()
118
+
119
+ return jsonify ({'status' : 'connecting' , 'protocol' : protocol })
178
120
179
- @app .route ('/check_app_status' , methods = ['GET' ])
180
- def check_app_status ():
181
- global running_apps
182
-
183
- app_name = request .args .get ('app_name' )
184
- if not app_name :
185
- return jsonify ({'status' : 'error' , 'message' : 'No application specified' })
186
-
187
- if app_name in running_apps :
188
- if running_apps [app_name ].poll () is None :
189
- return jsonify ({'status' : 'running' , 'message' : f'{ app_name } is running' })
190
- else :
191
- del running_apps [app_name ]
192
- return jsonify ({'status' : 'stopped' , 'message' : f'{ app_name } has stopped' })
193
- else :
194
- return jsonify ({'status' : 'stopped' , 'message' : f'{ app_name } is not running' })
121
+ @app .route ('/disconnect' , methods = ['POST' ])
122
+ def disconnect_device ():
123
+ global connection_manager
124
+ if connection_manager :
125
+ connection_manager .cleanup ()
126
+ connection_manager .stream_active = False
127
+ post_console_message ("disconnected" )
128
+ return jsonify ({'status' : 'disconnected' })
129
+ return jsonify ({'status' : 'no active connection' })
195
130
196
131
if __name__ == "__main__" :
197
132
app .run (debug = True )
0 commit comments