Skip to main content

Instinct JS SDK Reference

Overview

The Instinct JavaScript SDK (@nexstem/instinct-js) provides a comprehensive set of tools for discovering, connecting to, and controlling Nexstem Instinct headsets from Node.js applications. This SDK enables developers to:

  • Discover Instinct headsets on the local network
  • Monitor headset state (battery, CPU, RAM, storage)
  • Configure electrodes and sensors
  • Create and manage data streams
  • Build custom signal processing pipelines
  • Store and retrieve device configuration values

Installation

# Using npm
npm install @nexstem/instinct-js

# Using yarn
yarn add @nexstem/instinct-js

Requirements

  • Node.js 19 or later
  • An Instinct headset on the same network

Getting Started

Importing the SDK

// ES Modules
import { Headset } from '@nexstem/instinct-js';

// CommonJS
const { Headset } = require('@nexstem/instinct-js');

Discovering Headsets

The SDK provides methods to discover Instinct headsets on your local network:

import { Headset } from '@nexstem/instinct-js';

async function discoverHeadsets() {
try {
// Discover all Instinct headsets on the network
const headsets = await Headset.discover();
console.log(`Found ${headsets.length} headsets`);

if (headsets.length > 0) {
// Connect to the first headset found
const headset = headsets[0];
console.log(`Connected to headset: ${await headset.getName()}`);
return headset;
} else {
console.log('No headsets found');
return null;
}
} catch (error) {
console.error('Error discovering headsets:', error.message);
return null;
}
}

Connecting to a Known Headset

If you know the IP address of your headset, you can connect directly:

import { Headset } from '@nexstem/instinct-js';

// Connect to a headset at a specific IP address
const headset = new Headset('192.168.1.100');

// Enable debug mode for additional logging
const headsetWithDebug = new Headset('192.168.1.100', { debug: true });

API Reference

Headset Class

The Headset class is the main entry point for interacting with Instinct devices.

Static Methods

MethodDescriptionParametersReturns
discover()Discovers all Instinct headsets on the local networkNonePromise<Headset[]>

Instance Methods

MethodDescriptionParametersReturns
getState()Gets the current state of the headsetNonePromise<HeadsetState>
getName()Gets the headset nameNonePromise<string>
setName(name)Sets the headset namename: stringPromise<void>
getVersion()Gets the firmware versionNonePromise<string>
restart()Restarts the headsetNonePromise<void>
shutdown()Shuts down the headsetNonePromise<void>

Properties

PropertyTypeDescription
StreamsManagerStreamsManagerManages data processing streams
DeviceConfigManagerDeviceConfigManagerManages device configuration

HeadsetState Interface

interface HeadsetState {
status: 'connected' | 'disconnected' | 'error';
battery: {
percent: number;
charging: boolean;
};
cpu: {
load: number;
temperature: number;
};
memory: {
used: number;
total: number;
};
storage: {
used: number;
total: number;
};
}

Stream Management

The SDK provides classes for creating and managing data processing streams:

StreamsManager Class

Manages the lifecycle of data processing streams.

MethodDescriptionParametersReturns
createStream(config)Creates a new stream with the provided configurationconfig: StreamConfigStream
getStream(id)Gets a stream by IDid: stringPromise<Stream>
listStreams()Lists all streamsNonePromise<Stream[]>
deleteStream(id)Deletes a streamid: stringPromise<void>

Stream Class

Represents a data processing stream with nodes and pipes.

MethodDescriptionParametersReturns
create()Creates the stream on the headsetNonePromise<void>
start()Starts data processingNonePromise<void>
stop()Stops data processingNonePromise<void>
sendSignal(signal, parameters?)Sends a signal to the streamsignal: string, parameters?: anyPromise<void>
addNodes(nodes)Adds new nodes to the streamnodes: NodeConfig[]Promise<void>
deleteNode(nodeId)Deletes a node from the streamnodeId: stringPromise<void>
addPipes(pipes)Adds new pipes to the streampipes: PipeConfig[]Promise<void>
deletePipe(pipeId)Deletes a pipe from the streampipeId: stringPromise<void>
reconcile()Reconciles the stream configurationNonePromise<void>

Node Class

Represents a processing node in a stream.

MethodDescriptionParametersReturns
create()Creates the node on the headsetNonePromise<void>
sendSignal(signal, parameters)Sends a signal to the nodesignal: string, parameters: anyPromise<void>
delete()Deletes the node from the streamNonePromise<void>

Pipe Class

Represents a connection between nodes.

MethodDescriptionParametersReturns
create()Creates the pipe on the headsetNonePromise<void>
delete()Deletes the pipe from the streamNonePromise<void>

Device Configuration Management

The SDK provides DeviceConfigManager for storing and retrieving persistent configuration values on the headset.

DeviceConfigManager Class

Manages device configuration storage.

MethodDescriptionParametersReturns
createConfig(configData)Creates a new configuration entryconfigData: ConfigDataPromise<string>
getConfig(key)Retrieves a configuration by keykey: stringPromise<ConfigData>
updateConfig(key, configData)Updates an existing configurationkey: string, configData: ConfigDataPromise<void>
deleteConfig(key)Deletes a configuration entrykey: stringPromise<void>

Configuration Data Types

interface ConfigData {
key: string; // Unique identifier for the configuration
value: any; // Value to store (serialized to string)
expiresIn?: string; // Optional time-to-live (e.g., "1h", "2d", "30m")
id?: string; // Optional unique ID (auto-generated if not provided)
}

Tutorials

Basic Stream Creation

This tutorial demonstrates how to create a simple EEG stream:

import { Headset } from '@nexstem/instinct-js';
import { v4 as uuidv4 } from 'uuid';

async function createBasicStream() {
try {
// Discover and connect to headset
const headsets = await Headset.discover();
if (headsets.length === 0) {
console.log('No headsets found.');
return;
}

const headset = headsets[0];
console.log(`Connected to headset: ${await headset.getName()}`);

// Generate UUIDs for nodes
const sourceId = uuidv4();
const ssvepId = uuidv4();

// Create a stream
const stream = headset.StreamsManager.createStream({
id: uuidv4(),
nodes: [
{
executable: 'eeg_source',
config: {
sampleRate: 1000,
gain: 1,
},
id: sourceId,
},
{
executable: 'ssvep_algo',
config: {},
id: ssvepId,
},
],
pipes: [
{
source: sourceId,
destination: ssvepId,
id: uuidv4(),
},
],
});

// Create the stream on the headset
await stream.create();
console.log('Stream created successfully');

// Start the stream
await stream.start();
console.log('Stream started successfully');

// Let it run for a while (10 seconds)
await new Promise((resolve) => setTimeout(resolve, 10000));

// Stop the stream
await stream.stop();
console.log('Stream stopped successfully');
} catch (error) {
console.error('Error:', error.message);
}
}

createBasicStream();

Alpha Rhythm Analysis Stream

This tutorial demonstrates how to create a more complex stream for analyzing alpha rhythms:

import { Headset } from '@nexstem/instinct-js';
import { v4 as uuidv4 } from 'uuid';

async function createAlphaRhythmStream() {
try {
// Connect to a headset at a specific IP address
const headset = new Headset('192.168.1.100');

// Generate UUIDs for nodes
const sourceId = uuidv4();
const bandpassId = uuidv4();
const fftId = uuidv4();
const websocketId = uuidv4();

// Create a stream with custom metadata
const stream = headset.StreamsManager.createStream({
id: uuidv4(),
meta: {
name: 'Alpha Rhythm Analysis',
description:
'Extracts and analyzes alpha rhythms from occipital electrodes',
version: '1.0.0',
},
nodes: [
// EEG data source
{
executable: 'eeg_source',
config: {
sampleRate: 250,
channels: ['O1', 'O2', 'PZ'],
},
id: sourceId,
},
// Bandpass filter for alpha band (8-12 Hz)
{
executable: 'bandpass_filter',
config: {
cutoff: 10,
bandwidth: 4,
order: 4,
},
id: bandpassId,
},
// Spectral analysis node
{
executable: 'fft_analyzer',
config: {
windowSize: 512,
overlap: 0.5,
},
id: fftId,
},
// Data output node
{
executable: 'websocket_output',
config: {
port: 36006,
},
id: websocketId,
},
],
pipes: [
// Connect the nodes in sequence
{
id: uuidv4(),
source: sourceId,
destination: bandpassId,
},
{
id: uuidv4(),
source: bandpassId,
destination: fftId,
},
{
id: uuidv4(),
source: fftId,
destination: websocketId,
},
],
});

// Create and start the stream
await stream.create();
console.log('Alpha rhythm analysis stream created');

await stream.start();
console.log('Alpha rhythm analysis stream started');

console.log(
'Stream is running. Connect to WebSocket at ws://localhost:36006 to view data'
);
console.log('Press Ctrl+C to stop the stream');
} catch (error) {
console.error('Error:', error.message);
}
}

createAlphaRhythmStream();

Extending a Stream with Custom Nodes

This tutorial shows how to add custom nodes to an existing stream:

import { Headset } from '@nexstem/instinct-js';
import { v4 as uuidv4 } from 'uuid';

async function extendStreamWithCustomNode(
streamId,
fftNodeId,
websocketNodeId
) {
try {
// Connect to headset
const headset = new Headset('192.168.1.100');

// Get existing stream
const stream = await headset.StreamsManager.getStream(streamId);

// First, stop the running stream
await stream.stop();
console.log('Stream stopped for modification');

// Add the new custom node
const customNodeId = uuidv4();
await stream.addNodes([
{
executable: 'custom_feature_extractor',
config: {
threshold: 0.75,
windowSize: 256,
},
meta: {
author: 'Nexstem',
version: '1.1.0',
},
id: customNodeId,
},
]);
console.log('Custom node added');

// Add new pipes to connect the custom node
const newPipes = [
{
id: uuidv4(),
source: fftNodeId,
destination: customNodeId,
},
{
id: uuidv4(),
source: customNodeId,
destination: websocketNodeId,
},
];

await stream.addPipes(newPipes);
console.log('New pipes added');

// Find and delete the pipe which is no longer relevant
// (This would be the direct connection from FFT to WebSocket)
const pipes = stream.getPipes();
const pipeToDelete = pipes.find(
(pipe) =>
pipe.source === fftNodeId && pipe.destination === websocketNodeId
);

if (pipeToDelete) {
await stream.deletePipes([pipeToDelete.id]);
console.log('Old pipe deleted');
}

// Reconcile the stream configuration to adapt to the new configuration
await stream.reconcile();
console.log('Stream reconciled');

// Start the stream again
await stream.start();
console.log('Stream restarted with custom node');
} catch (error) {
console.error('Error:', error.message);
}
}

Managing Device Configuration

This tutorial shows how to store and retrieve configuration values on the headset:

import { Headset } from '@nexstem/instinct-js';

async function manageDeviceConfig() {
try {
// Connect to headset
const headset = new Headset('192.168.1.100');

// Store a configuration value
const configKey = 'user_preferences';
const configValue = {
channels: ['Fp1', 'Fp2', 'F7', 'F8'],
sampleRate: 500,
notchFilter: true,
theme: 'dark',
};

// Create a configuration that expires in 30 days
const configId = await headset.DeviceConfigManager.createConfig({
key: configKey,
value: configValue,
expiresIn: '30d',
});

console.log(`Configuration created with ID: ${configId}`);

// Retrieve the configuration
const retrievedConfig = await headset.DeviceConfigManager.getConfig(
configKey
);
console.log('Retrieved configuration:', retrievedConfig);

// Update the configuration
configValue.theme = 'light';
await headset.DeviceConfigManager.updateConfig(configKey, {
value: configValue,
expiresIn: '60d', // Extended expiration
});

console.log('Configuration updated');

// Retrieve the updated configuration
const updatedConfig = await headset.DeviceConfigManager.getConfig(
configKey
);
console.log('Updated configuration:', updatedConfig);
} catch (error) {
console.error('Error:', error.message);
}
}

manageDeviceConfig();

Best Practices

Error Handling

Always implement proper error handling when working with the SDK:

try {
const headsets = await Headset.discover();
if (headsets.length === 0) {
console.log('No headsets found.');
return;
}

const headset = headsets[0];
await headset.setName('My Headset');
} catch (error) {
console.error('Error:', error.message);

// Handle specific error types
if (error.name === 'ConnectionError') {
console.log('Connection to headset was lost. Attempting to reconnect...');
// Implement reconnection logic
} else if (error.name === 'ValidationError') {
console.log('Invalid configuration provided:', error.details);
// Fix configuration issues
}
}

Resource Management

Properly manage streams to conserve device resources:

// Always stop streams when they're no longer needed
try {
await stream.start();
// Use the stream...
} finally {
// Ensure the stream is stopped even if an error occurs
if (stream) {
await stream.stop();
}
}

Connection Optimization

For optimal performance, implement the following practices:

  1. Use debug mode only when necessary

    // Enable only during development
    const headset = new Headset('192.168.1.100', { debug: true });
  2. Optimize stream configurations

    // Use appropriate sample rates
    {
    executable: 'eeg_source',
    config: {
    // Only use the sample rate you actually need
    sampleRate: 250, // Instead of 1000 if 250Hz is sufficient
    // Only include channels you need to process
    channels: ['O1', 'O2'],
    }
    }
  3. Implement connection monitoring

    // Periodically check connection status
    setInterval(async () => {
    try {
    const state = await headset.getState();
    if (state.status !== 'connected') {
    console.log('Headset disconnected, attempting to reconnect...');
    // Implement reconnection logic
    }
    } catch (error) {
    console.error('Connection monitoring error:', error.message);
    }
    }, 5000);

Troubleshooting

Common Issues

Cannot Discover Headsets

// Problem: Headset.discover() returns an empty array

// Solutions:
// 1. Ensure the headset is powered on and connected to the same network
// 2. Check firewall settings that might block UDP broadcasts (port 48010)
// 3. Try specifying the IP address directly:
const headset = new Headset('192.168.1.100');
tip

If you know the headset's IP address, connecting directly is more reliable than discovery.

Stream Creation Fails

// Problem: stream.create() throws an error

// Solutions:
// 1. Verify all UUIDs are valid
import { validate as uuidValidate } from 'uuid';
if (!uuidValidate(nodeId)) {
console.error('Invalid UUID:', nodeId);
}

// 2. Check that node executables exist on the headset
// Only use executables that are documented or that you've confirmed exist

// 3. Ensure pipe connections reference valid node IDs
// Make sure source and destination IDs match existing nodes

Configuration Storage Fails

// Problem: DeviceConfigManager.createConfig() throws an error

// Solutions:
// 1. Check that the key is a valid string
// 2. Ensure the value can be properly serialized
// 3. Verify that the expiration format is correct
const validExpirationFormats = ['30s', '5m', '2h', '1d', '1w', '1M'];

Connection Timeouts

// Problem: API calls time out or take too long

// Solutions:
// 1. The headset may be overloaded; try simplifying your stream
// 2. Check network stability and latency
// 3. Increase timeout values in API calls
const headset = new Headset('192.168.1.100', {
timeout: 10000, // 10 seconds instead of default
});

Diagnostic Techniques

Enable Debug Mode

// Turn on detailed logging
const headset = new Headset('192.168.1.100', { debug: true });

Check Headset State

// Get comprehensive information about the headset
const state = await headset.getState();
console.log(JSON.stringify(state, null, 2));

// Check specific metrics
if (state.cpu.load > 80) {
console.warn('CPU load is high, consider simplifying your stream');
}

if (state.battery.percent < 20 && !state.battery.charging) {
console.warn('Battery is low, connect the headset to power');
}

Inspect Streams

// List all running streams to debug potential conflicts
const streams = await headset.StreamsManager.listStreams();
console.log(`Running streams: ${streams.length}`);
streams.forEach((stream) => {
console.log(`- Stream ${stream.id}: ${stream.meta.name || 'unnamed'}`);
});

Advanced Topics

Custom Networking Configuration

For environments with complex networking requirements:

const headset = new Headset('192.168.1.100', {
port: 48011, // Custom port
timeout: 15000, // 15 second timeout
retries: 3, // Number of retry attempts
retryDelay: 1000, // Delay between retries (ms)
});

WebSocket Data Consumption

To work with data from a WebSocket output node:

const WebSocket = require('ws');

function connectToDataStream(url) {
const socket = new WebSocket(url);

socket.on('open', () => {
console.log('Connected to data stream');
});

socket.on('message', (data) => {
// Parse the incoming binary data
const parsedData = parseEEGData(data);
console.log('Received data:', parsedData);

// Process or visualize the data
processData(parsedData);
});

socket.on('error', (error) => {
console.error('WebSocket error:', error.message);
});

socket.on('close', () => {
console.log('Disconnected from data stream');
});

return socket;
}

// Connect to the WebSocket server running on the headset
const dataSocket = connectToDataStream('ws://192.168.1.100:36006');

Multi-Headset Management

For applications that need to work with multiple headsets:

import { Headset } from '@nexstem/instinct-js';

class HeadsetManager {
constructor() {
this.headsets = new Map();
}

async discoverAndConnect() {
const devices = await Headset.discover();
console.log(`Found ${devices.length} headsets`);

for (const device of devices) {
try {
const name = await device.getName();
const state = await device.getState();

this.headsets.set(name, {
device,
state,
activeStreams: [],
});

console.log(`Connected to headset: ${name}`);
} catch (error) {
console.error(`Failed to connect to headset: ${error.message}`);
}
}

return this.headsets.size;
}

getHeadset(name) {
return this.headsets.get(name)?.device || null;
}

async startStreamOnAll(streamConfig) {
const results = [];

for (const [name, headsetInfo] of this.headsets.entries()) {
try {
const stream =
headsetInfo.device.StreamsManager.createStream(streamConfig);
await stream.create();
await stream.start();

headsetInfo.activeStreams.push(stream);
results.push({ name, success: true, stream });

console.log(`Started stream on headset: ${name}`);
} catch (error) {
results.push({ name, success: false, error: error.message });
console.error(
`Failed to start stream on headset ${name}: ${error.message}`
);
}
}

return results;
}

async stopAllStreams() {
for (const [name, headsetInfo] of this.headsets.entries()) {
try {
for (const stream of headsetInfo.activeStreams) {
await stream.stop();
}
headsetInfo.activeStreams = [];
console.log(`Stopped all streams on headset: ${name}`);
} catch (error) {
console.error(
`Error stopping streams on headset ${name}: ${error.message}`
);
}
}
}
}

This documentation provides a comprehensive overview of the Instinct JS SDK. For further assistance or contributions to this documentation, please contact the Nexstem development team.