alexandrebarachant / muse-lsl

Python script to stream EEG data from the muse 2016 headset
BSD 3-Clause "New" or "Revised" License
599 stars 177 forks source link

Muse S? #120

Open adamwolf opened 4 years ago

adamwolf commented 4 years ago

I'm thinking of getting a Muse S. Has anyone played with getting it to work with this yet?

alexandrebarachant commented 4 years ago

Not me, but I'm curious to know if it works.

adamwolf commented 4 years ago

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 .

RemyRamdam commented 4 years ago

Got one yesterday, working exactly as the Muse 2 from a bluepy perspective.

iPsych commented 4 years ago

@RemyRamdam Fantastique, pioneer! Thanks for the information. Would you check how many EEG channels in Muse-S? Same four like the other bands?

RemyRamdam17 commented 4 years ago

Hello @iPsych, As far as I can tell, they are the same Carousel_MuseS_ProductPage_4-small

kowalej commented 4 years ago

@Remy17 @RemyRamdam - just to confirm - were you able to steam all channels (accelerometer, gryo, PPG, EEG, telemetry, etc) without any modification of muse-lsl?

RemyRamdam17 commented 4 years ago

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 :

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 ;)

RemyRamdam17 commented 4 years ago

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).

apavlo89 commented 3 years ago

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?

oori commented 3 years ago

@apavlo89 No. same electrode positions, different material and mechanics.

ErikBjare commented 3 years ago

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.

xloem commented 3 years ago

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.

xloem commented 3 years ago

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.

xloem commented 2 years ago

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.

guz6 commented 1 year ago

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.