muse-lsl icon indicating copy to clipboard operation
muse-lsl copied to clipboard

Muse S?

Open adamwolf opened this issue 5 years ago • 15 comments

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

adamwolf avatar Feb 16 '20 02:02 adamwolf

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

alexandrebarachant avatar Feb 16 '20 03:02 alexandrebarachant

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 < [email protected]> 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 .

adamwolf avatar Feb 16 '20 03:02 adamwolf

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

RemyRamdam avatar Mar 07 '20 19:03 RemyRamdam

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

iPsych avatar Mar 27 '20 10:03 iPsych

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

RemyRamdam17 avatar Mar 27 '20 13:03 RemyRamdam17

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

kowalej avatar Apr 05 '20 18:04 kowalej

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

  • a screenshot annoted to understand what you are looking in the video screenshot

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 avatar Apr 06 '20 15:04 RemyRamdam17

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

RemyRamdam17 avatar Apr 06 '20 15:04 RemyRamdam17

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 avatar Sep 18 '20 11:09 apavlo89

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

oori avatar Sep 18 '20 13:09 oori

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.

ErikBjare avatar Dec 07 '20 10:12 ErikBjare

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 avatar Jan 06 '21 17:01 xloem

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 avatar Jan 07 '21 23:01 xloem

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.

xloem avatar Apr 05 '22 18:04 xloem

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.

guz6 avatar Feb 25 '23 23:02 guz6