Skip to content

Commit 1f1f2a7

Browse files
committed
Onle 1 npg ble device can be detected and connected
1 parent b3a113b commit 1f1f2a7

File tree

2 files changed

+134
-7
lines changed

2 files changed

+134
-7
lines changed

app.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import atexit
77
import time
8+
import os
89

910
app = Flask(__name__)
1011
lsl_process = None
@@ -73,22 +74,42 @@ def start_npg():
7374
return redirect(url_for('home'))
7475

7576
try:
77+
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "direct.py")
7678
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
77-
npg_process = subprocess.Popen(["python", "npg.py"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=creation_flags, text=True, bufsize=1)
79+
80+
npg_process = subprocess.Popen([sys.executable, script_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=creation_flags, text=True, bufsize=1, cwd=os.path.dirname(os.path.abspath(__file__)))
7881

79-
time.sleep(2)
80-
for line in iter(npg_process.stdout.readline, ''):
81-
if "NPG WebSocket connected!" in line.strip():
82+
start_time = time.time()
83+
connected = False
84+
while time.time() - start_time < 10: # 10 second timeout
85+
line = npg_process.stdout.readline()
86+
if not line:
87+
break
88+
if "Connected to NPG-30:30:f9:f9:db:76" in line.strip():
8289
current_message = "NPG stream started successfully"
8390
npg_running = True
91+
connected = True
8492
break
85-
else:
86-
current_message = "Failed to connect NPG stream"
93+
94+
if not connected:
95+
current_message = "Failed to connect NPG stream (timeout)"
96+
npg_process.terminate()
8797
npg_running = False
88-
98+
return redirect(url_for('home'))
99+
100+
def consume_output():
101+
while npg_process.poll() is None: # While process is running
102+
npg_process.stdout.readline() # Keep reading to prevent buffer fill
103+
104+
import threading
105+
output_thread = threading.Thread(target=consume_output, daemon=True)
106+
output_thread.start()
107+
89108
except Exception as e:
90109
current_message = f"Error starting NPG: {str(e)}"
91110
npg_running = False
111+
if 'npg_process' in globals() and npg_process.poll() is None:
112+
npg_process.terminate()
92113

93114
return redirect(url_for('home'))
94115

one.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import asyncio
2+
from bleak import BleakScanner, BleakClient
3+
import time
4+
from pylsl import StreamInfo, StreamOutlet
5+
6+
# BLE parameters (must match your firmware)
7+
DEVICE_NAME = "NPG-30:30:f9:f9:db:76"
8+
SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
9+
DATA_CHAR_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8"
10+
CONTROL_CHAR_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
11+
12+
# Packet parameters for batched samples:
13+
SINGLE_SAMPLE_LEN = 7 # Each sample is 7 bytes
14+
BLOCK_COUNT = 10 # Batch size: 10 samples per notification
15+
NEW_PACKET_LEN = SINGLE_SAMPLE_LEN * BLOCK_COUNT # Total packet length (70 bytes)
16+
17+
# Set up an LSL stream with int16 data format (irregular rate)
18+
stream_name = "NPG"
19+
info = StreamInfo(stream_name, "EXG", 3, 500, "int16", "uid007")
20+
outlet = StreamOutlet(info)
21+
22+
# Global variables for unrolled counter, sample counting, and timing
23+
prev_unrolled_counter = None # Unrolled (cumulative) counter from firmware
24+
samples_received = 0 # Total samples received in the last second
25+
start_time = None # Time when first sample is received
26+
total_missing_samples = 0
27+
28+
def process_sample(sample_data: bytearray):
29+
global prev_unrolled_counter, samples_received, start_time, total_missing_samples
30+
if len(sample_data) != SINGLE_SAMPLE_LEN:
31+
print("Unexpected sample length:", len(sample_data))
32+
return
33+
sample_counter = sample_data[0]
34+
# Unroll the counter:
35+
if prev_unrolled_counter is None:
36+
prev_unrolled_counter = sample_counter
37+
else:
38+
last = prev_unrolled_counter % 256
39+
if sample_counter < last:
40+
current_unrolled = prev_unrolled_counter - last + sample_counter + 256
41+
else:
42+
current_unrolled = prev_unrolled_counter - last + sample_counter
43+
if current_unrolled != prev_unrolled_counter + 1:
44+
print(f"Missing sample: expected {prev_unrolled_counter + 1}, got {current_unrolled}")
45+
total_missing_samples += current_unrolled - (prev_unrolled_counter + 1)
46+
prev_unrolled_counter = current_unrolled
47+
48+
# Set start_time when first sample is received
49+
if start_time is None:
50+
start_time = time.time()
51+
elapsed = time.time() - start_time
52+
53+
channels = []
54+
for ch in range(3):
55+
offset = 1 + ch * 2
56+
value = int.from_bytes(sample_data[offset:offset+2], byteorder='big', signed=True)
57+
channels.append(value)
58+
print(f"Sample {prev_unrolled_counter} at {elapsed:.2f} s: Channels: {channels} Total missing samples: {total_missing_samples}")
59+
outlet.push_sample(channels)
60+
samples_received += 1
61+
62+
def notification_handler(sender, data: bytearray):
63+
if len(data) == NEW_PACKET_LEN:
64+
for i in range(0, NEW_PACKET_LEN, SINGLE_SAMPLE_LEN):
65+
sample = data[i:i+SINGLE_SAMPLE_LEN]
66+
process_sample(sample)
67+
elif len(data) == SINGLE_SAMPLE_LEN:
68+
process_sample(data)
69+
else:
70+
print("Unexpected packet length:", len(data))
71+
72+
async def print_rate():
73+
global samples_received
74+
while True:
75+
await asyncio.sleep(1)
76+
print(f"Samples per second: {samples_received}")
77+
samples_received = 0
78+
79+
async def run():
80+
print("Scanning for BLE devices with name starting with", DEVICE_NAME)
81+
devices = await BleakScanner.discover()
82+
target = None
83+
for d in devices:
84+
if d.name and DEVICE_NAME.lower() in d.name.lower():
85+
target = d
86+
break
87+
if target is None:
88+
print("No target device found")
89+
return
90+
91+
print("Connecting to:", target.name, target.address)
92+
async with BleakClient(target) as client:
93+
if not client.is_connected:
94+
print("Failed to connect")
95+
return
96+
print("Connected to", target.name)
97+
await client.write_gatt_char(CONTROL_CHAR_UUID, b"START", response=True)
98+
print("Sent START command")
99+
await client.start_notify(DATA_CHAR_UUID, notification_handler)
100+
print("Subscribed to data notifications")
101+
asyncio.create_task(print_rate())
102+
while True:
103+
await asyncio.sleep(1)
104+
105+
if __name__ == "__main__":
106+
asyncio.run(run())

0 commit comments

Comments
 (0)