Skip to content

Device pack manifest.json

Each device pack is a folder under assets/devices/ (bundled) or the on-disk device library. The pack must contain a manifest.json at its root. This document describes the manifest format; the machine-readable subset is in device-manifest.schema.json.

Layout

devices/
  docs/                          ← this documentation (not a device pack)
  products/<pack_id>/manifest.json
  vendors/<pack_id>/manifest.json
  protocols/<pack_id>/manifest.json
  generic/<pack_id>/manifest.json

The pack folder id (e.g. products/pixel_buds_2a) is the runtime device id. Paths in the manifest are relative to that folder.

Top-level fields

FieldDescription
nameDisplay name in the device library and scan UI.
descriptionShort summary.
authorAuthor or vendor label.
manifest_versionInteger manifest format version (which keys and semantics the app expects). Current value: 1.
manifest_version_noteOptional. Only if you need a short label when bumping manifest_version (e.g. "added device_name_regex"); omit on normal packs.
versionInteger pack content revision; shown in the library UI, bumped when you edit a pack, and compared with the ESP32 copy for sync (OUTDATED when the phone copy is higher).
notesLonger free-form notes (pairing, testing, provenance).
author_url, model_url, source_urlOptional links (URI).
profileRelative path to BLE profile JSON (e.g. ble.json).
uuidsRelative path to UUID map (default uuids.json when omitted).
varsRelative path to JSON merged into the Lua vars global (e.g. vars.json).
assetsicon, graphics, optional icon_tint for scan row / Lua.
appsCompanion app store links (google, apple, direct[]).
rolesobserver, central, peripheral — see below.

Additional top-level keys are allowed and ignored by the app unless documented elsewhere (e.g. catalog for Fast Pair).

Roles

Observer (roles.observer)

Runs on scan when advertisement conditions match. Lower priority runs first.

FieldDescription
entryRelative path to observer Lua (e.g. observer/adv_decode.lua). Omit for manifest-only packs: scan_conditions + assets.icon apply overlay icon without Lua.
priorityInteger; default 50.
scan_conditionsWhen to run this observer — see Scan conditions.

Fingerprinting (Lua parse() or manifest-only icon) runs only when the canonical advertisement payload for that device changes, not on every duplicate ADV.

Central (roles.central)

Scripts after connect / on the Inspect sheet. Each item in scripts[]:

FieldDescription
idStable script id.
titleUI label.
kindfull_menu (Scripts tab + menu JSON) or quick_action (one-shot Inspect action).
entryRelative path to central Lua.
menuMenu JSON path (required for full_menu).
priorityOrdering when multiple scripts match.
auto_run_on_connectRun automatically after GATT discovery when matched.
varsPer-script string map merged into Lua vars.
matchfingerprint (key → allowed values), services (GATT UUIDs), require_discovered_services (default true), fingerprint_or_services.

Central matching uses fingerprint overlay keys and discovered GATT services, not scan_conditions.

When match.services is non-empty, require_discovered_services defaults to true: the script does not appear on the Inspect sheet until GATT discovery (or a persisted session log) confirms those UUIDs. Set "require_discovered_services": false only if the script should match optimistically before discovery.

Peripheral (roles.peripheral)

Advertiser / peripheral simulation in the app.

FieldDescription
advertisementPath to advertisement profile JSON.
entryPeripheral Lua entry.
interfaceUI interface JSON.

Scan conditions

Used only under roles.observer.scan_conditions. Evaluated against the parsed advertisement before observer Lua runs.

Combining rules

  • Top-level keys on one object are ANDed.
  • one_of: array of nested condition objects; at least one branch must match (OR). Each branch is ANDed internally. Branches may nest one_of.
  • Most fields accept a single value or a JSON array; within that field, any array element may match (OR).

Condition keys

KeyMatches
company_idManufacturer company ID (16-bit). Prefer four hex digits (004C, 05A7).
manufacturer_data_prefix_hexHex prefix of manufacturer specific data.
service_uuid_128Full 128-bit service UUID in the AD service list.
service_uuid_1616-bit service UUID in the AD service list (e.g. fe2c).
service_data_uuid_128128-bit UUID key in service data AD.
service_data_uuid_1616-bit UUID key in service data AD (0x16).
device_name_containsCase-sensitive substring of the advertised device name.
device_name_regexKotlin regex on the advertised device name (see below).
fingerprint_has_entry_idPrior fingerprint entry id from another observer (chained decode).
fingerprint_entry_attributesAttribute map on that entry; requires fingerprint_has_entry_id.
ad_typeAD type byte(s), used with ad_type_data_hex.
ad_type_data_hexHex prefix or regex on payload(s) for ad_type.
has_gap_device_type_hintstrue if Appearance, Class of Device, or service UUID list is present.

Observer Lua parse(input).manufacturer_data keys and first_company_id use the same 4-digit uppercase hex company id format (e.g. "0075", "004C"). See lua-mobile.md.

device_name_contains vs device_name_regex

Use device_name_contains for simple substring checks:

"scan_conditions": {
  "device_name_contains": "Pixel Buds 2a"
}
"device_name_contains": ["WH-CH720N", "LE-WH-CH720N"]

Use device_name_regex when you need anchors, character classes, or alternation:

"scan_conditions": {
  "device_name_regex": "^QCAR-[0-9A-F]{4}$"
}
"scan_conditions": {
  "device_name_regex": ["^WH-CH720N$", "^LE-WH-CH720N$"]
}
TopicBehavior
TargetComplete / Short Local Name from the advertisement (device_name in Lua). Empty if absent.
Match modeRegex(pattern).containsMatchIn(name) — use ^ and $ for full-string match.
CaseCase-sensitive; use (?i) in the pattern for ignore-case.
Invalid regexTreated as non-match (no crash).
With device_name_containsIf both are set, both must pass.

Chained observers (one_of + fingerprint)

Example: run only when Apple meta continuity was decoded first:

"scan_conditions": {
  "fingerprint_has_entry_id": "apple_meta",
  "fingerprint_entry_attributes": {
    "continuity_has_nearbyinfo": "true"
  }
}

Example: company id only (manifest-only icon)

"roles": {
  "observer": {
    "priority": 20,
    "scan_conditions": {
      "company_id": "05A7"
    }
  }
}

Requires assets.icon when entry is omitted.

JSON Schema

device-manifest.schema.json validates the structured parts of the manifest (especially scan_conditions). The app does not load this file at runtime; it documents the contract for authors and editors.

Point your editor’s JSON Schema setting at that file when editing a pack’s manifest.json, or use it in CI if you add manifest linting.

Version fields and ESP32 sync

  • manifest_version: format of the JSON file (bump when adding breaking manifest keys).
  • version: content revision of the pack folder. The app increments version when you save edits; ESP32 sync compares the phone copy with manifest.json on the device (SYNCED when equal, OUTDATED when the phone is higher).
  • Legacy dotted strings in old version values (e.g. "1.0.0") are still read from ESP32 using the first numeric segment (1) until the pack is re-synced.
© BLESPlo.it · BLE Research Tool · GitHub