Device Manifest
Each device library entry is a folder that must contain a manifest.json at its root.
This document describes the manifest format; the machine-readable subset is in device-manifest.schema.json.
Top-level fields
Most of the fields are optional, defined depending on needs.
| Field | Description |
|---|---|
name | Display name in the device library and scan UI. |
description | Short summary. |
author | Author or vendor label. |
manifest_version | Integer manifest format version (which keys and semantics the app expects). Current value: 1. |
manifest_version_note | Optional. Only if you need a short label when bumping manifest_version (e.g. "added device_name_regex") |
version | Integer content revision; shown in the library UI, bumped when you edit a the entry, and compared with the ESP32 copy for sync. |
notes | Longer free-form notes and description. |
author_url, model_url, source_url | Optional links (URI). |
profile | Relative path to BLE profile JSON (e.g. ble.json). |
uuids | Relative path to UUID map (default uuids.json when omitted). |
vars | Relative path to JSON merged into the Lua vars global (e.g. vars.json). |
assets | icon, graphics, optional icon_tint for scan row / Lua. |
apps | Companion app store links (google, apple, direct[]). |
roles | observer, central, peripheral — see below. |
Roles
Observer (roles.observer)
Runs on scan when advertisement conditions match. Lower priority runs first.
| Field | Description |
|---|---|
entry | Relative path to observer Lua (e.g. observer/adv_decode.lua). Omit for simple manifest-only option: scan_conditions + assets.icon apply overlay icon without Lua. |
priority | Integer. Lower priority runs first. |
scan_conditions | When 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[]:
| Field | Description |
|---|---|
id | Stable script id. |
title | UI label. |
kind | full_menu (Scripts tab + menu JSON) or quick_action (one-shot Inspect action). |
entry | Relative path to central Lua. |
menu | Menu JSON path (required for full_menu). |
priority | Ordering when multiple scripts match. |
auto_run_on_connect | Run automatically after GATT discovery when matched. |
vars | Per-script string map merged into Lua vars. |
match | fingerprint (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.
Peripheral (roles.peripheral)
Advertiser / peripheral simulation.
| Field | Description |
|---|---|
advertisement | Path to advertisement profile JSON. |
entry | Peripheral Lua entry. |
interface | UI interface JSON - simulating “physical” on-device buttons etc. |
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 nestone_of.- Most fields accept a single value or a JSON array; within that field, any array element may match (OR).
Condition keys
| Key | Matches |
|---|---|
company_id | Manufacturer company ID (16-bit). Prefer four hex digits (004C, 05A7). |
manufacturer_data_prefix_hex | Hex prefix of manufacturer specific data. |
service_uuid_128 | Full 128-bit service UUID in the AD service list. |
service_uuid_16 | 16-bit service UUID in the AD service list (e.g. fe2c). |
service_data_uuid_128 | 128-bit UUID key in service data AD. |
service_data_uuid_16 | 16-bit UUID key in service data AD (0x16). |
device_name_contains | Case-sensitive substring of the advertised device name. |
device_name_regex | Kotlin regex on the advertised device name (see below). |
fingerprint_has_entry_id | Prior fingerprint entry id from another observer (chained decode). |
fingerprint_entry_attributes | Attribute map on that entry; requires fingerprint_has_entry_id. |
ad_type | AD type byte(s), used with ad_type_data_hex. |
ad_type_data_hex | Hex prefix or regex on payload(s) for ad_type. |
has_gap_device_type_hints | true if Appearance, Class of Device, or service UUID list is present. |
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$"]
}| Topic | Behavior |
|---|---|
| Target | Complete / Short Local Name from the advertisement (device_name in Lua). Empty if absent. |
| Match mode | Regex(pattern).containsMatchIn(name) — use ^ and $ for full-string match. |
| Case | Case-sensitive; use (?i) in the pattern for ignore-case. |
| Invalid regex | Treated as non-match (no crash). |
With device_name_contains | If 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 device library folder. The app incrementsversionwhen you save edits; ESP32 sync compares the phone copy withmanifest.jsonon the device (SYNCEDwhen equal,OUTDATEDwhen the phone is higher).