@anthriq_dev/bxi-interface
v1.1.2
Published
Node.js SDK for BXI Interface - communicates with C++ bridge via ZMQ
Maintainers
Readme
BXI SDK Usage Guide
Complete guide to using the BXI SDK for Anthriq Instinct device operations.
Table of Contents
- Getting Started
- System Operations
- Register Operations
- Motor Operations
- EEG Streaming
- Impedance Streaming
- Error Handling
- Best Practices
Getting Started
Installation
npm install @anthriq_dev/bxi-interfaceBasic Setup
import { BxiClient } from "@anthriq_dev/bxi-interface";
// Create a typed client for Anthriq Instinct
const client = new BxiClient<"anthriq-instinct">({
debug: false,
deviceType: "anthriq-instinct",
timeout: 30000
});
// Initialize the client
await client.initialize("localhost"); // or "192.168.1.100" for remote devices
// Connect to the device (required after initialization)
await client.connect();
// Your device is now ready to use!Cleanup
// Always disconnect and shutdown when done
await client.disconnect();
await client.shutdown();System Operations
Get Capabilities
Retrieve device capabilities and available features.
const response = await client.features?.executeOperation("system", "get_capabilities", {});
if (response?.success) {
const capabilities = response.data;
console.log("Available features:", capabilities.features);
console.log("Register types:", capabilities.registers);
}Response Structure:
{
features: {
registers: [...],
motors: {...},
eeg: {...},
impedance: {...}
},
registers: {
synap: [...],
nerv: [...],
motor: [...],
// ... other register types
}
}Get State
Get the current device state.
const response = await client.features?.executeOperation("system", "get_state", {});
if (response?.success) {
const state = response.data;
console.log("Status Message:", state.statusMessage);
console.log("Details:", state.details); // Only present when connected
console.log("Last Update:", state.lastUpdate);
}Response Structure:
{
statusMessage: string,
details?: Record<string, string>, // Only when connected and not in error
lastUpdate?: string
}Subscribe to State Changes
Monitor device state changes in real-time.
const subscriptionResult = await client.features?.subscribeToOperation(
"system",
"get_state",
(update) => {
if (update.success) {
const state = update.data;
console.log("Status:", state.statusMessage);
console.log("Details:", state.details);
// Handle state changes based on status message or details
if (state.statusMessage?.toLowerCase().includes("error")) {
console.error("Device error:", state.statusMessage);
}
}
}
);
if (subscriptionResult?.success) {
const subscriptionId = subscriptionResult.data.subscriptionId;
console.log("Subscribed with ID:", subscriptionId);
// Wait for state updates
await new Promise(resolve => setTimeout(resolve, 3000));
// Later, unsubscribe (use same feature and operation as subscribe)
await client.features?.unsubscribeFromOperation("system", "get_state", {
subscriptionId
});
}Connect / Disconnect
Explicitly connect or disconnect from the device.
// Connect (required after initialize)
await client.connect();
// Disconnect (optional, called automatically in shutdown)
await client.disconnect();Register Operations
Registers are device configuration registers that control various aspects of the device. All register operations use the "registers" feature with a type field in the payload.
Register Types
synap- Synap register (channel configuration)nerv- Nerv register (ADC configuration)motor- Motor register (motor configuration)cms_drl- CMS/DRL register (common mode sense/driven right leg)features- Features register (system features)definition- Definition register (system definition)routing- Routing register (signal routing)bucket- Bucket register (data bucket configuration)
Read Register
Read a single register.
// Read synap register
const response = await client.features?.executeOperation("registers", "read", {
type: "synap",
synap_id: 0
});
if (response?.success) {
const data = response.data.fields;
console.log("Synap 0 enabled:", data.enabled);
console.log("Synap type:", data.synap_type);
console.log("Gain stage 1:", data.gain_stage_1);
console.log("Gain stage 2:", data.gain_stage_2);
console.log("LPF setting:", data.lpf_setting);
}Synap Register Fields:
{
synap_id: number; // Read-only
enabled: number; // 0 = Disabled, 1 = Enabled
synap_type: number; // 0 = Normal, 1 = REF
ref_synap_id: number;
connected_nerv_id: number;
connected_nerv_channel_id: number;
gain_stage_1: number; // 0 = 20G, 1 = 10G, 2 = 5G, 3 = 2G
gain_stage_2: number; // 0 = 5G, 1 = 10G, 2 = 50G, 3 = 100G
impedance_monitoring_enabled: number;
lpf_setting: number; // 0 = 700Hz, 1 = 300Hz, 2 = 100Hz, 3 = 40Hz
cms_drl_id: number;
cms_contribution_ch: number;
motor_associated: number;
associated_motor_id: number;
}Nerv Register Fields:
{
nerv_id: number; // Read-only
enabled: number; // 0 = Disabled, 1 = Enabled
osr_for_exg: number; // 0-31
osr_for_impedance: number; // 0-31
}Motor Register Fields:
{
motor_id: number; // Read-only
enabled: number; // 0 = Disabled, 1 = Enabled
current_position: number; // Read-only, 0-20
displacement: number; // 0-20
is_motor_group: number;
calibrate: number; // 0-7
operation: number; // 0 = Stop, 1 = Forward, 2 = Backward, 3 = Brake
status: number; // Read-only
stall_switch: number;
}CMS/DRL Register Fields:
{
cms_drl_id: number; // Read-only
enabled: number; // 0 = Disabled, 1 = Enabled
connected_nerv: number;
cms_mon_ch: number;
drl_mon_ch: number;
drl_impedance_monitoring_enabled: number;
cms_input_map: number; // 24-bit value
cms_pga: number; // 0 = -1G, 1 = -2G, 2 = -4G, 3 = -8G, 4 = -16G, 5 = -32G, 6 = -64G
}Write Register
Write to a register (partial writes supported).
// Enable synap register
const response = await client.features?.executeOperation("registers", "write", {
type: "synap",
synap_id: 0,
fields: {
enabled: 1,
gain_stage_1: 0, // 20G
gain_stage_2: 2, // 50G
lpf_setting: 1 // 300Hz
}
});
if (response?.success) {
console.log("Register written successfully");
// Verify by reading back
await new Promise(resolve => setTimeout(resolve, 200)); // Wait for propagation
const verifyResponse = await client.features?.executeOperation("registers", "read", {
type: "synap",
synap_id: 0
});
if (verifyResponse?.success) {
const data = verifyResponse.data.fields;
console.log("Verified - enabled:", data.enabled);
}
}Batch Register Operations
Read or write multiple registers in a single operation.
// Batch read
const batchReadResponse = await client.features?.executeOperation("registers", "read", {
commands: [
{ type: "synap", synap_id: 0 },
{ type: "synap", synap_id: 1 },
{ type: "motor", motor_id: 0 },
{ type: "nerv", nerv_id: 0 }
]
});
if (batchReadResponse?.success) {
const results = batchReadResponse.data.results;
results.forEach((result: any) => {
console.log(`Register ${result.type} ${result.id}:`, result.fields);
});
}
// Batch write
const batchWriteResponse = await client.features?.executeOperation("registers", "write", {
commands: [
{
type: "synap",
synap_id: 0,
fields: { enabled: 1 }
},
{
type: "motor",
motor_id: 0,
fields: { enabled: 1 }
}
]
});
if (batchWriteResponse?.success) {
console.log("Batch write successful");
}Motor Operations
Motor operations control the physical motors on the device.
Read Motor
Read motor status and position.
const response = await client.features?.executeOperation("motors", "read", {
motor_ids: [0] // Array of motor IDs
});
if (response?.success) {
const results = response.data.results;
results.forEach((motor: any) => {
console.log(`Motor ${motor.motor_id}:`);
console.log(" Enabled:", motor.enabled);
console.log(" Position:", motor.current_position);
});
}Response Structure:
{
results: [
{
motor_id: number;
enabled: number; // 0 = Disabled, 1 = Enabled
current_position: number; // 0-20
}
]
}Move Motor
Move a motor forward or backward.
// Move forward
const moveResponse = await client.features?.executeOperation("motors", "move", {
motor_id: 0,
displacement: 5, // Steps to move (0-20)
operation: "forward" // or "backward"
});
if (moveResponse?.success) {
const motorData = moveResponse.data;
console.log(`Motor ${motorData.motor_id} moved to position:`, motorData.current_position);
console.log("Status:", motorData.status);
// Verify position after a delay
await new Promise(resolve => setTimeout(resolve, 500));
const readResponse = await client.features?.executeOperation("motors", "read", {
motor_ids: [0]
});
if (readResponse?.success) {
const actualPosition = readResponse.data.results[0].current_position;
console.log("Verified position:", actualPosition);
}
}Important Notes:
- Motor position is clamped between 0 and 20
- Moving forward at position 20 or backward at position 0 will not change position
- Always verify position after movement for consistency
Stop Motor
Stop a motor immediately.
const stopResponse = await client.features?.executeOperation("motors", "stop", {
motor_id: 0
});
if (stopResponse?.success) {
const motorData = stopResponse.data;
console.log(`Motor ${motorData.motor_id} stopped at position:`, motorData.current_position);
console.log("Status:", motorData.status);
// Verify position doesn't change after stop
await new Promise(resolve => setTimeout(resolve, 500));
const readResponse = await client.features?.executeOperation("motors", "read", {
motor_ids: [0]
});
if (readResponse?.success) {
const positionAfter = readResponse.data.results[0].current_position;
if (motorData.current_position === positionAfter) {
console.log("Position stable after stop");
}
}
}Brake Motor
Apply brake to a motor.
const brakeResponse = await client.features?.executeOperation("motors", "brake", {
motor_id: 0
});
if (brakeResponse?.success) {
const motorData = brakeResponse.data;
console.log(`Motor ${motorData.motor_id} brake applied at position:`, motorData.current_position);
console.log("Status:", motorData.status);
}EEG Streaming
EEG streaming provides real-time electroencephalography data from the device.
Stream Lifecycle
- Add Stream - Configure a stream
- Subscribe - Subscribe to receive data
- Start Stream - Begin data transmission
- Stop Stream - Stop data transmission
- Unsubscribe - Stop receiving data
- Remove Stream - Remove stream configuration
Add Stream
Configure an EEG stream.
const streamId = 20;
const port = 9020;
const addStreamResponse = await client.features?.executeOperation("eeg", "add_stream", {
stream: {
stream_id: streamId,
protocol: "websocket",
host: "127.0.0.1",
port: port,
elements_before_flush: 30 // Samples per batch (for 1kHz: 30 samples = 30ms)
}
});
if (addStreamResponse?.success) {
console.log("Stream added:", addStreamResponse.data.stream_id);
}
// Wait for WebSocket server to be ready
await new Promise(resolve => setTimeout(resolve, 1000));Stream Configuration:
stream_id: Unique identifier for the stream (e.g., 20)protocol:"websocket"or"ws"host: Host address (usually"127.0.0.1"for local)port: Port number for the stream server (e.g., 9020)elements_before_flush: Number of samples per batch (30 for 1kHz EEG)
Note: source_id is automatically set by the SDK based on the feature type:
0x2100for EEG streams0x2200for impedance streams
You should not provide source_id in the stream configuration.
Subscribe to Stream
Subscribe to receive EEG data.
const streamId = 20;
let eegDataReceived = 0;
const subscriptionResult = await client.features?.subscribeToOperation(
"eeg",
"stream",
(update) => {
if (update.success) {
eegDataReceived++;
const streamData = update.data;
// Stream data structure
if (streamData.frames?.[0]?.channels) {
const channels = streamData.frames[0].channels;
// Log first few updates
if (eegDataReceived <= 3) {
console.log(`EEG update #${eegDataReceived}: ${channels.length} channels`);
}
channels.forEach((channel: any) => {
console.log(`Channel ${channel.channel_id}:`);
console.log(" Channel ID:", channel.channel_id);
console.log(" ADC Data:", channel.adc_data);
console.log(" Has Error:", channel.has_error);
if (channel.has_error) {
console.log(" Error Info:", channel.error_info);
}
});
}
}
},
{
stream_id: streamId
}
);
if (subscriptionResult?.success) {
const subscriptionId = subscriptionResult.data.subscriptionId;
console.log("Subscribed with ID:", subscriptionId);
// Store subscriptionId for later unsubscribe
const eegSubscriptionId = subscriptionId;
// Wait for WebSocket client to connect
await new Promise(resolve => setTimeout(resolve, 2000));
}Stream Data Structure:
{
stream_id: number;
frames: [
{
channels: [
{
channel_id: number; // Absolute channel ID (combined from adc_id and channel_id_local)
adc_data: number; // 24-bit ADC data (signed integer)
has_error: boolean;
error_info: number; // Error info (8 bits)
}
]
}
]
}Start Stream
Begin data transmission.
const streamId = 20;
const startResponse = await client.features?.executeOperation("eeg", "start_stream", {
stream_id: streamId
});
if (startResponse?.success) {
console.log("Stream started");
// Wait for some data
console.log("Collecting EEG data (3 seconds)...");
await new Promise(resolve => setTimeout(resolve, 3000));
console.log(`Received ${eegDataReceived} EEG data packets`);
}Stop Stream
Stop data transmission.
const streamId = 20;
const dataCountBeforeStop = eegDataReceived;
const stopResponse = await client.features?.executeOperation("eeg", "stop_stream", {
stream_id: streamId
});
if (stopResponse?.success) {
console.log("Stream stopped");
}
// Wait a bit - some buffered packets may still arrive
// At 1kHz (30 samples/batch), we get ~33 batches/second
await new Promise(resolve => setTimeout(resolve, 1500));
// Verify no new data is received
const totalPacketsAfterStop = eegDataReceived - dataCountBeforeStop;
if (totalPacketsAfterStop === 0) {
console.log("EEG stop_stream: Verified - no new data after stop");
} else if (totalPacketsAfterStop <= 60) {
console.log(`EEG stop_stream: Received ${totalPacketsAfterStop} buffered packet(s) after stop (expected for 1kHz stream)`);
}Unsubscribe
Stop receiving stream data. Use the same feature and operation name as subscribe.
const eegSubscriptionId = subscriptionResult.data.subscriptionId;
// Use unsubscribeFromOperation with same feature and operation as subscribe
const unsubscribeResult = await client.features?.unsubscribeFromOperation("eeg", "stream", {
subscriptionId: eegSubscriptionId
});
if (unsubscribeResult?.success) {
console.log("Successfully unsubscribed");
}Remove Stream
Remove stream configuration.
const streamId = 20;
const removeResponse = await client.features?.executeOperation("eeg", "remove_stream", {
stream_id: streamId
});
if (removeResponse?.success) {
console.log("Stream removed");
}Complete EEG Streaming Example
async function streamEEG() {
const streamId = 20;
const port = 9020;
let eegDataReceived = 0;
let eegSubscriptionId: string | null = null;
// 1. Add stream
const addStreamResult = await client.features?.executeOperation("eeg", "add_stream", {
stream: {
stream_id: streamId,
protocol: "websocket",
host: "127.0.0.1",
port: port,
elements_before_flush: 30
}
});
if (!addStreamResult?.success) {
throw new Error("Failed to add stream");
}
// 2. Wait for server to be ready
await new Promise(resolve => setTimeout(resolve, 1000));
// 3. Subscribe
const subscriptionResult = await client.features?.subscribeToOperation(
"eeg",
"stream",
(update) => {
if (update.success) {
eegDataReceived++;
const data = update.data;
if (data.frames?.[0]?.channels && eegDataReceived <= 3) {
const channels = data.frames[0].channels;
console.log(`EEG update #${eegDataReceived}: ${channels.length} channels`);
}
}
},
{ stream_id: streamId }
);
if (subscriptionResult?.success) {
eegSubscriptionId = subscriptionResult.data.subscriptionId;
}
// Wait for WebSocket client to connect
await new Promise(resolve => setTimeout(resolve, 2000));
// 4. Start stream
await client.features?.executeOperation("eeg", "start_stream", {
stream_id: streamId
});
// 5. Collect data for 10 seconds
console.log("Collecting EEG data (10 seconds)...");
await new Promise(resolve => setTimeout(resolve, 10000));
console.log(`Received ${eegDataReceived} EEG data packets`);
// 6. Stop stream
const dataCountBeforeStop = eegDataReceived;
await client.features?.executeOperation("eeg", "stop_stream", {
stream_id: streamId
});
// Wait for buffered packets
await new Promise(resolve => setTimeout(resolve, 1500));
const totalPacketsAfterStop = eegDataReceived - dataCountBeforeStop;
console.log(`Received ${totalPacketsAfterStop} buffered packet(s) after stop`);
// 7. Unsubscribe (use same feature and operation as subscribe)
if (eegSubscriptionId) {
await client.features?.unsubscribeFromOperation("eeg", "stream", {
subscriptionId: eegSubscriptionId
});
}
// 8. Remove stream
await client.features?.executeOperation("eeg", "remove_stream", {
stream_id: streamId
});
console.log(`Total packets received: ${eegDataReceived}`);
}Impedance Streaming
Impedance streaming provides real-time impedance measurements from the device.
Add Impedance Stream
const impedanceStreamId = 21;
const impedancePort = 9021;
const addStreamResponse = await client.features?.executeOperation("impedance", "add_stream", {
stream: {
stream_id: impedanceStreamId,
protocol: "websocket",
host: "127.0.0.1",
port: impedancePort,
elements_before_flush: 25 // Samples per batch (for 250Hz: 25 samples = 100ms)
}
});
if (addStreamResponse?.success) {
console.log("Impedance stream added");
}
// Wait for WebSocket server to be ready
await new Promise(resolve => setTimeout(resolve, 1000));Impedance Stream Configuration:
stream_id: Unique identifier for the stream (e.g., 21)port: Port number for the stream server (e.g., 9021)elements_before_flush: 25 for 250Hz sampling rate
Note: source_id is automatically set to "0x2200" for impedance streams by the SDK.
Subscribe to Impedance Stream
const impedanceStreamId = 21;
let impedanceDataReceived = 0;
const subscriptionResult = await client.features?.subscribeToOperation(
"impedance",
"stream",
(update) => {
if (update.success) {
impedanceDataReceived++;
const streamData = update.data;
if (streamData.frames?.[0]?.channels) {
const channels = streamData.frames[0].channels;
// Log first few updates
if (impedanceDataReceived <= 3) {
console.log(`Impedance update #${impedanceDataReceived}: ${channels.length} channels`);
}
channels.forEach((channel: any) => {
console.log(`Channel ${channel.channel_id}:`);
console.log(" ADC Data:", channel.adc_data);
console.log(" Has Error:", channel.has_error);
if (channel.has_error) {
console.log(" Error Info:", channel.error_info);
}
});
}
}
},
{
stream_id: impedanceStreamId
}
);
if (subscriptionResult?.success) {
const subscriptionId = subscriptionResult.data.subscriptionId;
console.log("Subscribed with ID:", subscriptionId);
// Wait for WebSocket client to connect
await new Promise(resolve => setTimeout(resolve, 2000));
}Impedance Data Structure:
{
stream_id: number;
frames: [
{
channels: [
{
channel_id: number; // Absolute channel ID (combined from adc_id and channel_id_local)
adc_data: number; // 24-bit ADC data (signed integer)
has_error: boolean;
error_info: number; // Error info (8 bits)
}
]
}
]
}Impedance Stream Operations
All operations are identical to EEG streaming:
const impedanceStreamId = 21;
const impedanceSubscriptionId = subscriptionResult.data.subscriptionId;
// Start
await client.features?.executeOperation("impedance", "start_stream", {
stream_id: impedanceStreamId
});
// Collect data
console.log("Collecting Impedance data (3 seconds)...");
await new Promise(resolve => setTimeout(resolve, 3000));
console.log(`Received ${impedanceDataReceived} Impedance data packets`);
// Stop
const dataCountBeforeStop = impedanceDataReceived;
await client.features?.executeOperation("impedance", "stop_stream", {
stream_id: impedanceStreamId
});
// Wait for buffered packets (at 250Hz, we get ~10 batches/second)
await new Promise(resolve => setTimeout(resolve, 2000));
const totalPacketsAfterStop = impedanceDataReceived - dataCountBeforeStop;
if (totalPacketsAfterStop <= 15) {
console.log(`Impedance stop_stream: Received ${totalPacketsAfterStop} buffered packet(s) after stop (expected for 250Hz stream)`);
}
// Unsubscribe (use same feature and operation as subscribe)
await client.features?.unsubscribeFromOperation("impedance", "stream", {
subscriptionId: impedanceSubscriptionId
});
// Remove
await client.features?.executeOperation("impedance", "remove_stream", {
stream_id: impedanceStreamId
});Error Handling
Response Structure
All operations return a SdkResponse:
{
success: boolean;
data?: T; // Response data (type depends on operation)
error?: {
code: string;
message: string;
details?: string;
};
}Error Handling Pattern
const response = await client.features?.executeOperation("motors", "read", {
motor_ids: [0]
});
if (!response?.success) {
const error = response.error;
console.error("Operation failed:");
console.error(" Code:", error?.code);
console.error(" Message:", error?.message);
console.error(" Details:", error?.details);
// Handle specific error codes
if (error?.code === "0x01") {
console.error("Device not connected");
}
} else {
// Success - use response.data
const motorData = response.data;
}Common Error Codes
0x01: Device not connected0x02: Invalid operation0x03: Invalid payload0x04: Operation timeout0x05: Stream not found0x06: Subscription not found
Timeout Handling
// Set timeout for operations
const client = new BxiClient<"anthriq-instinct">({
timeout: 30000 // 30 seconds
});
// Operations will automatically timeout based on the client timeout setting
const streamId = 20;
try {
const response = await client.features?.executeOperation("eeg", "start_stream", {
stream_id: streamId
});
if (response?.success) {
// Handle success
} else {
// Handle error
console.error("Operation failed:", response.error);
}
} catch (error) {
console.error("Stream operation error:", error);
}Best Practices
1. Always Initialize and Connect
// ✅ Good
const client = new BxiClient<"anthriq-instinct">();
await client.initialize("localhost");
await client.connect();
// ❌ Bad - missing connect()
await client.initialize("localhost");
// Operations will fail with "device not connected"2. Clean Up Resources
try {
// Use client
} finally {
// Always cleanup
if (client?.isConnected()) {
await client.disconnect();
}
await client.shutdown();
}3. Handle Stream Lifecycle Properly
// ✅ Good - complete lifecycle
await addStream();
await subscribe();
await startStream();
// ... collect data ...
await stopStream();
await unsubscribe();
await removeStream();
// ❌ Bad - missing cleanup
await addStream();
await subscribe();
await startStream();
// Stream remains active!4. Verify Register Writes
// Write register
await client.features?.executeOperation("registers", "write", {
type: "synap",
synap_id: 0,
fields: { enabled: 1 }
});
// Wait for propagation
await new Promise(resolve => setTimeout(resolve, 200));
// Verify
const readResponse = await client.features?.executeOperation("registers", "read", {
type: "synap",
synap_id: 0
});
if (readResponse?.success) {
const enabled = readResponse.data.fields.enabled;
if (enabled !== 1) {
throw new Error("Write verification failed");
}
}5. Monitor Motor Position Consistency
// Read position before move
const positionBefore = await readMotorPosition(0);
// Move motor
await client.features?.executeOperation("motors", "move", {
motor_id: 0,
displacement: 5,
operation: "forward"
});
// Wait for position update
await new Promise(resolve => setTimeout(resolve, 500));
// Verify position
const positionAfter = await readMotorPosition(0);
const expectedPosition = Math.min(20, positionBefore + 5); // Clamp to max
if (Math.abs(positionAfter - expectedPosition) > 1) {
console.warn("Position inconsistency detected");
}6. Handle Stream Buffering
// After stopping a stream, some buffered packets may still arrive
await client.features?.executeOperation("eeg", "stop_stream", {
stream_id: 3
});
// Wait for buffered packets to clear
await new Promise(resolve => setTimeout(resolve, 1500));
// Monitor subscription callback - packets should stop arriving7. Use TypeScript Types
// ✅ Good - typed client
const client = new BxiClient<"anthriq-instinct">();
// TypeScript knows all payload and response types
// ❌ Bad - untyped
const client = new BxiClient();
// No type safety8. Subscribe to State Changes
// Monitor device state for errors
const stateSubscription = await client.features?.subscribeToOperation(
"system",
"get_state",
(update) => {
if (update.success && update.data.statusMessage?.toLowerCase().includes("error")) {
console.error("Device error:", update.data.statusMessage);
// Handle error (reconnect, retry, etc.)
}
}
);
// Later, unsubscribe (use same feature and operation as subscribe)
if (stateSubscription?.success) {
await client.features?.unsubscribeFromOperation("system", "get_state", {
subscriptionId: stateSubscription.data.subscriptionId
});
}9. Parallel Stream Operations
// When running multiple streams, use Promise.allSettled for resilience
const addStreamPromises = streams.map(stream =>
client.features?.executeOperation("eeg", "add_stream", {
stream: { ...stream }
})
);
const results = await Promise.allSettled(addStreamPromises);
const successful = results.filter(r => r.status === 'fulfilled' && r.value?.success);
console.log(`${successful.length}/${streams.length} streams added`);10. Error Recovery
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error("Max retries exceeded");
}
// Usage
const response = await retryOperation(() =>
client.features?.executeOperation("motors", "read", {
motor_ids: [0]
})
);Complete Example
import { BxiClient } from "@anthriq_dev/bxi-interface";
async function completeExample() {
const client = new BxiClient<"anthriq-instinct">({
debug: false,
deviceType: "anthriq-instinct",
timeout: 30000
});
try {
// Initialize and connect
await client.initialize("localhost");
await client.connect();
console.log("✅ Connected");
// Get capabilities
const capabilities = await client.features?.executeOperation("system", "get_capabilities", {});
console.log("Capabilities:", capabilities?.data);
// Read a register
const synapRead = await client.features?.executeOperation("registers", "read", {
type: "synap",
synap_id: 0
});
console.log("Synap 0:", synapRead?.data.fields);
// Write a register
await client.features?.executeOperation("registers", "write", {
type: "synap",
synap_id: 0,
fields: { enabled: 1 }
});
// Read motor position
const motorRead = await client.features?.executeOperation("motors", "read", {
motor_ids: [0]
});
console.log("Motor position:", motorRead?.data.results[0].current_position);
// Move motor
await client.features?.executeOperation("motors", "move", {
motor_id: 0,
displacement: 5,
operation: "forward"
});
// Setup EEG stream
const streamId = 20;
await client.features?.executeOperation("eeg", "add_stream", {
stream: {
stream_id: streamId,
protocol: "websocket",
host: "127.0.0.1",
port: 9020,
elements_before_flush: 30
}
});
// Wait for server to be ready
await new Promise(resolve => setTimeout(resolve, 1000));
// Subscribe to stream
const subscription = await client.features?.subscribeToOperation(
"eeg",
"stream",
(update) => {
if (update.success) {
console.log("EEG data received");
}
},
{ stream_id: streamId }
);
// Wait for WebSocket client to connect
await new Promise(resolve => setTimeout(resolve, 2000));
// Start stream
await client.features?.executeOperation("eeg", "start_stream", {
stream_id: streamId
});
// Collect data for 5 seconds
await new Promise(resolve => setTimeout(resolve, 5000));
// Stop and cleanup
await client.features?.executeOperation("eeg", "stop_stream", {
stream_id: streamId
});
// Wait for buffered packets
await new Promise(resolve => setTimeout(resolve, 1500));
// Unsubscribe (use same feature and operation as subscribe)
await client.features?.unsubscribeFromOperation("eeg", "stream", {
subscriptionId: subscription?.data.subscriptionId
});
await client.features?.executeOperation("eeg", "remove_stream", {
stream_id: streamId
});
} catch (error) {
console.error("Error:", error);
} finally {
// Always cleanup
if (client.isConnected()) {
await client.disconnect();
}
await client.shutdown();
}
}
completeExample();