Open adamwolf opened 4 years ago
Not me, but I'm curious to know if it works.
I have some experience reversing BLE stuff, and a lot of experience with BLE as a developer, so I'm pretty excited to see what's what. I just ordered one, but they're not shipping for a while...
On Sat, Feb 15, 2020 at 9:35 PM alexandre barachant < notifications@github.com> wrote:
Not me, but I'm curious to know if it works.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/alexandrebarachant/muse-lsl/issues/120?email_source=notifications&email_token=AAAIWYK6QKDOPGGFGINOEYLRDCYBJA5CNFSM4KV6VR3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL34YNA#issuecomment-586665012, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAIWYMRH7GH3LFN264ZCODRDCYBJANCNFSM4KV6VR3A .
Got one yesterday, working exactly as the Muse 2 from a bluepy perspective.
@RemyRamdam Fantastique, pioneer! Thanks for the information. Would you check how many EEG channels in Muse-S? Same four like the other bands?
Hello @iPsych,
As far as I can tell, they are the same
@Remy17 @RemyRamdam - just to confirm - were you able to steam all channels (accelerometer, gryo, PPG, EEG, telemetry, etc) without any modification of muse-lsl?
Sorry, I didn't notice that I had two accounts :D Actually, I use my own code, 99% inspired by MuseLSL but retailored to serve my purposes. This should totally work the same with Muse LSL.
What I obtained so far is this output for the LSL streams attached to the BLE data :
('Number of streams = ', 5)
<?xml version="1.0"?>
<info>
<name>anonymous</name>
<type>RAW_PPG</type>
<channel_count>3</channel_count>
<nominal_srate>64</nominal_srate>
<channel_format>float32</channel_format>
<source_id>00:55:DA:B9:09:99_PPG</source_id>
<version>1.1000000000000001</version>
<created_at>186968.33128078</created_at>
<uid>13fdd4ea-7337-45f5-9044-d6ccf96817e2</uid>
<session_id>default</session_id>
<hostname>laptop</hostname>
<v4address />
<v4data_port>16574</v4data_port>
<v4service_port>16574</v4service_port>
<v6address />
<v6data_port>16575</v6data_port>
<v6service_port>16575</v6service_port>
<desc>
<manufacturer>Muse</manufacturer>
<channels>
<channel>
<label>AMBIANT</label>
<unit>mmHg</unit>
<type>RAW_PPG</type>
</channel>
<channel>
<label>INFRARED</label>
<unit>mmHg</unit>
<type>RAW_PPG</type>
</channel>
<channel>
<label>RED</label>
<unit>mmHg</unit>
<type>RAW_PPG</type>
</channel>
</channels>
</desc>
</info>
<?xml version="1.0"?>
<info>
<name>anonymous</name>
<type>RAW_GYRO</type>
<channel_count>3</channel_count>
<nominal_srate>52</nominal_srate>
<channel_format>float32</channel_format>
<source_id>00:55:DA:B9:09:99_GYRO</source_id>
<version>1.1000000000000001</version>
<created_at>186968.53343392801</created_at>
<uid>432b0469-9c94-413b-9466-5ee4eafece50</uid>
<session_id>default</session_id>
<hostname>laptop</hostname>
<v4address />
<v4data_port>16576</v4data_port>
<v4service_port>16576</v4service_port>
<v6address />
<v6data_port>16577</v6data_port>
<v6service_port>16577</v6service_port>
<desc>
<manufacturer>Muse</manufacturer>
<channels>
<channel>
<label>X</label>
<unit>dps</unit>
<type>RAW_GYRO</type>
</channel>
<channel>
<label>Y</label>
<unit>dps</unit>
<type>RAW_GYRO</type>
</channel>
<channel>
<label>Z</label>
<unit>dps</unit>
<type>RAW_GYRO</type>
</channel>
</channels>
</desc>
</info>
<?xml version="1.0"?>
<info>
<name>anonymous</name>
<type>RAW_ACC</type>
<channel_count>3</channel_count>
<nominal_srate>52</nominal_srate>
<channel_format>float32</channel_format>
<source_id>00:55:DA:B9:09:99_ACCELERO</source_id>
<version>1.1000000000000001</version>
<created_at>186968.73676574699</created_at>
<uid>445e48c2-4547-4ec8-9c51-62b6d9b4a0bd</uid>
<session_id>default</session_id>
<hostname>laptop</hostname>
<v4address />
<v4data_port>16578</v4data_port>
<v4service_port>16578</v4service_port>
<v6address />
<v6data_port>16579</v6data_port>
<v6service_port>16579</v6service_port>
<desc>
<manufacturer>Muse</manufacturer>
<channels>
<channel>
<label>X</label>
<unit>g</unit>
<type>RAW_ACC</type>
</channel>
<channel>
<label>Y</label>
<unit>g</unit>
<type>RAW_ACC</type>
</channel>
<channel>
<label>Z</label>
<unit>g</unit>
<type>RAW_ACC</type>
</channel>
</channels>
</desc>
</info>
<?xml version="1.0"?>
<info>
<name>anonymous</name>
<type>RAW_EEG</type>
<channel_count>5</channel_count>
<nominal_srate>256</nominal_srate>
<channel_format>float32</channel_format>
<source_id>00:55:DA:B9:09:99_EEG</source_id>
<version>1.1000000000000001</version>
<created_at>186967.723528006</created_at>
<uid>5bd0b4ee-e634-4eb8-b4d9-adb794f7722a</uid>
<session_id>default</session_id>
<hostname>laptop</hostname>
<v4address />
<v4data_port>16572</v4data_port>
<v4service_port>16572</v4service_port>
<v6address />
<v6data_port>16573</v6data_port>
<v6service_port>16573</v6service_port>
<desc>
<manufacturer>Muse</manufacturer>
<channels>
<channel>
<label>TP9</label>
<unit>microvolts</unit>
<type>RAW_EEG</type>
</channel>
<channel>
<label>AF7</label>
<unit>microvolts</unit>
<type>RAW_EEG</type>
</channel>
<channel>
<label>AF8</label>
<unit>microvolts</unit>
<type>RAW_EEG</type>
</channel>
<channel>
<label>TP10</label>
<unit>microvolts</unit>
<type>RAW_EEG</type>
</channel>
</channels>
</desc>
</info>
<?xml version="1.0"?>
<info>
<name>anonymous</name>
<type>RAW_TELEMETRY</type>
<channel_count>4</channel_count>
<nominal_srate>0</nominal_srate>
<channel_format>float32</channel_format>
<source_id>00:55:DA:B9:09:99_TELEMETRY</source_id>
<version>1.1000000000000001</version>
<created_at>186968.94259854301</created_at>
<uid>74bdbd18-f08b-46f7-9fb0-bf39920cb49f</uid>
<session_id>default</session_id>
<hostname>laptop</hostname>
<v4address />
<v4data_port>16580</v4data_port>
<v4service_port>16580</v4service_port>
<v6address />
<v6data_port>16581</v6data_port>
<v6service_port>16581</v6service_port>
<desc>
<manufacturer>Muse</manufacturer>
<channels>
<channel>
<label>battery</label>
<unit>percentage</unit>
<type>RAW_TELEMETRY</type>
</channel>
<channel>
<label>fuel_gauge</label>
<unit>??</unit>
<type>RAW_TELEMETRY</type>
</channel>
<channel>
<label>adv_volt</label>
<unit>volt?</unit>
<type>RAW_TELEMETRY</type>
</channel>
<channel>
<label>temperature</label>
<unit>farenheit ?</unit>
<type>RAW_TELEMETRY</type>
</channel>
</channels>
</desc>
</info>
Also, I attached those files for you to reproduce the same things :
a video of the different channel streams (actually a gif)
a screenshot annoted to understand what you are looking in the video
The code I used :
# Main.py
from bluepy.btle import Peripheral, ADDR_TYPE_PUBLIC, AssignedNumbers, BTLEException
from pylsl import StreamInfo, StreamOutlet
import numpy as np
import time, struct, argparse, sys, datetime
import bitstring
from bitstring import BitArray
from constants import *
parser = argparse.ArgumentParser(description = 'Stream heart rate of bluetooth BLE compatible devices using LSL.')
parser.add_argument("-m", "--mac-address", help = "MAC address of the device.", default = "00:00:00:00:00:00", type = str)
parser.add_argument("-id", "--id", help = "id on the network, will be added to Muse_", default = "anonymous", type = str)
parser.add_argument("-e", "--eeg", default = False, action = 'store_true', help = "stream eeg data")
parser.add_argument("-p", "--ppg", default = False, action = 'store_true', help = "stream ppg data")
parser.add_argument("-g", "--gyro", default = False, action = 'store_true', help = "stream gyro data")
parser.add_argument("-a", "--accelero", default = False, action = 'store_true', help = "stream accelero data")
parser.add_argument("-t", "--telemetry", default = False, action = 'store_true', help = "stream telemetry data")
parser.add_argument("-v", "--verbose", action = 'store_true', help = "Print verbose information.")
parser.add_argument("-vv", "--ultraverbose", action = 'store_true', help = "Print more verbose information.")
parser.set_defaults(verbose=False)
parser.set_defaults(ultraverbose=False)
args = parser.parse_args()
eeg_info = []
eeg_outlet = []
ppg_info = []
ppg_outlet = []
gyro_info = []
gyro_outlet = []
accelero_info = []
accelero_outlet = []
telemetry_info = []
telemetry_outlet = []
data_eeg = np.zeros((MUSE_NB_EEG_CHANNELS, LSL_EEG_CHUNK))
data_ppg = np.zeros((MUSE_NB_PPG_CHANNELS, LSL_PPG_CHUNK))
data_gyro = np.zeros((MUSE_NB_GYRO_CHANNELS, LSL_GYRO_CHUNK))
data_accelero = np.zeros((MUSE_NB_ACC_CHANNELS, LSL_ACC_CHUNK))
data_telemetry = np.zeros((MUSE_NB_TELEMETRY_CHANNELS, LSL_TELEMETRY_CHUNK))
class Muse(Peripheral) :
def __init__(self, addr) :
if args.verbose :
print("connecting to device", addr)
Peripheral.__init__(self, addr, addrType = ADDR_TYPE_PUBLIC)
if args.verbose :
print("Connected to Muse", addr)
def print_data(cHandle, handle, data) :
aa = bitstring.Bits(bytes=data)
if handle == 0x001b-1 :
pattern = "uint:16,uint:16,uint:16,uint:16,uint:16"
elif handle == 0x0015-1 or handle == 0x0018-1 :
pattern = "uint:16,int:16,int:16,int:16,int:16,int:16,int:16,int:16,int:16,int:16"
elif handle == 0x0039-1 or handle == 0x003c-1 or handle == 0x003f-1 :
pattern = "uint:16,uint:24,uint:24,uint:24,uint:24,uint:24,uint:24"
else :
pattern = "uint:16,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12,uint:12"
res = aa.unpack(pattern)
data = res[1:]
if args.ultraverbose :
print("Handle = ", handle)
print("Data = ", data)
# EEG
if (handle == 0x0021-1) : # EEG TP9 #03
data = MUSE_EEG_SCALE_FACTOR * (np.array(data) - 2048)
data_eeg[2] = data
if (handle == 0x0024-1) : # EEG AF7 #04
data = MUSE_EEG_SCALE_FACTOR * (np.array(data) - 2048)
data_eeg[3] = data
timestamp = time.time()
timestamps = np.arange(timestamp - 1.0*LSL_EEG_CHUNK/MUSE_SAMPLING_EEG_RATE, timestamp, 1./MUSE_SAMPLING_EEG_RATE)
for ii in range(LSL_EEG_CHUNK) :
eeg_outlet.push_sample(data_eeg[:, ii], timestamps[ii])
if (handle == 0x0027-1) : # EEG AF8 #02
data = MUSE_EEG_SCALE_FACTOR * (np.array(data) - 2048)
data_eeg[1] = data
if (handle == 0x002a-1) : #EEG TP10 #01
data = MUSE_EEG_SCALE_FACTOR * (np.array(data) - 2048)
data_eeg[0] = data
# PPG
if (handle == 0x0039-1) : # PPG 1
data_ppg[0] = data
if (handle == 0x003c-1) : # PPG 2
data_ppg[1] = data
if (handle == 0x003f-1) : # PPG 3
data_ppg[2] = data
timestamp = time.time()
timestamps = np.arange(timestamp - 1.0*LSL_PPG_CHUNK/MUSE_SAMPLING_PPG_RATE, timestamp, 1./MUSE_SAMPLING_PPG_RATE)
for ii in range(LSL_PPG_CHUNK) :
ppg_outlet.push_sample(data_ppg[:, ii], timestamps[ii])
# GYRO
if (handle == 0x0015-1) : # GYRO
data_gyro = MUSE_GYRO_SCALE_FACTOR * np.array(data).reshape((3, 3), order='F')
timestamp = time.time()
timestamps = np.arange(timestamp - 1.0*LSL_GYRO_CHUNK/MUSE_SAMPLING_GYRO_RATE, timestamp, 1./MUSE_SAMPLING_GYRO_RATE)
for ii in range(LSL_GYRO_CHUNK) :
gyro_outlet.push_sample(data_gyro[:, ii], timestamps[ii])
# ACCELERO
if (handle == 0x0018-1) : # ACCELERO
data_accelero = MUSE_ACCELERO_SCALE_FACTOR * np.array(data).reshape((3, 3), order='F')
timestamp = time.time()
timestamps = np.arange(timestamp - 1.0*LSL_ACC_CHUNK/MUSE_SAMPLING_ACC_RATE, timestamp, 1./MUSE_SAMPLING_ACC_RATE)
for ii in range(LSL_ACC_CHUNK) :
accelero_outlet.push_sample(data_accelero[:, ii], timestamps[ii])
# TELEMETRY
if (handle == 0x001b-1) : # TELEMETRY >> battery, fuel_gauge, adc_volt, temperature
data_telemetry = [data[0]/512., data[1]*2.2, data[2], data[3]]
timestamp = time.time()
telemetry_outlet.push_sample(data_telemetry, timestamp)
print("Battery is ", data_telemetry[0])
if __name__=="__main__":
if args.ultraverbose :
args.verbose = True
muse = []
if (not args.eeg and not args.ppg and not args.gyro and not args.accelero and not args.telemetry) :
if args.verbose :
print("You did not subscribed to any data. Please choose at least one.")
sys.exit()
connected = False
while not connected :
try :
muse = Muse(args.mac_address)
except BTLEException :
if args.verbose:
print("Peripheral unavailable. Trying again in 2 seconds")
time.sleep(2)
else :
connected = True
if args.verbose :
print("Connected at ", datetime.datetime.now())
service, = [s for s in muse.getServices() if s.uuid == MUSE_GATT_CUSTOM_SERVICE]
ccc = service.getCharacteristics(forUUID=str(MUSE_GATT_ATTR_STREAM_TOGGLE))
ccc[0].write(S_ASK)
ccc[0].write(S_STREAM)
if args.eeg :
_handles_eeg = [0x0021, 0x0024, 0x0027, 0x002a]
for hnd in _handles_eeg :
muse.writeCharacteristic(hnd, b"\x01\x00")
if args.verbose :
print("subscribed to EEG handles", hnd)
time.sleep(.2)
eeg_info = StreamInfo('%s' % args.id, 'RAW_EEG', MUSE_NB_EEG_CHANNELS, MUSE_SAMPLING_EEG_RATE, 'float32', '%s_EEG' % args.mac_address)
eeg_info.desc().append_child_value("manufacturer", "Muse")
eeg_channels = eeg_info.desc().append_child("channels")
for c in ['TP9', 'AF7', 'AF8', 'TP10'] :
eeg_channels.append_child("channel").append_child_value("label", c).append_child_value("unit", "microvolts").append_child_value("type", "RAW_EEG")
eeg_outlet = StreamOutlet(eeg_info, LSL_EEG_CHUNK)
if args.verbose :
print("Created LSL outlet for Muse EEG")
if args.ppg :
_handles_ppg = [0x0039, 0x003c, 0x003f] # ambiant / infrared / red
for hnd in _handles_ppg :
muse.writeCharacteristic(hnd, b"\x01\x00")
if args.verbose :
print("subscribed to PPG handles", hnd)
time.sleep(.2)
ppg_info = StreamInfo('%s' % args.id, 'RAW_PPG', MUSE_NB_PPG_CHANNELS, MUSE_SAMPLING_PPG_RATE, 'float32', '%s_PPG' % args.mac_address)
ppg_info.desc().append_child_value("manufacturer", "Muse")
ppg_channels = ppg_info.desc().append_child("channels")
for c in ['AMBIANT', 'INFRARED', 'RED'] :
ppg_channels.append_child("channel").append_child_value("label", c).append_child_value("unit", "mmHg").append_child_value("type", "RAW_PPG")
ppg_outlet = StreamOutlet(ppg_info, LSL_PPG_CHUNK)
if args.verbose :
print("Created LSL outlet for Muse PPG")
if args.gyro :
_handles_gyro = [0x0015]
for hnd in _handles_gyro :
muse.writeCharacteristic(hnd, b"\x01\x00")
if args.verbose :
print("subscribed to GYRO handles", hnd)
time.sleep(.2)
gyro_info = StreamInfo('%s' % args.id, 'RAW_GYRO', MUSE_NB_GYRO_CHANNELS, MUSE_SAMPLING_GYRO_RATE, 'float32', '%s_GYRO' % args.mac_address)
gyro_info.desc().append_child_value("manufacturer", "Muse")
gyro_channels = gyro_info.desc().append_child("channels")
for c in ['X', 'Y', 'Z'] :
gyro_channels.append_child("channel").append_child_value("label", c).append_child_value("unit", "dps").append_child_value("type", "RAW_GYRO")
gyro_outlet = StreamOutlet(gyro_info, LSL_GYRO_CHUNK)
if args.verbose :
print("Created LSL outlet for Muse GYRO")
if args.accelero :
_handles_accelero = [0x0018]
for hnd in _handles_accelero :
muse.writeCharacteristic(hnd, b"\x01\x00")
if args.verbose :
print("subscribed to ACCELERO handles", hnd)
time.sleep(.2)
accelero_info = StreamInfo('%s' % args.id, 'RAW_ACC', MUSE_NB_ACC_CHANNELS, MUSE_SAMPLING_ACC_RATE, 'float32', '%s_ACCELERO' % args.mac_address)
accelero_info.desc().append_child_value("manufacturer", "Muse")
acc_channels = accelero_info.desc().append_child("channels")
for c in ['X', 'Y', 'Z'] :
acc_channels.append_child("channel").append_child_value("label", c).append_child_value("unit", "g").append_child_value("type", "RAW_ACC")
accelero_outlet = StreamOutlet(accelero_info, LSL_ACC_CHUNK)
if args.verbose :
print("Created LSL outlet for Muse ACC")
if args.telemetry :
_handles_telemetry = [0x001b]
for hnd in _handles_telemetry :
muse.writeCharacteristic(hnd, b"\x01\x00")
if args.verbose :
print("subscribed to TELEMETRY handles", hnd)
time.sleep(.2)
telemetry_info = StreamInfo('%s' % args.id, 'RAW_TELEMETRY', MUSE_NB_TELEMETRY_CHANNELS, MUSE_SAMPLING_TELEMETRY_RATE, 'float32', '%s_TELEMETRY' % args.mac_address)
telemetry_info.desc().append_child_value("manufacturer", "Muse")
tel_chans = telemetry_info.desc().append_child("channels")
tel_chans.append_child("channel").append_child_value("label", "battery").append_child_value("unit", "percentage").append_child_value("type", "RAW_TELEMETRY")
tel_chans.append_child("channel").append_child_value("label", "fuel_gauge").append_child_value("unit", "??").append_child_value("type", "RAW_TELEMETRY")
tel_chans.append_child("channel").append_child_value("label", "adv_volt").append_child_value("unit", "volt?").append_child_value("type", "RAW_TELEMETRY")
tel_chans.append_child("channel").append_child_value("label", "temperature").append_child_value("unit", "farenheit ?").append_child_value("type", "RAW_TELEMETRY")
telemetry_outlet = StreamOutlet(telemetry_info, LSL_TELEMETRY_CHUNK)
muse.delegate.handleNotification = muse.print_data
if args.verbose :
print("Launching infinite loop")
while connected :
try :
while True :
muse.waitForNotifications(1./MUSE_SAMPLING_EEG_RATE)
except KeyboardInterrupt :
if args.verbose :
print("Exited by KeyboardInterrupt event at ", datetime.datetime.now())
sys.exit()
except BTLEException :
if args.verbose :
print("BTLE exception at ", datetime.datetime.now(), ". Trying to reconnect.")
connected = False
except :
if args.verbose :
print("Don't know what happened at ", datetime.datetime.now())
print(sys.exc_info()[0])
connected = False
#constants.py
MUSE_GATT_CUSTOM_SERVICE = "0000fe8d-0000-1000-8000-00805f9b34fb"
MUSE_GATT_ATTR_STREAM_TOGGLE = '273e0001-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_TP9 = '273e0003-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_AF7 = '273e0004-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_AF8 = '273e0005-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_TP10 = '273e0006-4c4d-454d-96be-f03bac821358'
#MUSE_GATT_ATTR_RIGHTAUX = '273e0007-4c4d-454d-96be-f03bac821358' // 2c
#273e0008 ? 11
#273e0002 ? 1d
#273e000c ? 2f
#273e000d ? 32
#273e000e ? 35
#273e000f ? 38
#273e0010 ? 3b
#273e0011 ? 3e
#273e0012 ? 41
MUSE_GATT_ATTR_PPG1 = "273e000f-4c4d-454d-96be-f03bac821358"
MUSE_GATT_ATTR_PPG2 = "273e0010-4c4d-454d-96be-f03bac821358"
MUSE_GATT_ATTR_PPG3 = "273e0011-4c4d-454d-96be-f03bac821358"
MUSE_GATT_ATTR_GYRO = '273e0009-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_ACCELEROMETER = '273e000a-4c4d-454d-96be-f03bac821358'
MUSE_GATT_ATTR_TELEMETRY = '273e000b-4c4d-454d-96be-f03bac821358'
S_ASK = b'\x02\x73\x0a'
S_STREAM = b'\x02\x64\x0a'
MUSE_EEG_SCALE_FACTOR = 0.48828125
MUSE_PPG_SCALE_FACTOR = 0.48828125
MUSE_ACCELERO_SCALE_FACTOR = 0.0000610352
MUSE_GYRO_SCALE_FACTOR = 0.0074768
MUSE_NB_EEG_CHANNELS = 5
MUSE_SAMPLING_EEG_RATE = 256
LSL_EEG_CHUNK = 12
MUSE_NB_PPG_CHANNELS = 3
MUSE_SAMPLING_PPG_RATE = 64
LSL_PPG_CHUNK = 6
MUSE_NB_ACC_CHANNELS = 3
MUSE_SAMPLING_ACC_RATE = 52
LSL_ACC_CHUNK = 1
MUSE_NB_GYRO_CHANNELS = 3
MUSE_SAMPLING_GYRO_RATE = 52
LSL_GYRO_CHUNK = 1
MUSE_NB_TELEMETRY_CHANNELS = 4
MUSE_SAMPLING_TELEMETRY_RATE = 0
LSL_TELEMETRY_CHUNK = 1
LSL_BUFFER = 360 # Buffer length.
And to read the streams :
from pylsl import ContinuousResolver, StreamInfo, StreamInlet
#from pylsl import LostError
import time, sys, argparse
parser = argparse.ArgumentParser(description='Two-ways connection to a remote tobecou device, using LSL for input / output.')
parser.add_argument("-t", "--type", help="LSL type of the stream", default="notype", type=str)
parser.add_argument("-v", "--verbose", action='store_true', help="Print more verbose information.")
parser.set_defaults(verbose=False)
args = parser.parse_args()
cr = ContinuousResolver(prop = "type", value = args.type, forget_after = 5)
streams = []
while (len(streams) == 0) :
streams = cr.results()
time.sleep(2)
print("looking for the first stream")
size_streams = len(streams)
print("initial number of streams = ", size_streams)
inlets = []
for s in streams :
inlets.append(StreamInlet(s))
while True :
streams = cr.results()
if len(streams) == 0 :
print("Lost every stream, exiting ...")
sys.exit()
if len(streams) != size_streams :
for inlet in inlets :
del inlet
size_streams = len(streams)
for s in streams :
inlets.append(StreamInlet(s))
for inlet in inlets :
sample, timestamp = inlet.pull_sample(timeout = [.1])
if sample != None :
last_timestamp = timestamp
print("Name : " + inlet.info().name() + ", type : " + inlet.info().type() + " at " + str(timestamp) + " >> ")
for s in sample :
print s,
print ""
Hope it helps ;)
Also, those are the outputs of gatttool :
gatttool -I -b 00:55:DA:B9:09:99 -t public
> primary
attr handle: 0x0001, end grp handle: 0x0004 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0005, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x0042 uuid: 0000fe8d-0000-1000-8000-00805f9b34fb
> char-desc 0x000c 0x0042
handle: 0x000c, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000e, uuid: 273e0001-4c4d-454d-96be-f03bac821358
handle: 0x000f, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0011, uuid: 273e0008-4c4d-454d-96be-f03bac821358
handle: 0x0012, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0013, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 273e0009-4c4d-454d-96be-f03bac821358
handle: 0x0015, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0016, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 273e000a-4c4d-454d-96be-f03bac821358
handle: 0x0018, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001a, uuid: 273e000b-4c4d-454d-96be-f03bac821358
handle: 0x001b, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001d, uuid: 273e0002-4c4d-454d-96be-f03bac821358
handle: 0x001e, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001f, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0020, uuid: 273e0003-4c4d-454d-96be-f03bac821358
handle: 0x0021, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0022, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0023, uuid: 273e0004-4c4d-454d-96be-f03bac821358
handle: 0x0024, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0025, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0026, uuid: 273e0005-4c4d-454d-96be-f03bac821358
handle: 0x0027, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0028, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0029, uuid: 273e0006-4c4d-454d-96be-f03bac821358
handle: 0x002a, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x002b, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002c, uuid: 273e0007-4c4d-454d-96be-f03bac821358
handle: 0x002d, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x002e, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002f, uuid: 273e000c-4c4d-454d-96be-f03bac821358
handle: 0x0030, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0031, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0032, uuid: 273e000d-4c4d-454d-96be-f03bac821358
handle: 0x0033, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0034, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0035, uuid: 273e000e-4c4d-454d-96be-f03bac821358
handle: 0x0036, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0037, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0038, uuid: 273e000f-4c4d-454d-96be-f03bac821358
handle: 0x0039, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x003a, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x003b, uuid: 273e0010-4c4d-454d-96be-f03bac821358
handle: 0x003c, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x003d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x003e, uuid: 273e0011-4c4d-454d-96be-f03bac821358
handle: 0x003f, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0040, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0041, uuid: 273e0012-4c4d-454d-96be-f03bac821358
handle: 0x0042, uuid: 00002902-0000-1000-8000-00805f9b34fb
For the Muse2 they were
> primary
attr handle: 0x0001, end grp handle: 0x0004 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0005, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x003f uuid: 0000fe8d-0000-1000-8000-00805f9b34fb
> char-desc 0x000c 0x003f
handle: 0x000c, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000e, uuid: 273e0001-4c4d-454d-96be-f03bac821358
handle: 0x000f, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0011, uuid: 273e0008-4c4d-454d-96be-f03bac821358
handle: 0x0012, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0013, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0014, uuid: 273e0009-4c4d-454d-96be-f03bac821358
handle: 0x0015, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0016, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0017, uuid: 273e000a-4c4d-454d-96be-f03bac821358
handle: 0x0018, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0019, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001a, uuid: 273e000b-4c4d-454d-96be-f03bac821358
handle: 0x001b, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x001d, uuid: 273e0002-4c4d-454d-96be-f03bac821358
handle: 0x001e, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x001f, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0020, uuid: 273e0003-4c4d-454d-96be-f03bac821358
handle: 0x0021, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0022, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0023, uuid: 273e0004-4c4d-454d-96be-f03bac821358
handle: 0x0024, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0025, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0026, uuid: 273e0005-4c4d-454d-96be-f03bac821358
handle: 0x0027, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0028, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0029, uuid: 273e0006-4c4d-454d-96be-f03bac821358
handle: 0x002a, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x002b, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002c, uuid: 273e0007-4c4d-454d-96be-f03bac821358
handle: 0x002d, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x002e, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x002f, uuid: 273e000c-4c4d-454d-96be-f03bac821358
handle: 0x0030, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0031, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0032, uuid: 273e000d-4c4d-454d-96be-f03bac821358
handle: 0x0033, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0034, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0035, uuid: 273e000e-4c4d-454d-96be-f03bac821358
handle: 0x0036, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x0037, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0038, uuid: 273e000f-4c4d-454d-96be-f03bac821358
handle: 0x0039, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x003a, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x003b, uuid: 273e0010-4c4d-454d-96be-f03bac821358
handle: 0x003c, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x003d, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x003e, uuid: 273e0011-4c4d-454d-96be-f03bac821358
handle: 0x003f, uuid: 00002902-0000-1000-8000-00805f9b34fb
So MuseS - Muse2 is only
handle: 0x0040, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0041, uuid: 273e0012-4c4d-454d-96be-f03bac821358
handle: 0x0042, uuid: 00002902-0000-1000-8000-00805f9b34fb
But saying that, apart from those I use in my code, I don't know what others characteristics are for. When I tried to read the unknown characteristic, I had data coming from 0x0012, 0x0039, 0x003c, 0x003f (both Muse2 and S) and 0x0042 (just MuseS), but I don't know their meaning And nothing was coming from 0x0030, 0x0033 and 0x0036 (but this could come from a disabled parameter somewhere).
Question for people who've connected muse s. How is it the same? Are electrode column locations the same? Hasn't muse s scrapped mastoid/ear electrodes?
@apavlo89 No. same electrode positions, different material and mechanics.
FWIW, I've been able to stream EEG and PPG from the Muse S without any modifications. I'd expect ACC and GYRO to work as well, but haven't tested it.
sorry i missed this thread when i opened #142 . as mentioned there my notes are at https://github.com/xloem/pymuse/blob/master/muse_async.py including all gatt channels I found with names, a little ways down. I'm new to BLE and don't really understand how the uuids relate to the handles to fill in the handles remy17 describes but they're all there; there's a thermistor and another aux channel (and of course the drl channel, unsure why that one isn't in muselsl) in addition to the previously-known channels. #143 #144 preset 0x63 streams all the channels at once which is nice.
But saying that, apart from those I use in my code, I don't know what others characteristics are for. When I tried to read the unknown characteristic, I had data coming from 0x0012, 0x0039, 0x003c, 0x003f (both Muse2 and S) and 0x0042 (just MuseS), but I don't know their meaning
0x12 is DRL_REF. 0x39-0x3f you've already described. 0x42 appears to be thermistor.
And nothing was coming from 0x0030, 0x0033 and 0x0036 (but this could come from a disabled parameter somewhere).
I have magnetometer, pressure, ultraviolet for these. I haven't seen them output anything either.
I just recently noticed that the muse S appears to advertise its serial port over the usb cable attached to it. It looks like the muse S can be used without bluetooth at all, exchanging packets over its usb cable. I didn't know this. It would be good to eventually support direct serial access. EDIT: at first glance it seems the port might do commands but not data, unsure.
When I connect to MuseS, I can not get the extra electrode signal, which is Right AUX channel. Does anyone know how to fix this issue? How to modify the code? Thank you very much.
I'm thinking of getting a Muse S. Has anyone played with getting it to work with this yet?