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
Method | Description | Parameters | Returns |
---|---|---|---|
discover() | Discovers all Instinct headsets on the local network | None | Promise<Headset[]> |
Instance Methods
Method | Description | Parameters | Returns |
---|---|---|---|
getState() | Gets the current state of the headset | None | Promise<HeadsetState> |
getName() | Gets the headset name | None | Promise<string> |
setName(name) | Sets the headset name | name: string | Promise<void> |
getVersion() | Gets the firmware version | None | Promise<string> |
restart() | Restarts the headset | None | Promise<void> |
shutdown() | Shuts down the headset | None | Promise<void> |
Properties
Property | Type | Description |
---|---|---|
StreamsManager | StreamsManager | Manages data processing streams |
DeviceConfigManager | DeviceConfigManager | Manages 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.
Method | Description | Parameters | Returns |
---|---|---|---|
createStream(config) | Creates a new stream with the provided configuration | config: StreamConfig | Stream |
getStream(id) | Gets a stream by ID | id: string | Promise<Stream> |
listStreams() | Lists all streams | None | Promise<Stream[]> |
deleteStream(id) | Deletes a stream | id: string | Promise<void> |
Stream Class
Represents a data processing stream with nodes and pipes.
Method | Description | Parameters | Returns |
---|---|---|---|
create() | Creates the stream on the headset | None | Promise<void> |
start() | Starts data processing | None | Promise<void> |
stop() | Stops data processing | None | Promise<void> |
sendSignal(signal, parameters?) | Sends a signal to the stream | signal: string, parameters?: any | Promise<void> |
addNodes(nodes) | Adds new nodes to the stream | nodes: NodeConfig[] | Promise<void> |
deleteNode(nodeId) | Deletes a node from the stream | nodeId: string | Promise<void> |
addPipes(pipes) | Adds new pipes to the stream | pipes: PipeConfig[] | Promise<void> |
deletePipe(pipeId) | Deletes a pipe from the stream | pipeId: string | Promise<void> |
reconcile() | Reconciles the stream configuration | None | Promise<void> |
Node Class
Represents a processing node in a stream.
Method | Description | Parameters | Returns |
---|---|---|---|
create() | Creates the node on the headset | None | Promise<void> |
sendSignal(signal, parameters) | Sends a signal to the node | signal: string, parameters: any | Promise<void> |
delete() | Deletes the node from the stream | None | Promise<void> |
Pipe Class
Represents a connection between nodes.
Method | Description | Parameters | Returns |
---|---|---|---|
create() | Creates the pipe on the headset | None | Promise<void> |
delete() | Deletes the pipe from the stream | None | Promise<void> |
Device Configuration Management
The SDK provides DeviceConfigManager for storing and retrieving persistent configuration values on the headset.
DeviceConfigManager Class
Manages device configuration storage.
Method | Description | Parameters | Returns |
---|---|---|---|
createConfig(configData) | Creates a new configuration entry | configData: ConfigData | Promise<string> |
getConfig(key) | Retrieves a configuration by key | key: string | Promise<ConfigData> |
updateConfig(key, configData) | Updates an existing configuration | key: string, configData: ConfigData | Promise<void> |
deleteConfig(key) | Deletes a configuration entry | key: string | Promise<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:
-
Use debug mode only when necessary
// Enable only during development
const headset = new Headset('192.168.1.100', { debug: true }); -
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'],
}
} -
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');
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.