npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cmuav/sproto-protocol

v0.2.1

Published

TypeScript implementation of the Sproto serial protocol used by Tribunus ESCs and other Sproto-based motor controllers.

Readme

sproto-protocol

TypeScript implementation of the Sproto serial protocol used by Tribunus ESCs and other Sproto-based motor controllers.

Protocol Overview

Sproto is a register-based serial protocol. The ESC's memory is divided into numbered regions (0-15), each containing addressable data cells. The host reads and writes these cells over a serial link.

Wire Format

Every transaction uses a 6-byte header:

Byte 0 - Signature:  [W][1][0][1][R3][R2][R1][R0]
           W     = 1 for write, 0 for read
           101   = fixed marker
           R3-R0 = region number (0-15)

Byte 1 - Address bits 23-16
Byte 2 - Address bits 15-8
Byte 3 - Address bits 7-0
Byte 4 - Length (data units)
Byte 5 - Reserved (0x00)

Read transaction:

  1. Host sends 6-byte read header
  2. Device responds with 6-byte header (byte 4 = actual length)
  3. Device sends data bytes

Write transaction:

  1. Host sends 6-byte header + data bytes
  2. Device responds with 6-byte header (byte 4 = acknowledged length)

Half-duplex mode: When duplex is disabled, the device echoes back every write, which must be read and discarded before reading the response.

Data Types

| Type | Encoding | |------|----------| | IQ22 | Fixed-point: int32 / 2^22 | | Sprc_t | Percentage: int16 / 100 | | Smeas_t | Measurement: int16 / 100 |

Default Settings

| Setting | Default | |---------|---------| | Baud rate | 38400 | | Address bits | 8 | | Message timeout | 1000ms | | Retry count | 3 | | Max data length | 128 bytes | | Duplex | true |

Usage

1. Implement a Transport

The library is transport-agnostic. You provide an object that can write bytes, read bytes, and clear the buffer:

import type { Transport } from "sproto-protocol";

// Example: WebSerial transport
class WebSerialTransport implements Transport {
  private reader: ReadableStreamDefaultReader<Uint8Array>;
  private writer: WritableStreamDefaultWriter<Uint8Array>;
  private buffer: number[] = [];

  constructor(port: SerialPort) {
    this.reader = port.readable!.getReader();
    this.writer = port.writable!.getWriter();
  }

  async write(data: Uint8Array): Promise<void> {
    await this.writer.write(data);
  }

  async read(length: number): Promise<Uint8Array> {
    while (this.buffer.length < length) {
      const { value, done } = await this.reader.read();
      if (done) throw new Error("Port closed");
      this.buffer.push(...value);
    }
    return new Uint8Array(this.buffer.splice(0, length));
  }

  async clear(): Promise<void> {
    this.buffer = [];
  }
}

2. Connect and Read/Write

import { SprotoDevice } from "sproto-protocol";

const device = new SprotoDevice(transport, {
  baudRate: 38400,
  addressBits: 16,
  maxDataLength: 64,
  duplex: true,
});

// Define a region (number, offset, length from ESC documentation)
const config = device.addRegion({
  number: 1,
  offset: 0,
  length: 256,
});

// Read entire region from device into local buffer
await device.readRegion(config, undefined, undefined, (pct) => {
  console.log(`Reading... ${pct}%`);
});

// Access typed values from the local buffer
const voltage = config.readSmeas(0x10);
const throttleCurve = config.readSprc(0x20);
const firmwareVersion = config.readAscii(0x00, 16);
console.log(`FW: ${firmwareVersion}, Voltage: ${voltage}V, Throttle: ${throttleCurve}%`);

// Modify a value and write back
config.writeSprc(0x20, 80.0);
await device.writeRegion(config);

3. Low-Level Packet API

For custom protocol work:

import {
  buildReadPacket,
  buildWritePacket,
  decodeHeader,
  encodeSig,
} from "sproto-protocol";

// Build a read request for region 1, address 0x000000, 16 bytes
const packet = buildReadPacket(1, 0x000000, 16);
// => Uint8Array [0x51, 0x00, 0x00, 0x00, 0x10, 0x00]

// Build a write packet
const data = new Uint8Array([0x01, 0x02, 0x03]);
const writePacket = buildWritePacket(1, 0x000100, data);
// => Uint8Array [0xD1, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x02, 0x03]

// Decode a response header
const resp = decodeHeader(responseBytes);
console.log(resp.region, resp.address, resp.length, resp.write);

4. S-Record Files

Sproto uses Motorola S-Records for config save/load and firmware:

import { parseSRec, generateSRec, srecToBuffer } from "sproto-protocol";

// Parse an S-Record file
const entries = parseSRec(fileContent);
for (const entry of entries) {
  console.log(`Region ${entry.region} @ 0x${entry.address.toString(16)}: ${entry.data.length} bytes`);
}

// Generate S-Record from region data
const srec = generateSRec([
  { region: 1, address: 0, data: config.readRaw(0, 256) },
]);

// Convert firmware S-Record to flat binary
const firmware = srecToBuffer(firmwareFileContent);

Protocol Notes

  • Region numbers are 4 bits (0-15)
  • Addresses are 24 bits (0-0xFFFFFF)
  • Length field is 8 bits (max 255 units per transaction, but maxDataLength config controls chunking)
  • The signature byte's fixed 101 pattern can be used to validate responses
  • Byte ordering for 16/32-bit values is configurable per region