Skip to main content

Start With A Known-Good Demo

If you want the fastest path to a working browser BLE example, scaffold the BLE demo first:
npm create @elata-biosciences/elata-demo my-app -- --template eeg-web-ble-demo
cd my-app
pnpm install
pnpm run dev

Requirements

  • Chrome or Edge
  • https:// or localhost
  • Bluetooth enabled on the machine
  • a supported Muse-compatible EEG device
Supported device classes in this repo today:
  • Muse 2 and Muse S classic BLE devices
  • Muse S Athena protocol v2 devices
  • the synthetic Muse-compatible BLE bridge used for testing
Safari and iOS do not support this browser BLE workflow.

Install

pnpm add @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
npm install @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble

Minimal Integration

import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";

const transport = new BleTransport({
  deviceOptions: {
    // AthenaWasmDecoder lives in eeg-web but is required here as a peer dep.
    athenaDecoderFactory: () => new AthenaWasmDecoder(),
  },
});

transport.onFrame = (frame) => {
  console.log(frame.eeg.samples.length);
};

transport.onStatus = (status) => {
  console.log(status.state, status.reason);
};

// startStreaming() calls connect() + start() in one step.
// If BLE is already connected (e.g. after stop()), it skips re-pairing.
await transport.startStreaming();

Typical Flow

  1. Confirm the app is running in a secure context.
  2. Construct BleTransport.
  3. Provide athenaDecoderFactory if you need Athena support.
  4. Subscribe to frame and status callbacks.
  5. Call startStreaming() (or connect() then start() separately).

Common Gotchas

  • Use startStreaming() for the common case. It calls connect() and start() in one step and skips re-pairing if already connected. If you call connect() and start() separately, both are required — omitting start() produces silence with no error.
  • frame.eeg.samples is number[][] laid out as [channelIdx][sampleIdx]. Each inner array is one channel. If you need interleaved format for WASM models, convert manually.
  • If navigator.bluetooth is missing, you are likely in an unsupported browser or non-secure context.
  • If the device chooser never appears, confirm Bluetooth is enabled and the page is served from https:// or localhost.
  • If Athena devices fail to decode, make sure you pass an athenaDecoderFactory backed by @elata-biosciences/eeg-web.
  • If you need Safari or iOS support, plan for a native bridge or hybrid strategy instead of this browser path.

Next Steps