CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-arduino-firmata

Node.js implementation of the Arduino Firmata protocol for controlling Arduino boards from JavaScript applications

Overview
Eval results
Files

sysex.mddocs/

SysEx Messaging

Custom system exclusive messaging for extended Arduino functionality beyond standard Firmata operations, enabling custom protocols and advanced device communication.

Capabilities

SysEx Message Transmission

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:

  • command: Custom command byte (0-127)
  • data: Array of data bytes (each 0-127, automatically masked to 7-bit values)
  • callback: Optional function called after message transmission

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');
});

SysEx Message Reception

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);
  }
});

Bidirectional SysEx Communication

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);
});

Custom Protocol Example

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);
});

LED Control via SysEx

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);
    }
  });
});

Data Encoding for SysEx

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}`);
  }
});

Protocol Documentation Example

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]);

Constants

// SysEx protocol constants
ArduinoFirmata.START_SYSEX = 0xF0; // Start of SysEx message
ArduinoFirmata.END_SYSEX = 0xF7;   // End of SysEx message

SysEx Protocol Specifications

Message Format

SysEx messages follow the MIDI SysEx format:

  • Start byte: 0xF0 (START_SYSEX)
  • Command byte: Custom command (0-127, 7-bit)
  • Data bytes: Optional data (each 0-127, 7-bit)
  • End byte: 0xF7 (END_SYSEX)

Data Constraints

  • All data bytes are automatically masked to 7 bits (0-127)
  • Values above 127 are truncated: value & 0x7F
  • For values > 127, use multi-byte encoding (see encoding examples above)

Arduino-Side Implementation

To 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;
  }
}

Use Cases

Custom Sensors

  • Multi-sensor data collection with timestamps
  • Calibration parameter transmission
  • Sensor configuration and threshold settings

Advanced Motor Control

  • Stepper motor control with acceleration profiles
  • Multi-axis coordination commands
  • Complex motion sequences

Communication Protocols

  • I2C device configuration via Arduino
  • SPI device control and data transfer
  • Custom serial protocol bridging

Real-time Data Streaming

  • High-frequency sensor data with custom formatting
  • Compressed data transmission
  • Synchronized multi-device communication

Best Practices

Protocol Design

  1. Document commands: Maintain clear protocol documentation
  2. Use command constants: Define named constants for command values
  3. Validate data: Check data array lengths before processing
  4. Handle errors: Implement error responses for invalid commands
  5. Version compatibility: Include version information in protocols

Performance Considerations

// 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
  });
}

Error Handling

// 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

docs

analog-io.md

connection.md

digital-io.md

events.md

index.md

servo.md

sysex.md

tile.json