Node.js implementation of the Arduino Firmata protocol for controlling Arduino boards from JavaScript applications
Custom system exclusive messaging for extended Arduino functionality beyond standard Firmata operations, enabling custom protocols and advanced device communication.
Send custom SysEx messages to Arduino for extended functionality and custom protocols.
/**
* Send SysEx (System Exclusive) message to Arduino
* Enables custom protocols and extended functionality beyond standard Firmata
* @param command - SysEx command byte (0-127, automatically masked to 7-bit)
* @param data - Optional array of data bytes (each automatically masked to 7-bit)
* @param callback - Optional callback function called when message is sent
*/
sysex(command: number, data?: number[], callback?: Function): void;Parameters:
Usage Examples:
// Send simple command without data
arduino.sysex(0x01);
// Send command with data array
arduino.sysex(0x01, [13, 5, 2]); // Command 0x01 with three data bytes
// With callback
arduino.sysex(0x01, [13, 5, 2], function() {
console.log('SysEx message sent');
});
// Send empty data array explicitly
arduino.sysex(0x05, [], function() {
console.log('Command 0x05 sent without data');
});Handle incoming SysEx messages from Arduino for bidirectional custom communication.
// Event: sysex
// Fired when a SysEx message is received from Arduino
// Event object properties:
interface SysexEvent {
command: number; // SysEx command byte (0-127)
data: number[]; // Array of data bytes received
}Usage Example:
arduino.on('sysex', function(event) {
console.log(`SysEx received - Command: 0x${event.command.toString(16)}`);
console.log('Data bytes:', event.data);
// Handle specific commands
switch(event.command) {
case 0x01:
console.log('LED blink command response received');
break;
case 0x02:
if (event.data.length >= 2) {
const sensorId = event.data[0];
const sensorValue = event.data[1];
console.log(`Sensor ${sensorId} reading: ${sensorValue}`);
}
break;
default:
console.log('Unknown SysEx command:', event.command);
}
});Complete example showing both sending and receiving SysEx messages.
const ArduinoFirmata = require('arduino-firmata');
const arduino = new ArduinoFirmata();
arduino.connect();
arduino.on('connect', function() {
console.log('Arduino connected - SysEx communication ready');
// Set up SysEx message handler
arduino.on('sysex', function(event) {
console.log(`Received SysEx command: 0x${event.command.toString(16)}`);
console.log('Data:', event.data);
// Echo protocol - respond to command 0x10
if (event.command === 0x10) {
arduino.sysex(0x11, event.data); // Echo back with command 0x11
console.log('Echoed SysEx message');
}
});
// Send periodic SysEx commands
setInterval(function() {
arduino.sysex(0x01, [13, 3, 10]); // LED blink: pin 13, 3 times, 1000ms interval
}, 5000);
});Implement a custom sensor reading protocol using SysEx.
class CustomSensorProtocol {
constructor(arduino) {
this.arduino = arduino;
this.sensorReadings = {};
// Set up SysEx handler for sensor responses
this.arduino.on('sysex', (event) => {
this.handleSysexMessage(event);
});
}
// Request sensor reading
requestSensor(sensorId) {
console.log(`Requesting reading from sensor ${sensorId}`);
this.arduino.sysex(0x20, [sensorId]); // Command 0x20 = read sensor
}
// Request all sensors
requestAllSensors() {
console.log('Requesting all sensor readings');
this.arduino.sysex(0x21); // Command 0x21 = read all sensors
}
// Handle incoming SysEx messages
handleSysexMessage(event) {
switch(event.command) {
case 0x30: // Sensor reading response
if (event.data.length >= 3) {
const sensorId = event.data[0];
const valueHigh = event.data[1];
const valueLow = event.data[2];
const value = (valueHigh << 7) | valueLow; // Reconstruct 14-bit value
this.sensorReadings[sensorId] = value;
console.log(`Sensor ${sensorId}: ${value}`);
}
break;
case 0x31: // All sensors response
console.log('All sensors data received');
for (let i = 0; i < event.data.length; i += 3) {
if (i + 2 < event.data.length) {
const sensorId = event.data[i];
const valueHigh = event.data[i + 1];
const valueLow = event.data[i + 2];
const value = (valueHigh << 7) | valueLow;
this.sensorReadings[sensorId] = value;
}
}
break;
}
}
// Get cached sensor reading
getSensorReading(sensorId) {
return this.sensorReadings[sensorId] || null;
}
}
// Usage
arduino.on('connect', function() {
const sensorProtocol = new CustomSensorProtocol(arduino);
// Request individual sensor
sensorProtocol.requestSensor(1);
// Request all sensors periodically
setInterval(() => {
sensorProtocol.requestAllSensors();
}, 2000);
});Example implementation matching the sample code from the repository.
// LED blink control using SysEx (matches StandardFirmataWithLedBlink)
arduino.on('connect', function() {
console.log('LED blink controller ready');
// Blink LED on pin 13: 5 times with 200ms intervals
arduino.sysex(0x01, [13, 5, 2]); // pin, count, interval (200ms = 2 * 100ms)
// Blink LED on pin 12: 3 times with 1000ms intervals
arduino.sysex(0x01, [12, 3, 10]); // pin, count, interval (1000ms = 10 * 100ms)
// Handle blink completion notifications
arduino.on('sysex', function(event) {
if (event.command === 0x01) {
console.log('LED blink sequence completed');
console.log('Response data:', event.data);
}
});
});SysEx messages use 7-bit data encoding, requiring special handling for larger values.
// Helper functions for encoding/decoding multi-byte values
function encode14bit(value) {
// Split 14-bit value into two 7-bit bytes
const high = (value >> 7) & 0x7F;
const low = value & 0x7F;
return [high, low];
}
function decode14bit(high, low) {
// Reconstruct 14-bit value from two 7-bit bytes
return (high << 7) | low;
}
// Send 14-bit sensor threshold value
const threshold = 5000;
const [thresholdHigh, thresholdLow] = encode14bit(threshold);
arduino.sysex(0x40, [1, thresholdHigh, thresholdLow]); // Sensor 1 threshold
// Receive and decode 14-bit values
arduino.on('sysex', function(event) {
if (event.command === 0x41 && event.data.length >= 3) {
const sensorId = event.data[0];
const value = decode14bit(event.data[1], event.data[2]);
console.log(`14-bit sensor ${sensorId} reading: ${value}`);
}
});When implementing custom SysEx protocols, document the command structure:
/*
* Custom SysEx Protocol Documentation
*
* Command 0x01 - LED Blink Control
* Data: [pin, count, interval]
* - pin: LED pin number (0-13)
* - count: Number of blinks (1-127)
* - interval: Interval in 100ms units (1-127)
* Response: Command 0x01 with completion status
*
* Command 0x20 - Request Sensor Reading
* Data: [sensor_id]
* - sensor_id: Sensor identifier (0-127)
* Response: Command 0x30 with sensor data
*
* Command 0x30 - Sensor Reading Response
* Data: [sensor_id, value_high, value_low]
* - sensor_id: Sensor identifier (0-127)
* - value_high: Upper 7 bits of 14-bit value
* - value_low: Lower 7 bits of 14-bit value
*/
const SYSEX_COMMANDS = {
LED_BLINK: 0x01,
REQUEST_SENSOR: 0x20,
SENSOR_RESPONSE: 0x30,
SET_CONFIG: 0x40,
GET_STATUS: 0x50
};
// Use named constants for better code maintenance
arduino.sysex(SYSEX_COMMANDS.LED_BLINK, [13, 5, 2]);// SysEx protocol constants
ArduinoFirmata.START_SYSEX = 0xF0; // Start of SysEx message
ArduinoFirmata.END_SYSEX = 0xF7; // End of SysEx messageSysEx messages follow the MIDI SysEx format:
0xF0 (START_SYSEX)0xF7 (END_SYSEX)value & 0x7FTo use SysEx messaging, your Arduino must be running firmware that handles custom SysEx commands:
// Example Arduino SysEx handler (C++ code for Arduino)
void sysexCallback(byte command, byte argc, byte* argv) {
switch(command) {
case 0x01: // LED blink
if (argc >= 3) {
int pin = argv[0];
int count = argv[1];
int interval = argv[2] * 100; // Convert to milliseconds
blinkLED(pin, count, interval);
}
break;
}
}// Batch SysEx messages to avoid flooding the Arduino
const messageQueue = [];
let sending = false;
function queueSysex(command, data, callback) {
messageQueue.push({ command, data, callback });
processSysexQueue();
}
function processSysexQueue() {
if (sending || messageQueue.length === 0) return;
sending = true;
const { command, data, callback } = messageQueue.shift();
arduino.sysex(command, data, function() {
sending = false;
if (callback) callback();
setTimeout(processSysexQueue, 10); // Small delay between messages
});
}// Implement timeout for SysEx responses
function sysexWithTimeout(command, data, timeout = 5000) {
return new Promise((resolve, reject) => {
let responseReceived = false;
const timeoutId = setTimeout(() => {
if (!responseReceived) {
reject(new Error(`SysEx command 0x${command.toString(16)} timed out`));
}
}, timeout);
const responseHandler = (event) => {
if (event.command === command + 1) { // Assuming response is command + 1
responseReceived = true;
clearTimeout(timeoutId);
arduino.removeListener('sysex', responseHandler);
resolve(event.data);
}
};
arduino.on('sysex', responseHandler);
arduino.sysex(command, data);
});
}
// Usage with async/await
try {
const response = await sysexWithTimeout(0x20, [1]);
console.log('Sensor response:', response);
} catch (error) {
console.error('SysEx error:', error.message);
}Install with Tessl CLI
npx tessl i tessl/npm-arduino-firmata