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

@stormotion/ble-protocol-generator

v0.0.4

Published

TypeScript code generator for parsing BLE protocols from YAML schemas.

Readme

@stormotion/ble-protocol-generator

npm version License

A schema-driven protocol parser and serializer generator for BLE communications.

@stormotion/ble-protocol-generator allows you to define your binary protocols in human-readable YAML, then automatically generates robust, ready-to-use TypeScript code to parse and serialize that data.

Under the hood, it leverages the power of binary-layout for performant and accurate bit-level manipulation.

Navigation

💡 Motivation

Mobile / Web apps interacting with BLE hardware often require complex binary data handling.

  • Stop manual bit-shifting: No more maintaining fragile DataView code.
  • Single Source of Truth: Define your protocol once in YAML; use the generated code in your mobile / web app.
  • Bi-directional: Generates both Parsers (Binary → TS Object) and Serializers (TS Object → Binary) automatically.

📦 Installation

npm install @stormotion/ble-protocol-generator

# or

yarn add @stormotion/ble-protocol-generator

🚀 Quick Start

Follow these steps to integrate protocol parsing into your project.

1. Setup protocol directory

Create a dedicated directory in your project root to store your schema definitions.

mkdir protocol

2. Create file for schema

Create a YAML file inside the protocol folder to describe your binary structure.

Naming Convention: Files must use snake_case (underscores _) for multi-word names.

  • ThermostatControl.yaml
  • thermostat-control.yaml
  • smart_light.yaml

Example: thermostat/thermostat_control.yaml

touch protocol/thermostat_control.yaml

3. Define schema

Define yaml schema using special syntax

Basic example:

# thermostat_control.yaml

meta:
  type: root
  endianness: little
  service_uuid: 0000181A-0000-1000-8000-00805F9B34FB
  notifiable_characteristic_uuid: 00002A6E-0000-1000-8000-00805F9B34FB
seq:
  - id: control_flags
    binary: uint
    size: 1

  - id: current_temperature
    binary: int
    size: 2

  - id: target_temperature
    binary: uint
    size: 2

  - id: humidity_percentage
    binary: uint
    size: 1

  - id: hvac_cycles_count
    binary: uint
    size: 4

  - id: wifi_ssid
    binary: bytes
    size: 32

4. Generate parser

Generate parser with command

# Syntax
ble-gen <source_path> <output_parser_name>

# Example
ble-gen ./thermostat ThermostatProtocolParser

Output

If the command runs successfully, you will see a confirmation message indicating where the file with your parser was saved.

✅ Generated <output_parser_name> into generated/<output_parser_name>.ts

# Example
✅ Generated ThermostatProtocolParser into generated/ThermostatProtocolParser.ts

Now you free to use generated parser anywhere in the code!

import ThermostatProtocolParser from "./generated/ThermostatProtocolParser.ts";

const bleProtocolBuffer = Buffer.from([
  // control_flags (1 byte: 170)
  0xaa,

  // current_temperature (2 bytes: -55 or 0xFFC9 LE)
  0xc9, 0xff,

  // target_temperature (2 bytes: 240 or 0x00F0 LE)
  0xf0, 0x00,

  // humidity_percentage (1 byte: 45)
  0x2d,

  // hvac_cycles_count (4 bytes: 10000 or 0x00002710 LE)
  0x10, 0x27, 0x00, 0x00,

  // wifi_ssid (32 bytes: "HomeNetwork" + 21 null bytes)
  0x48, 0x6f, 0x6d, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);

const parser = new ThermostatProtocolParser();

const deserializedValue = parser.deserializeBinaryThermostat(bleProtocolBuffer);

console.log(deserializedValue);

# {
#  control_flags: 170,
#  current_temperature: -13825,
#  target_temperature: 240,
#  humidity_percentage: 45,
#  hvac_cycles_count: 10000,
#  wifi_ssid: <Buffer 48 6f 6d 65 4e 65 74 77 6f 72 6b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
# }

const serializedValue = parser.serializeDataThermostat(deserializedValue);

console.log(serializedValue);

# Uint8Array(42) [
#  170, 201, 255, 240,  0,  45,  16,  39,   0,   0,
#   72, 111, 109, 101, 78, 101, 116, 119, 111, 114,
#  107,   0,   0,   0,  0,   0,   0,   0,   0,   0,
#    0,   0,   0,   0,  0,   0,   0,   0,   0,   0,
#    0,   0
# ]

📝 Protocol Definition

Meta

Meta data of the schema.

| Property | Type | Description | | ------------------------------ | ------------------------------ | -------------------------------------------------------------------------------------------- | | type | "root" \| "layout" | Set "root" for common schema. Check details in Root and Layout section | | endianness | "little" \| "big" (optional) | Endianness. Default is "big" | | writable_characteristic_uuid | string (optional) | Writable characteristic uuid | | notifiable_characteristic_uuid | string (optional) | Notifiable characteristic uuid |

Seq

Sequence of bytes in binary with definitions.

| Property | Type | Description | | -------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | Name of the property | | binary | "(u)int" \| "bytes" \| "switch" | Type of data. Check details in Binary section | | size | number | Size of data | | id_size | number | Defines the byte size of the field that immediately precedes this "switch" block. This field's value is used to select the correct layout. | | layouts | array | A map of types field. | | layout | string | Specifies the name of a separate, reusable layout schema file to be embedded inline at this position. layout | | bits | string[] | An ordered list of names used to parse the current field (must be a uint or int) into individual Boolean flags. Use to define bitmasks or flag structures within a single byte/integer field. Check Bits section. |

🔢 Binary

The binary types used in the seq block closely align with those provided by the robust binary-layout library. Where necessary, certain types have been abstracted or modified to better suit the declarative style of YAML schema definition.

u(int)

Numeric value (signed or unsigned). By default, converted into a number or bigint depending on its size. Source

Definition in yaml example:

  - id: header
    binary: uint
    size: 1

Bytes

Raw bytes or sub-layouts. By default, converted into a Uint8Array when not specifying a sub-layout, or an object otherwise. Source

Definition in yaml example::

Returns raw bytes as Uint8Array.

  - id: reserve
    binary: bytes
    size: 2

Returns deserialized object.

  - id: weekly_schedule
    binary: bytes
    layout: schedule_day_config

Switch

Enables branching logic. Comparable to Rust enums. Converted into a union type of the derived types of its layout variants. Source

The following example demonstrates how to map specific layout sequences to the values 42 and 254 which are address bytes.

- id: command
    binary: switch
    id_size: 1
    layouts:
      - address: 42 // address byte
        seq: // sequence of layouts for specific address
          - id: data_bytes
            binary: uint
            size: 1
          - id: data
            binary: bytes
            layout: schedule_day_config
      - address: 254
        seq:
          - id: data_bytes
            binary: uint
            size: 1
          - id: data
            binary: bytes
            layout: schedule_night_config

🦸 Advanced

Root and Layout

To keep protocol definitions maintainable and scalable, this library distinguishes between Root Schemas (Entry Points) and Nested Layouts (Reusable Parts).

Root Schema (root)

A Root Schema represents a complete BLE Characteristic. It serves as the entry point for the parser. It should be stored at the root level of the protocols folder. You can have as much root schemas as you wish in this folder. Our codegen will unite them in one single parser.

Nested Layout (layout)

A Nested Layout is a reusable building block. It can be injected in bytes binary in layout field.


Usage example

Imagine a Smart Thermostat where multiple characteristics share a common "Schedule" structure.

1. Setup file structure

Create layout folder and put your layouts yaml's there.

[!IMPORTANT] Directory Naming Convention It is strictly necessary to place your reusable nested schemas in a dedicated folder named layout. This ensures clear separation from your root protocol files.

protocols/
├── thermostat_control.yaml   # [ROOT] Entry point
├── thermostat_settings.yaml   # [ROOT] Another entry point
└── layouts/
    └── weekly_schedule.yaml   # [LAYOUT] Reusable part

Set type filed to layout.

# weekly_schedule.yaml

meta:
  type: layout
seq:
  - id: active_days
    binary: uint
    size: 1
    bits:
      - monday
      - tuesday
      - wednesday
      - thursday
      - friday
      - saturday
      - sunday
      - is_active

  - id: start_time_minutes
    binary: uint
    size: 2

  - id: target_temp
    binary: int
    size: 2
    description: "Value in 0.1 increments (e.g., 210 = 21.0C)"

  - id: fan_mode
    binary: uint
    size: 1
    description: "0=Auto, 1=Low, 2=High"

Add layout name to the root schema.

# thermostat_control.yaml

meta:
  type: root
  endianness: little
  service_uuid: 0000181A-0000-1000-8000-00805F9B34FB
  notifiable_characteristic_uuid: 00002A6E-0000-1000-8000-00805F9B34FB
seq:
  - id: control_flags
    binary: uint
    size: 1

  - id: current_temperature
    binary: int
    size: 2

  - id: target_temperature
    binary: uint
    size: 2

  - id: humidity_percentage
    binary: uint
    size: 1

  - id: hvac_cycles_count
    binary: uint
    size: 4

  - id: weekly_schedule
    binary: bytes
    layout: weekly_schedule # add layout name

Bits

Any single u(int) (signed or unsigned integer) field can be treated as a bitmask. This technique allows you to use the bits property to decompose the field, where each individual bit is parsed and exposed as a separate boolean flag.

Definition in yaml example:

- id: control_flags
    binary: uint
    size: 1
    bits:
      - is_power_on
      - is_heating
      - is_cooling
      - fan_auto_mode
      - eco_save_enabled

Usage example:

import ThermostatProtocolParser from "./generated/ThermostatProtocolParser.ts";

const bleProtocolBuffer = Buffer.from([
  // control_flags (1 byte: 170)
  0xaa,
]);

const parser = new ThermostatProtocolParser();

const deserializedValue = parser.deserializeBinaryThermostat(bleProtocolBuffer);

console.log(deserializedValue);

# {
#  control_flags: {
#    is_power_on: false,
#    is_heating: true,
#    is_cooling: false,
#    fan_auto_mode: true,
#    eco_save_enabled: false
#  }
# }

🐛 Troubleshooting

Feel free to open issue in case of any troubles