chordspy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chordspy-0.1.0/LICENSE +21 -0
- chordspy-0.1.0/MANIFEST.in +5 -0
- chordspy-0.1.0/PKG-INFO +143 -0
- chordspy-0.1.0/README.md +101 -0
- chordspy-0.1.0/chordspy/__init__.py +2 -0
- chordspy-0.1.0/chordspy/app.py +256 -0
- chordspy-0.1.0/chordspy/beetle.py +247 -0
- chordspy-0.1.0/chordspy/chords_ble.py +206 -0
- chordspy-0.1.0/chordspy/chords_serial.py +156 -0
- chordspy-0.1.0/chordspy/chords_wifi.py +138 -0
- chordspy-0.1.0/chordspy/config/apps.yaml +63 -0
- chordspy-0.1.0/chordspy/connection.py +596 -0
- chordspy-0.1.0/chordspy/csvplotter.py +108 -0
- chordspy-0.1.0/chordspy/emgenvelope.py +146 -0
- chordspy-0.1.0/chordspy/eog.py +194 -0
- chordspy-0.1.0/chordspy/ffteeg.py +428 -0
- chordspy-0.1.0/chordspy/game.py +346 -0
- chordspy-0.1.0/chordspy/gui.py +131 -0
- chordspy-0.1.0/chordspy/heartbeat_ecg.py +170 -0
- chordspy-0.1.0/chordspy/keystroke.py +225 -0
- chordspy-0.1.0/chordspy/static/script.js +970 -0
- chordspy-0.1.0/chordspy/templates/index.html +141 -0
- chordspy-0.1.0/chordspy.egg-info/PKG-INFO +143 -0
- chordspy-0.1.0/chordspy.egg-info/SOURCES.txt +29 -0
- chordspy-0.1.0/chordspy.egg-info/dependency_links.txt +1 -0
- chordspy-0.1.0/chordspy.egg-info/entry_points.txt +2 -0
- chordspy-0.1.0/chordspy.egg-info/requires.txt +18 -0
- chordspy-0.1.0/chordspy.egg-info/top_level.txt +1 -0
- chordspy-0.1.0/requirements.txt +18 -0
- chordspy-0.1.0/setup.cfg +4 -0
- chordspy-0.1.0/setup.py +40 -0
chordspy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Upside Down Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
chordspy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chordspy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An open source bag of tools for recording and visualizing Bio-potential signals like EEG, ECG, EMG , or EOG.
|
|
5
|
+
Home-page: https://github.com/upsidedownlabs/Chords-Python
|
|
6
|
+
Author: Payal
|
|
7
|
+
Author-email: payallakra307@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy==2.1.3
|
|
15
|
+
Requires-Dist: pylsl==1.16.2
|
|
16
|
+
Requires-Dist: pyserial==3.5
|
|
17
|
+
Requires-Dist: bleak==0.22.3
|
|
18
|
+
Requires-Dist: pyqtgraph==0.13.7
|
|
19
|
+
Requires-Dist: PyQt5==5.15.11
|
|
20
|
+
Requires-Dist: keyboard==0.13.5
|
|
21
|
+
Requires-Dist: scipy==1.14.1
|
|
22
|
+
Requires-Dist: pygame==2.6.1
|
|
23
|
+
Requires-Dist: neurokit2==0.2.10
|
|
24
|
+
Requires-Dist: plotly==5.24.1
|
|
25
|
+
Requires-Dist: pandas==2.2.3
|
|
26
|
+
Requires-Dist: tk==0.1.0
|
|
27
|
+
Requires-Dist: PyAutoGUI==0.9.54
|
|
28
|
+
Requires-Dist: Flask==3.1.1
|
|
29
|
+
Requires-Dist: psutil==6.1.1
|
|
30
|
+
Requires-Dist: websocket-client==1.8.0
|
|
31
|
+
Requires-Dist: PyYAML==6.0.2
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
Dynamic: summary
|
|
42
|
+
|
|
43
|
+
# Chords - Python
|
|
44
|
+
|
|
45
|
+
Chords- Python is a bag of tools designed to interface with Micro-controller development boards running [Chords Arduino Firmware](https://github.com/upsidedownlabs/Chords-Arduino-Firmware).Use Upside Down Labs bio-potential amplifiers to read data, visualize it, record data in CSV Files, and stream it via Lab Streaming Layer.
|
|
46
|
+
|
|
47
|
+
> [!NOTE]
|
|
48
|
+
> **Firmware Required:**
|
|
49
|
+
> - For Arduino: [Chords Arduino Firmware](https://github.com/upsidedownlabs/Chords-Arduino-Firmware)
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
- **Multiple Protocols**: Supports `Wi-Fi`, `Bluetooth`, and `Serial` communication.
|
|
53
|
+
- **LSL Data Streaming**:Once the LSL stream starts, any PC on the same Wi-Fi network can access the data using tools like BrainVision LSL Viewer.
|
|
54
|
+
- **CSV Logging**: Save raw data with Counter
|
|
55
|
+
- **GUI**: Live plotting for all channels.
|
|
56
|
+
- **Applications**: EEG/ECG/EMG/EOG-based games and utilities (e.g., Tug of War, Keystroke Emulator).
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install chordspy
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
Run the script and access the web interface:
|
|
66
|
+
```bash
|
|
67
|
+
python app.py
|
|
68
|
+
```
|
|
69
|
+
**Web Interface Preview**:
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
### Key Options:
|
|
75
|
+
|
|
76
|
+
- **LSL Streaming**: Choose a protocol (`Wi-Fi`, `Bluetooth`, `Serial`).
|
|
77
|
+
- **CSV Logging**: Data saved as `ChordsPy_{timestamp}.csv`.
|
|
78
|
+
- **Applications**: Multiple Applications can be Launch from the Interface simultaneously(e.g., `EEG Tug of War`).
|
|
79
|
+
|
|
80
|
+
## Connection Guide
|
|
81
|
+
|
|
82
|
+
#### WIFI Connection
|
|
83
|
+
1. Upload the NPG-Lite WIFI Code to your device.
|
|
84
|
+
2. Connect to the device's WIFI network.
|
|
85
|
+
3. Click the **WIFI** button in the interface, then select **CONNECT**.
|
|
86
|
+
4. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Wifi!"*
|
|
87
|
+
|
|
88
|
+
#### Bluetooth Connection
|
|
89
|
+
1. Ensure Bluetooth is turned ON on your system.
|
|
90
|
+
2. Upload the Bluetooth code to your device.
|
|
91
|
+
3. Click the **Bluetooth** button to scan for available devices.
|
|
92
|
+
4. Select your device from the list and click **Connect**.
|
|
93
|
+
5. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Bluetooth!"*
|
|
94
|
+
|
|
95
|
+
#### Serial Connection
|
|
96
|
+
1. Ensure Bluetooth is OFF and the device is connected via USB.
|
|
97
|
+
2. Upload the required code to your hardware.
|
|
98
|
+
3. Click the **Serial** button, then select **Connect**.
|
|
99
|
+
4. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Serial!"*
|
|
100
|
+
|
|
101
|
+
## CSV Logging
|
|
102
|
+
To save sensor data for future analysis, follow these steps:
|
|
103
|
+
1. **Start Data Streaming** – Begin streaming data via **WiFi, Bluetooth, or Serial**.
|
|
104
|
+
2. **Start Recording** – Click the **Start Recording** button (it will change to **Stop Recording**).
|
|
105
|
+
3. **File Saved Automatically** – The data is saved as `ChordsPy_{timestamp}.csv` in your default folder.
|
|
106
|
+
|
|
107
|
+
Visualizing CSV Data - You can plot the recorded data using the **CSV Plotter** tool.
|
|
108
|
+
|
|
109
|
+
## Applications
|
|
110
|
+
| Application | Description |
|
|
111
|
+
|----------------------------|------------------------------------------------------------------|
|
|
112
|
+
| **ECG with Heart Rate** | Real-time ECG with BPM calculation. |
|
|
113
|
+
| **EMG with Envelope** | Real-time EMG Visualization with Envelope. |
|
|
114
|
+
| **EOG with Blinks** | Real-time EOG Signal visualization with Blinks marked as Red Dot.|
|
|
115
|
+
| **EEG with FFT** | Real-time EEG Signal visualization with FFT and Brainpower bands.|
|
|
116
|
+
| **EEG Tug of War Game** | 2 Player EEG Based Game |
|
|
117
|
+
| **EEG Beetle game** | Real-time EEG focus based game. |
|
|
118
|
+
| **EOG Keystroke Emulator** | Blink detection triggers spacebar. |
|
|
119
|
+
| **GUI** | Visualize raw data in real-time |
|
|
120
|
+
| **CSV Plotter** | Tool to plot the recorded CSV Files |
|
|
121
|
+
|
|
122
|
+
## Troubleshooting
|
|
123
|
+
|
|
124
|
+
- **Arduino Not Detected:** Ensure the Arduino is properly connected and powered. Check the serial port and baud rate settings.
|
|
125
|
+
- **CSV File Not Created:** Ensure you have write permissions in the directory where the script is run.
|
|
126
|
+
- **LSL Stream Issues:** Ensure that the `pylsl` library is properly installed and configured. Additionally, confirm that Bluetooth is turned off.
|
|
127
|
+
|
|
128
|
+
## How to Contribute
|
|
129
|
+
|
|
130
|
+
You can add your project to this repo:
|
|
131
|
+
|
|
132
|
+
- Add a button in apps.yaml to link your application.
|
|
133
|
+
- Include your script as a .py file with LSL Data Reception code.
|
|
134
|
+
(Pull requests welcome!)
|
|
135
|
+
|
|
136
|
+
## Contributors
|
|
137
|
+
|
|
138
|
+
We are thankful to our awesome contributors, the list below is alphabetically sorted.
|
|
139
|
+
|
|
140
|
+
- [Aman Maheshwari](https://github.com/Amanmahe)
|
|
141
|
+
- [Payal Lakra](https://github.com/payallakra)
|
|
142
|
+
|
|
143
|
+
The audio file used in `game.py` is sourced from [Pixabay](https://pixabay.com/sound-effects/brass-fanfare-with-timpani-and-windchimes-reverberated-146260/)
|
chordspy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Chords - Python
|
|
2
|
+
|
|
3
|
+
Chords- Python is a bag of tools designed to interface with Micro-controller development boards running [Chords Arduino Firmware](https://github.com/upsidedownlabs/Chords-Arduino-Firmware).Use Upside Down Labs bio-potential amplifiers to read data, visualize it, record data in CSV Files, and stream it via Lab Streaming Layer.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> **Firmware Required:**
|
|
7
|
+
> - For Arduino: [Chords Arduino Firmware](https://github.com/upsidedownlabs/Chords-Arduino-Firmware)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
- **Multiple Protocols**: Supports `Wi-Fi`, `Bluetooth`, and `Serial` communication.
|
|
11
|
+
- **LSL Data Streaming**:Once the LSL stream starts, any PC on the same Wi-Fi network can access the data using tools like BrainVision LSL Viewer.
|
|
12
|
+
- **CSV Logging**: Save raw data with Counter
|
|
13
|
+
- **GUI**: Live plotting for all channels.
|
|
14
|
+
- **Applications**: EEG/ECG/EMG/EOG-based games and utilities (e.g., Tug of War, Keystroke Emulator).
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install chordspy
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
Run the script and access the web interface:
|
|
24
|
+
```bash
|
|
25
|
+
python app.py
|
|
26
|
+
```
|
|
27
|
+
**Web Interface Preview**:
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
### Key Options:
|
|
33
|
+
|
|
34
|
+
- **LSL Streaming**: Choose a protocol (`Wi-Fi`, `Bluetooth`, `Serial`).
|
|
35
|
+
- **CSV Logging**: Data saved as `ChordsPy_{timestamp}.csv`.
|
|
36
|
+
- **Applications**: Multiple Applications can be Launch from the Interface simultaneously(e.g., `EEG Tug of War`).
|
|
37
|
+
|
|
38
|
+
## Connection Guide
|
|
39
|
+
|
|
40
|
+
#### WIFI Connection
|
|
41
|
+
1. Upload the NPG-Lite WIFI Code to your device.
|
|
42
|
+
2. Connect to the device's WIFI network.
|
|
43
|
+
3. Click the **WIFI** button in the interface, then select **CONNECT**.
|
|
44
|
+
4. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Wifi!"*
|
|
45
|
+
|
|
46
|
+
#### Bluetooth Connection
|
|
47
|
+
1. Ensure Bluetooth is turned ON on your system.
|
|
48
|
+
2. Upload the Bluetooth code to your device.
|
|
49
|
+
3. Click the **Bluetooth** button to scan for available devices.
|
|
50
|
+
4. Select your device from the list and click **Connect**.
|
|
51
|
+
5. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Bluetooth!"*
|
|
52
|
+
|
|
53
|
+
#### Serial Connection
|
|
54
|
+
1. Ensure Bluetooth is OFF and the device is connected via USB.
|
|
55
|
+
2. Upload the required code to your hardware.
|
|
56
|
+
3. Click the **Serial** button, then select **Connect**.
|
|
57
|
+
4. Once connected, the button will change to **Disconnect**, and a pop-up will confirm: *"Connected via Serial!"*
|
|
58
|
+
|
|
59
|
+
## CSV Logging
|
|
60
|
+
To save sensor data for future analysis, follow these steps:
|
|
61
|
+
1. **Start Data Streaming** – Begin streaming data via **WiFi, Bluetooth, or Serial**.
|
|
62
|
+
2. **Start Recording** – Click the **Start Recording** button (it will change to **Stop Recording**).
|
|
63
|
+
3. **File Saved Automatically** – The data is saved as `ChordsPy_{timestamp}.csv` in your default folder.
|
|
64
|
+
|
|
65
|
+
Visualizing CSV Data - You can plot the recorded data using the **CSV Plotter** tool.
|
|
66
|
+
|
|
67
|
+
## Applications
|
|
68
|
+
| Application | Description |
|
|
69
|
+
|----------------------------|------------------------------------------------------------------|
|
|
70
|
+
| **ECG with Heart Rate** | Real-time ECG with BPM calculation. |
|
|
71
|
+
| **EMG with Envelope** | Real-time EMG Visualization with Envelope. |
|
|
72
|
+
| **EOG with Blinks** | Real-time EOG Signal visualization with Blinks marked as Red Dot.|
|
|
73
|
+
| **EEG with FFT** | Real-time EEG Signal visualization with FFT and Brainpower bands.|
|
|
74
|
+
| **EEG Tug of War Game** | 2 Player EEG Based Game |
|
|
75
|
+
| **EEG Beetle game** | Real-time EEG focus based game. |
|
|
76
|
+
| **EOG Keystroke Emulator** | Blink detection triggers spacebar. |
|
|
77
|
+
| **GUI** | Visualize raw data in real-time |
|
|
78
|
+
| **CSV Plotter** | Tool to plot the recorded CSV Files |
|
|
79
|
+
|
|
80
|
+
## Troubleshooting
|
|
81
|
+
|
|
82
|
+
- **Arduino Not Detected:** Ensure the Arduino is properly connected and powered. Check the serial port and baud rate settings.
|
|
83
|
+
- **CSV File Not Created:** Ensure you have write permissions in the directory where the script is run.
|
|
84
|
+
- **LSL Stream Issues:** Ensure that the `pylsl` library is properly installed and configured. Additionally, confirm that Bluetooth is turned off.
|
|
85
|
+
|
|
86
|
+
## How to Contribute
|
|
87
|
+
|
|
88
|
+
You can add your project to this repo:
|
|
89
|
+
|
|
90
|
+
- Add a button in apps.yaml to link your application.
|
|
91
|
+
- Include your script as a .py file with LSL Data Reception code.
|
|
92
|
+
(Pull requests welcome!)
|
|
93
|
+
|
|
94
|
+
## Contributors
|
|
95
|
+
|
|
96
|
+
We are thankful to our awesome contributors, the list below is alphabetically sorted.
|
|
97
|
+
|
|
98
|
+
- [Aman Maheshwari](https://github.com/Amanmahe)
|
|
99
|
+
- [Payal Lakra](https://github.com/payallakra)
|
|
100
|
+
|
|
101
|
+
The audio file used in `game.py` is sourced from [Pixabay](https://pixabay.com/sound-effects/brass-fanfare-with-timpani-and-windchimes-reverberated-146260/)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from flask import Flask, render_template, request, jsonify
|
|
2
|
+
from chordspy.connection import Connection
|
|
3
|
+
import threading
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
from bleak import BleakScanner
|
|
7
|
+
from flask import Response
|
|
8
|
+
import queue
|
|
9
|
+
import yaml
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import os
|
|
12
|
+
import webbrowser
|
|
13
|
+
|
|
14
|
+
console_queue = queue.Queue()
|
|
15
|
+
app = Flask(__name__)
|
|
16
|
+
logging.basicConfig(level=logging.INFO)
|
|
17
|
+
|
|
18
|
+
# Global variables
|
|
19
|
+
connection_manager = None
|
|
20
|
+
connection_thread = None
|
|
21
|
+
ble_devices = []
|
|
22
|
+
stream_active = False
|
|
23
|
+
running_apps = {} # Dictionary to track running apps
|
|
24
|
+
|
|
25
|
+
@app.route('/log_error', methods=['POST'])
|
|
26
|
+
def log_error():
|
|
27
|
+
try:
|
|
28
|
+
error_data = request.get_json()
|
|
29
|
+
if not error_data or 'error' not in error_data or 'log_error' in str(error_data):
|
|
30
|
+
return jsonify({'status': 'error', 'message': 'Invalid data'}), 400
|
|
31
|
+
|
|
32
|
+
os.makedirs('logs', exist_ok=True)
|
|
33
|
+
|
|
34
|
+
with open('logs/logging.txt', 'a') as f:
|
|
35
|
+
f.write(error_data['error'])
|
|
36
|
+
|
|
37
|
+
return jsonify({'status': 'success'})
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return jsonify({'status': 'error', 'message': 'Logging failed'}), 500
|
|
40
|
+
|
|
41
|
+
def run_async(coro):
|
|
42
|
+
def wrapper(*args, **kwargs):
|
|
43
|
+
loop = asyncio.new_event_loop()
|
|
44
|
+
asyncio.set_event_loop(loop)
|
|
45
|
+
try:
|
|
46
|
+
return loop.run_until_complete(coro(*args, **kwargs))
|
|
47
|
+
finally:
|
|
48
|
+
loop.close()
|
|
49
|
+
return wrapper
|
|
50
|
+
|
|
51
|
+
@app.route('/')
|
|
52
|
+
def index():
|
|
53
|
+
return render_template('index.html')
|
|
54
|
+
|
|
55
|
+
@app.route('/get_apps_config')
|
|
56
|
+
def get_apps_config():
|
|
57
|
+
try:
|
|
58
|
+
config_path = Path(__file__).parent / 'config' / 'apps.yaml' # Try package-relative path first
|
|
59
|
+
if not config_path.exists():
|
|
60
|
+
config_path = Path('chordspy.config') / 'apps.yaml' # Fallback to local path
|
|
61
|
+
|
|
62
|
+
if config_path.exists():
|
|
63
|
+
with open(config_path, 'r') as file:
|
|
64
|
+
config = yaml.safe_load(file)
|
|
65
|
+
return jsonify(config)
|
|
66
|
+
return jsonify({'apps': []})
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logging.error(f"Error loading apps config: {str(e)}")
|
|
70
|
+
return jsonify({'apps': [], 'error': str(e)})
|
|
71
|
+
|
|
72
|
+
@app.route('/scan_ble')
|
|
73
|
+
@run_async
|
|
74
|
+
async def scan_ble_devices():
|
|
75
|
+
global ble_devices
|
|
76
|
+
try:
|
|
77
|
+
devices = await BleakScanner.discover(timeout=5)
|
|
78
|
+
ble_devices = [{'name': d.name or 'Unknown', 'address': d.address}
|
|
79
|
+
for d in devices if d.name and d.name.startswith(('NPG', 'npg'))]
|
|
80
|
+
return jsonify({'status': 'success', 'devices': ble_devices})
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logging.error(f"BLE scan error: {str(e)}")
|
|
83
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
84
|
+
|
|
85
|
+
@app.route('/check_stream')
|
|
86
|
+
def check_stream():
|
|
87
|
+
is_connected = connection_manager.stream_active if hasattr(connection_manager, 'stream_active') else False
|
|
88
|
+
return jsonify({'connected': is_connected})
|
|
89
|
+
|
|
90
|
+
@app.route('/check_connection')
|
|
91
|
+
def check_connection():
|
|
92
|
+
if connection_manager and connection_manager.stream_active:
|
|
93
|
+
return jsonify({'status': 'connected'})
|
|
94
|
+
return jsonify({'status': 'connecting'})
|
|
95
|
+
|
|
96
|
+
def post_console_message(message):
|
|
97
|
+
global stream_active
|
|
98
|
+
if "LSL stream started" in message:
|
|
99
|
+
stream_active = True
|
|
100
|
+
elif "disconnected" in message:
|
|
101
|
+
stream_active = False
|
|
102
|
+
console_queue.put(message)
|
|
103
|
+
|
|
104
|
+
@app.route('/console_updates')
|
|
105
|
+
def console_updates():
|
|
106
|
+
def event_stream():
|
|
107
|
+
while True:
|
|
108
|
+
message = console_queue.get()
|
|
109
|
+
yield f"data: {message}\n\n"
|
|
110
|
+
|
|
111
|
+
return Response(event_stream(), mimetype="text/event-stream")
|
|
112
|
+
|
|
113
|
+
@app.route('/launch_app', methods=['POST'])
|
|
114
|
+
def launch_application():
|
|
115
|
+
if not connection_manager or not connection_manager.stream_active:
|
|
116
|
+
return jsonify({'status': 'error', 'message': 'No active stream'}), 400
|
|
117
|
+
|
|
118
|
+
data = request.get_json()
|
|
119
|
+
module_name = data.get('app')
|
|
120
|
+
|
|
121
|
+
if not module_name:
|
|
122
|
+
return jsonify({'status': 'error', 'message': 'No application specified'}), 400
|
|
123
|
+
|
|
124
|
+
# Check if app is already running
|
|
125
|
+
if module_name in running_apps and running_apps[module_name].poll() is None:
|
|
126
|
+
return jsonify({'status': 'error', 'message': f'{module_name} is already running','code': 'ALREADY_RUNNING'}), 400
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
import subprocess
|
|
130
|
+
import sys
|
|
131
|
+
|
|
132
|
+
# Run the module using Python's -m flag
|
|
133
|
+
process = subprocess.Popen([sys.executable, "-m", f"chordspy.{module_name}"])
|
|
134
|
+
|
|
135
|
+
running_apps[module_name] = process
|
|
136
|
+
|
|
137
|
+
return jsonify({'status': 'success', 'message': f'Launched {module_name}'})
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logging.error(f"Error launching {module_name}: {str(e)}")
|
|
140
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
141
|
+
|
|
142
|
+
@app.route('/check_app_status/<app_name>')
|
|
143
|
+
def check_app_status(app_name):
|
|
144
|
+
if app_name in running_apps:
|
|
145
|
+
if running_apps[app_name].poll() is None: # Still running
|
|
146
|
+
return jsonify({'status': 'running'})
|
|
147
|
+
else: # Process has terminated
|
|
148
|
+
del running_apps[app_name]
|
|
149
|
+
return jsonify({'status': 'not_running'})
|
|
150
|
+
return jsonify({'status': 'not_running'})
|
|
151
|
+
|
|
152
|
+
@app.route('/connect', methods=['POST'])
|
|
153
|
+
def connect_device():
|
|
154
|
+
global connection_manager, connection_thread, stream_active
|
|
155
|
+
|
|
156
|
+
data = request.get_json()
|
|
157
|
+
protocol = data.get('protocol')
|
|
158
|
+
device_address = data.get('device_address')
|
|
159
|
+
|
|
160
|
+
# Reset stream status
|
|
161
|
+
stream_active = False
|
|
162
|
+
|
|
163
|
+
# Clean up any existing connection
|
|
164
|
+
if connection_manager:
|
|
165
|
+
connection_manager.cleanup()
|
|
166
|
+
if connection_thread and connection_thread.is_alive():
|
|
167
|
+
connection_thread.join()
|
|
168
|
+
|
|
169
|
+
# Create new connection
|
|
170
|
+
connection_manager = Connection()
|
|
171
|
+
|
|
172
|
+
def run_connection():
|
|
173
|
+
try:
|
|
174
|
+
if protocol == 'usb':
|
|
175
|
+
success = connection_manager.connect_usb()
|
|
176
|
+
elif protocol == 'wifi':
|
|
177
|
+
success = connection_manager.connect_wifi()
|
|
178
|
+
elif protocol == 'ble':
|
|
179
|
+
if not device_address:
|
|
180
|
+
logging.error("No BLE device address provided")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# For BLE, we need to run in an event loop
|
|
184
|
+
loop = asyncio.new_event_loop()
|
|
185
|
+
asyncio.set_event_loop(loop)
|
|
186
|
+
success = connection_manager.connect_ble(device_address)
|
|
187
|
+
|
|
188
|
+
if success:
|
|
189
|
+
post_console_message("LSL stream started")
|
|
190
|
+
else:
|
|
191
|
+
post_console_message("Connection failed")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logging.error(f"Connection error: {str(e)}")
|
|
194
|
+
post_console_message(f"Connection error: {str(e)}")
|
|
195
|
+
|
|
196
|
+
# Start connection in a separate thread
|
|
197
|
+
connection_thread = threading.Thread(target=run_connection, daemon=True)
|
|
198
|
+
connection_thread.start()
|
|
199
|
+
|
|
200
|
+
return jsonify({'status': 'connecting', 'protocol': protocol})
|
|
201
|
+
|
|
202
|
+
@app.route('/disconnect', methods=['POST'])
|
|
203
|
+
def disconnect_device():
|
|
204
|
+
global connection_manager, stream_active
|
|
205
|
+
if connection_manager:
|
|
206
|
+
connection_manager.cleanup()
|
|
207
|
+
stream_active = False
|
|
208
|
+
post_console_message("disconnected")
|
|
209
|
+
return jsonify({'status': 'disconnected'})
|
|
210
|
+
return jsonify({'status': 'no active connection'})
|
|
211
|
+
|
|
212
|
+
@app.route('/start_recording', methods=['POST'])
|
|
213
|
+
def start_recording():
|
|
214
|
+
global connection_manager
|
|
215
|
+
if not connection_manager:
|
|
216
|
+
return jsonify({'status': 'error', 'message': 'No active connection'}), 400
|
|
217
|
+
|
|
218
|
+
data = request.get_json()
|
|
219
|
+
filename = data.get('filename')
|
|
220
|
+
|
|
221
|
+
# If filename is empty or None, let connection_manager use default
|
|
222
|
+
if filename == "":
|
|
223
|
+
filename = None
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
if connection_manager.start_csv_recording(filename):
|
|
227
|
+
post_console_message(f"Recording started: {filename or 'default filename'}")
|
|
228
|
+
return jsonify({'status': 'recording_started'})
|
|
229
|
+
return jsonify({'status': 'error', 'message': 'Failed to start recording'}), 500
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logging.error(f"Recording error: {str(e)}")
|
|
232
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
233
|
+
|
|
234
|
+
@app.route('/stop_recording', methods=['POST'])
|
|
235
|
+
def stop_recording():
|
|
236
|
+
global connection_manager
|
|
237
|
+
if connection_manager:
|
|
238
|
+
try:
|
|
239
|
+
if connection_manager.stop_csv_recording():
|
|
240
|
+
post_console_message("Recording stopped")
|
|
241
|
+
return jsonify({'status': 'recording_stopped'})
|
|
242
|
+
return jsonify({'status': 'error', 'message': 'Failed to stop recording'}), 500
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logging.error(f"Stop recording error: {str(e)}")
|
|
245
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
246
|
+
return jsonify({'status': 'error', 'message': 'No active connection'}), 400
|
|
247
|
+
|
|
248
|
+
def main():
|
|
249
|
+
def open_browser():
|
|
250
|
+
webbrowser.open("http://localhost:5000")
|
|
251
|
+
|
|
252
|
+
threading.Timer(1.5, open_browser).start()
|
|
253
|
+
app.run(debug=True, use_reloader=False, host='0.0.0.0', port=5000)
|
|
254
|
+
|
|
255
|
+
if __name__ == "__main__":
|
|
256
|
+
main()
|