Skip to content

Instantly share code, notes, and snippets.

@ddlsmurf
Last active August 27, 2025 01:36
Show Gist options
  • Select an option

  • Save ddlsmurf/1d1ee3aedd4aee6d3b800490d3e6dde7 to your computer and use it in GitHub Desktop.

Select an option

Save ddlsmurf/1d1ee3aedd4aee6d3b800490d3e6dde7 to your computer and use it in GitHub Desktop.

Revisions

  1. ddlsmurf revised this gist Aug 27, 2025. No changes.
  2. ddlsmurf revised this gist Aug 27, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion generate_sensors_json.js
    Original file line number Diff line number Diff line change
    @@ -237,4 +237,4 @@ sensors.forEach(sensorDUT => {
    });
    sensors.sort((a, b) => a.id - b.id);
    // sensors.sort((a, b) => a.name.localeCompare(b.name));
    console.error(sensors)
    console.log(JSON.stringify(sensors, null, 2));
  3. ddlsmurf revised this gist Aug 27, 2025. 1 changed file with 1015 additions and 557 deletions.
    1,572 changes: 1,015 additions & 557 deletions bthome_sensors.json
    Original file line number Diff line number Diff line change
    @@ -1,697 +1,1155 @@
    [
    {
    id: 0,
    name: 'packet id',
    example: { data: '0009', result: '9' },
    kind: 'number',
    size: 1
    "id": 0,
    "name": "packet id",
    "example": {
    "data": "0009",
    "result": "9"
    },
    "kind": "number",
    "size": 1
    },
    {
    id: 1,
    name: 'battery',
    unit: { factor: 1, label: '%' },
    example: { data: '0161', result: '97' },
    kind: 'number',
    size: 1
    "id": 1,
    "name": "battery",
    "unit": {
    "factor": 1,
    "label": "%"
    },
    "example": {
    "data": "0161",
    "result": "97"
    },
    "kind": "number",
    "size": 1
    },
    {
    id: 2,
    name: 'temperature',
    unit: { factor: 0.01, label: '°C' },
    example: { data: '02CA09', result: '25.06' },
    kind: 'number',
    size: 2,
    signed: true
    "id": 2,
    "name": "temperature",
    "unit": {
    "factor": 0.01,
    "label": "°C"
    },
    "example": {
    "data": "02CA09",
    "result": "25.06"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    id: 3,
    name: 'humidity',
    unit: { factor: 0.01, label: '%' },
    example: { data: '03BF13', result: '50.55' },
    kind: 'number',
    size: 2
    {
    "id": 3,
    "name": "humidity",
    "unit": {
    "factor": 0.01,
    "label": "%"
    },
    "example": {
    "data": "03BF13",
    "result": "50.55"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 4,
    name: 'pressure',
    unit: { factor: 0.01, label: 'hPa' },
    example: { data: '04138A01', result: '1008.83' },
    kind: 'number',
    size: 3
    {
    "id": 4,
    "name": "pressure",
    "unit": {
    "factor": 0.01,
    "label": "hPa"
    },
    "example": {
    "data": "04138A01",
    "result": "1008.83"
    },
    "kind": "number",
    "size": 3
    },
    {
    id: 5,
    name: 'illuminance',
    unit: { factor: 0.01, label: 'lux' },
    example: { data: '05138A14', result: '13460.67' },
    kind: 'number',
    size: 3
    {
    "id": 5,
    "name": "illuminance",
    "unit": {
    "factor": 0.01,
    "label": "lux"
    },
    "example": {
    "data": "05138A14",
    "result": "13460.67"
    },
    "kind": "number",
    "size": 3
    },
    {
    id: 6,
    name: 'mass (kg)',
    unit: { factor: 0.01, label: 'kg' },
    example: { data: '065E1F', result: '80.3' },
    kind: 'number',
    size: 2
    {
    "id": 6,
    "name": "mass (kg)",
    "unit": {
    "factor": 0.01,
    "label": "kg"
    },
    "example": {
    "data": "065E1F",
    "result": "80.3"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 7,
    name: 'mass (lb)',
    unit: { factor: 0.01, label: 'lb' },
    example: { data: '073E1D', result: '74.86' },
    kind: 'number',
    size: 2
    {
    "id": 7,
    "name": "mass (lb)",
    "unit": {
    "factor": 0.01,
    "label": "lb"
    },
    "example": {
    "data": "073E1D",
    "result": "74.86"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 8,
    name: 'dewpoint',
    unit: { factor: 0.01, label: '°C' },
    example: { data: '08CA06', result: '17.38' },
    kind: 'number',
    size: 2,
    signed: true
    {
    "id": 8,
    "name": "dewpoint",
    "unit": {
    "factor": 0.01,
    "label": "°C"
    },
    "example": {
    "data": "08CA06",
    "result": "17.38"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    id: 9,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '0960', result: '96' },
    kind: 'number',
    size: 1
    "id": 9,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "0960",
    "result": "96"
    },
    "kind": "number",
    "size": 1
    },
    {
    id: 10,
    name: 'energy',
    unit: { factor: 0.001, label: 'kWh' },
    example: { data: '0A138A14', result: '1346.067' },
    kind: 'number',
    size: 3
    "id": 10,
    "name": "energy",
    "unit": {
    "factor": 0.001,
    "label": "kWh"
    },
    "example": {
    "data": "0A138A14",
    "result": "1346.067"
    },
    "kind": "number",
    "size": 3
    },
    {
    id: 11,
    name: 'power',
    unit: { factor: 0.01, label: 'W' },
    example: { data: '0B021B00', result: '69.14' },
    kind: 'number',
    size: 3
    "id": 11,
    "name": "power",
    "unit": {
    "factor": 0.01,
    "label": "W"
    },
    "example": {
    "data": "0B021B00",
    "result": "69.14"
    },
    "kind": "number",
    "size": 3
    },
    {
    id: 12,
    name: 'voltage',
    unit: { factor: 0.001, label: 'V' },
    example: { data: '0C020C', result: '3.074' },
    kind: 'number',
    size: 2
    "id": 12,
    "name": "voltage",
    "unit": {
    "factor": 0.001,
    "label": "V"
    },
    "example": {
    "data": "0C020C",
    "result": "3.074"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 13,
    name: 'pm2.5',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '0D120C', result: '3090' },
    kind: 'number',
    size: 2
    "id": 13,
    "name": "pm2.5",
    "unit": {
    "factor": 1,
    "label": "ug/m3"
    },
    "example": {
    "data": "0D120C",
    "result": "3090"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 14,
    name: 'pm10',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '0E021C', result: '7170' },
    kind: 'number',
    size: 2
    "id": 14,
    "name": "pm10",
    "unit": {
    "factor": 1,
    "label": "ug/m3"
    },
    "example": {
    "data": "0E021C",
    "result": "7170"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 15,
    name: 'generic boolean',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    "id": 15,
    "name": "generic boolean",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Off",
    "On"
    ]
    },
    {
    id: 16,
    name: 'power',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    "id": 16,
    "name": "power",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Off",
    "On"
    ]
    },
    {
    id: 17,
    name: 'opening',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    "id": 17,
    "name": "opening",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Closed",
    "Open"
    ]
    },
    {
    id: 18,
    name: 'co2',
    unit: { factor: 1, label: 'ppm' },
    example: { data: '12E204', result: '1250' },
    kind: 'number',
    size: 2
    "id": 18,
    "name": "co2",
    "unit": {
    "factor": 1,
    "label": "ppm"
    },
    "example": {
    "data": "12E204",
    "result": "1250"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 19,
    name: 'tvoc',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '133301', result: '307' },
    kind: 'number',
    size: 2
    "id": 19,
    "name": "tvoc",
    "unit": {
    "factor": 1,
    "label": "ug/m3"
    },
    "example": {
    "data": "133301",
    "result": "307"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 20,
    name: 'moisture',
    unit: { factor: 0.01, label: '%' },
    example: { data: '14020C', result: '30.74' },
    kind: 'number',
    size: 2
    "id": 20,
    "name": "moisture",
    "unit": {
    "factor": 0.01,
    "label": "%"
    },
    "example": {
    "data": "14020C",
    "result": "30.74"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 21,
    name: 'battery',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Low' ]
    "id": 21,
    "name": "battery",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Normal",
    "Low"
    ]
    },
    {
    id: 22,
    name: 'battery charging',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not Charging', 'Charging' ]
    "id": 22,
    "name": "battery charging",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Not Charging",
    "Charging"
    ]
    },
    {
    id: 23,
    name: 'carbon monoxide',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not detected', 'Detected' ]
    "id": 23,
    "name": "carbon monoxide",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Not detected",
    "Detected"
    ]
    },
    {
    id: 24,
    name: 'cold',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Cold' ]
    "id": 24,
    "name": "cold",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Normal",
    "Cold"
    ]
    },
    {
    id: 25,
    name: 'connectivity',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Disconnected', 'Connected' ]
    "id": 25,
    "name": "connectivity",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Disconnected",
    "Connected"
    ]
    },
    {
    id: 26,
    name: 'door',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    "id": 26,
    "name": "door",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Closed",
    "Open"
    ]
    },
    {
    id: 27,
    name: 'garage door',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    "id": 27,
    "name": "garage door",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Closed",
    "Open"
    ]
    },
    {
    id: 28,
    name: 'gas',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 28,
    "name": "gas",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 29,
    name: 'heat',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Hot' ]
    "id": 29,
    "name": "heat",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Normal",
    "Hot"
    ]
    },
    {
    id: 30,
    name: 'light',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'No light', 'Light detected' ]
    "id": 30,
    "name": "light",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "No light",
    "Light detected"
    ]
    },
    {
    id: 31,
    name: 'lock',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Locked', 'Unlocked' ]
    "id": 31,
    "name": "lock",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Locked",
    "Unlocked"
    ]
    },
    {
    id: 32,
    name: 'moisture',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Dry', 'Wet' ]
    "id": 32,
    "name": "moisture",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Dry",
    "Wet"
    ]
    },
    {
    id: 33,
    name: 'motion',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 33,
    "name": "motion",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 34,
    name: 'moving',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not moving', 'Moving' ]
    "id": 34,
    "name": "moving",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Not moving",
    "Moving"
    ]
    },
    {
    id: 35,
    name: 'occupancy',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 35,
    "name": "occupancy",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 36,
    name: 'plug',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Unplugged', 'Plugged in' ]
    "id": 36,
    "name": "plug",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Unplugged",
    "Plugged in"
    ]
    },
    {
    id: 37,
    name: 'presence',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Away', 'Home' ]
    "id": 37,
    "name": "presence",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Away",
    "Home"
    ]
    },
    {
    id: 38,
    name: 'problem',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'OK', 'Problem' ]
    "id": 38,
    "name": "problem",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "OK",
    "Problem"
    ]
    },
    {
    id: 39,
    name: 'running',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not Running', 'Running' ]
    "id": 39,
    "name": "running",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Not Running",
    "Running"
    ]
    },
    {
    id: 40,
    name: 'safety',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Unsafe', 'Safe' ]
    "id": 40,
    "name": "safety",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Unsafe",
    "Safe"
    ]
    },
    {
    id: 41,
    name: 'smoke',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 41,
    "name": "smoke",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 42,
    name: 'sound',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 42,
    "name": "sound",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 43,
    name: 'tamper',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    "id": 43,
    "name": "tamper",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Off",
    "On"
    ]
    },
    {
    id: 44,
    name: 'vibration',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    "id": 44,
    "name": "vibration",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Clear",
    "Detected"
    ]
    },
    {
    id: 45,
    name: 'window',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    "id": 45,
    "name": "window",
    "kind": "boolean",
    "size": 1,
    "boolLabels": [
    "Closed",
    "Open"
    ]
    },
    {
    id: 46,
    name: 'humidity',
    unit: { factor: 1, label: '%' },
    example: { data: '2E23', result: '35' },
    kind: 'number',
    size: 1
    "id": 46,
    "name": "humidity",
    "unit": {
    "factor": 1,
    "label": "%"
    },
    "example": {
    "data": "2E23",
    "result": "35"
    },
    "kind": "number",
    "size": 1
    },
    {
    id: 47,
    name: 'moisture',
    unit: { factor: 1, label: '%' },
    example: { data: '2F23', result: '35' },
    kind: 'number',
    size: 1
    "id": 47,
    "name": "moisture",
    "unit": {
    "factor": 1,
    "label": "%"
    },
    "example": {
    "data": "2F23",
    "result": "35"
    },
    "kind": "number",
    "size": 1
    },
    {
    "id": 58,
    "name": "button",
    "size": 1,
    "kind": "trigger",
    "enum": [
    [
    0,
    "None"
    ],
    [
    1,
    "press"
    ],
    [
    2,
    "double_press"
    ],
    [
    3,
    "triple_press"
    ],
    [
    4,
    "long_press"
    ],
    [
    5,
    "long_double_press"
    ],
    [
    6,
    "long_triple_press"
    ],
    [
    128,
    "hold_press"
    ]
    ]
    },
    {
    id: 58,
    name: 'button',
    size: 1,
    kind: 'trigger',
    enum: [
    [Array], [Array],
    [Array], [Array],
    [Array], [Array],
    [Array], [Array]
    "id": 60,
    "name": "dimmer",
    "size": 2,
    "kind": "trigger_with_count",
    "enum": [
    [
    0,
    "None"
    ],
    [
    1,
    "rotate left"
    ],
    [
    2,
    "rotate right"
    ]
    ]
    },
    {
    id: 60,
    name: 'dimmer',
    size: 2,
    kind: 'trigger_with_count',
    enum: [ [Array], [Array], [Array] ]
    "id": 61,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "3D0960",
    "result": "24585"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 62,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "3E2A2C0960",
    "result": "1611213866"
    },
    "kind": "number",
    "size": 4
    },
    {
    "id": 63,
    "name": "rotation",
    "unit": {
    "factor": 0.1,
    "label": "°"
    },
    "example": {
    "data": "3F020C",
    "result": "307.4"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    "id": 64,
    "name": "distance (mm)",
    "unit": {
    "factor": 1,
    "label": "mm"
    },
    "example": {
    "data": "400C00",
    "result": "12"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 65,
    "name": "distance (m)",
    "unit": {
    "factor": 0.1,
    "label": "m"
    },
    "example": {
    "data": "414E00",
    "result": "7.8"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 66,
    "name": "duration",
    "unit": {
    "factor": 0.001,
    "label": "s"
    },
    "example": {
    "data": "424E3400",
    "result": "13.390"
    },
    "kind": "number",
    "size": 3
    },
    {
    "id": 67,
    "name": "current",
    "unit": {
    "factor": 0.001,
    "label": "A"
    },
    "example": {
    "data": "434E34",
    "result": "13.39"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 68,
    "name": "speed",
    "unit": {
    "factor": 0.01,
    "label": "m/s"
    },
    "example": {
    "data": "444E34",
    "result": "133.90"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 69,
    "name": "temperature",
    "unit": {
    "factor": 0.1,
    "label": "°C"
    },
    "example": {
    "data": "451101",
    "result": "27.3"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    "id": 70,
    "name": "UV index",
    "unit": {
    "factor": 0.1,
    "label": ""
    },
    "example": {
    "data": "4632",
    "result": "5.0"
    },
    "kind": "number",
    "size": 1
    },
    {
    "id": 71,
    "name": "volume",
    "unit": {
    "factor": 0.1,
    "label": "L"
    },
    "example": {
    "data": "478756",
    "result": "2215.1"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 72,
    "name": "volume",
    "unit": {
    "factor": 1,
    "label": "mL"
    },
    "example": {
    "data": "48DC87",
    "result": "34780"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 73,
    "name": "volume flow rate",
    "unit": {
    "factor": 0.001,
    "label": "m3/hr"
    },
    "example": {
    "data": "49DC87",
    "result": "34.780"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 74,
    "name": "voltage",
    "unit": {
    "factor": 0.1,
    "label": "V"
    },
    "example": {
    "data": "4A020C",
    "result": "307.4"
    },
    "kind": "number",
    "size": 2
    },
    {
    "id": 75,
    "name": "gas",
    "unit": {
    "factor": 0.001,
    "label": "m3"
    },
    "example": {
    "data": "4B138A14",
    "result": "1346.067"
    },
    "kind": "number",
    "size": 3
    },
    {
    "id": 76,
    "name": "gas",
    "unit": {
    "factor": 0.001,
    "label": "m3"
    },
    "example": {
    "data": "4C41018A01",
    "result": "25821.505"
    },
    "kind": "number",
    "size": 4
    },
    {
    id: 61,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '3D0960', result: '24585' },
    kind: 'number',
    size: 2
    "id": 77,
    "name": "energy",
    "unit": {
    "factor": 0.001,
    "label": "kWh"
    },
    "example": {
    "data": "4d12138a14",
    "result": "344593.170"
    },
    "kind": "number",
    "size": 4
    },
    {
    id: 62,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '3E2A2C0960', result: '1611213866' },
    kind: 'number',
    size: 4
    "id": 78,
    "name": "volume",
    "unit": {
    "factor": 0.001,
    "label": "L"
    },
    "example": {
    "data": "4E87562A01",
    "result": "19551.879"
    },
    "kind": "number",
    "size": 4
    },
    {
    id: 63,
    name: 'rotation',
    unit: { factor: 0.1, label: '°' },
    example: { data: '3F020C', result: '307.4' },
    kind: 'number',
    size: 2,
    signed: true
    "id": 79,
    "name": "water",
    "unit": {
    "factor": 0.001,
    "label": "L"
    },
    "example": {
    "data": "4F87562A01",
    "result": "19551.879"
    },
    "kind": "number",
    "size": 4
    },
    {
    id: 64,
    name: 'distance (mm)',
    unit: { factor: 1, label: 'mm' },
    example: { data: '400C00', result: '12' },
    kind: 'number',
    size: 2
    "id": 80,
    "name": "timestamp",
    "unit": {
    "factor": 1,
    "label": "s since epoch"
    },
    "example": {
    "data": "505d396164",
    "result": "2023-05-14T19:41:17.000Z"
    },
    "kind": "epochS",
    "size": 4
    },
    {
    id: 65,
    name: 'distance (m)',
    unit: { factor: 0.1, label: 'm' },
    example: { data: '414E00', result: '7.8' },
    kind: 'number',
    size: 2
    "id": 81,
    "name": "acceleration",
    "unit": {
    "factor": 0.001,
    "label": "m/s²"
    },
    "example": {
    "data": "518756",
    "result": "22.151"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 66,
    name: 'duration',
    unit: { factor: 0.001, label: 's' },
    example: { data: '424E3400', result: '13.390' },
    kind: 'number',
    size: 3
    "id": 82,
    "name": "gyroscope",
    "unit": {
    "factor": 0.001,
    "label": "°/s"
    },
    "example": {
    "data": "528756",
    "result": "22.151"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 67,
    name: 'current',
    unit: { factor: 0.001, label: 'A' },
    example: { data: '434E34', result: '13.39' },
    kind: 'number',
    size: 2
    "id": 83,
    "name": "text",
    "example": {
    "data": "530C48656C6C6F20576F726C6421",
    "result": "Hello World!"
    },
    "size": -1,
    "kind": "utf8"
    },
    {
    id: 68,
    name: 'speed',
    unit: { factor: 0.01, label: 'm/s' },
    example: { data: '444E34', result: '133.90' },
    kind: 'number',
    size: 2
    "id": 84,
    "name": "raw",
    "example": {
    "data": "540C48656C6C6F20576F726C6421",
    "result": "48656c6c6f20576f726c6421"
    },
    "size": -1,
    "kind": "hex"
    },
    {
    id: 69,
    name: 'temperature',
    unit: { factor: 0.1, label: '°C' },
    example: { data: '451101', result: '27.3' },
    kind: 'number',
    size: 2,
    signed: true
    "id": 85,
    "name": "volume storage",
    "unit": {
    "factor": 0.001,
    "label": "L"
    },
    "example": {
    "data": "5587562A01",
    "result": "19551.879"
    },
    "kind": "number",
    "size": 4
    },
    {
    id: 70,
    name: 'UV index',
    unit: { factor: 0.1, label: '' },
    example: { data: '4632', result: '5.0' },
    kind: 'number',
    size: 1
    "id": 86,
    "name": "conductivity",
    "unit": {
    "factor": 1,
    "label": "µS/cm"
    },
    "example": {
    "data": "56E803",
    "result": "1000"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 71,
    name: 'volume',
    unit: { factor: 0.1, label: 'L' },
    example: { data: '478756', result: '2215.1' },
    kind: 'number',
    size: 2
    "id": 87,
    "name": "temperature",
    "unit": {
    "factor": 1,
    "label": "°C"
    },
    "example": {
    "data": "57EA",
    "result": "-22"
    },
    "kind": "number",
    "size": 1,
    "signed": true
    },
    {
    id: 72,
    name: 'volume',
    unit: { factor: 1, label: 'mL' },
    example: { data: '48DC87', result: '34780' },
    kind: 'number',
    size: 2
    "id": 88,
    "name": "temperature",
    "unit": {
    "factor": 0.35,
    "label": "°C"
    },
    "example": {
    "data": "58EA",
    "result": "-7.7"
    },
    "kind": "number",
    "size": 1,
    "signed": true
    },
    {
    id: 73,
    name: 'volume flow rate',
    unit: { factor: 0.001, label: 'm3/hr' },
    example: { data: '49DC87', result: '34.780' },
    kind: 'number',
    size: 2
    "id": 89,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "59EA",
    "result": "-22"
    },
    "kind": "number",
    "size": 1,
    "signed": true
    },
    {
    id: 74,
    name: 'voltage',
    unit: { factor: 0.1, label: 'V' },
    example: { data: '4A020C', result: '307.4' },
    kind: 'number',
    size: 2
    "id": 90,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "5AEAEA",
    "result": "-5398"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    id: 75,
    name: 'gas',
    unit: { factor: 0.001, label: 'm3' },
    example: { data: '4B138A14', result: '1346.067' },
    kind: 'number',
    size: 3
    "id": 91,
    "name": "count",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "5BEA0234EA",
    "result": "-365690134"
    },
    "kind": "number",
    "size": 4,
    "signed": true
    },
    {
    id: 76,
    name: 'gas',
    unit: { factor: 0.001, label: 'm3' },
    example: { data: '4C41018A01', result: '25821.505' },
    kind: 'number',
    size: 4
    "id": 92,
    "name": "power",
    "unit": {
    "factor": 0.01,
    "label": "W"
    },
    "example": {
    "data": "5C02FBFFFF",
    "result": "-12.78"
    },
    "kind": "number",
    "size": 4,
    "signed": true
    },
    {
    id: 77,
    name: 'energy',
    unit: { factor: 0.001, label: 'kWh' },
    example: { data: '4d12138a14', result: '344593.170' },
    kind: 'number',
    size: 4
    "id": 93,
    "name": "current",
    "unit": {
    "factor": 0.001,
    "label": "A"
    },
    "example": {
    "data": "5D02EA",
    "result": "-5.63"
    },
    "kind": "number",
    "size": 2,
    "signed": true
    },
    {
    id: 78,
    name: 'volume',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '4E87562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    "id": 94,
    "name": "direction",
    "unit": {
    "factor": 0.01,
    "label": "°"
    },
    "example": {
    "data": "5E9F8C",
    "result": "359.99"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 79,
    name: 'water',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '4F87562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    {
    id: 80,
    name: 'timestamp',
    unit: { factor: 1, label: 's since epoch' },
    example: { data: '505d396164', result: '2023-05-14T19:41:17.000Z' },
    kind: 'epochS',
    size: 4
    },
    {
    id: 81,
    name: 'acceleration',
    unit: { factor: 0.001, label: 'm/s²' },
    example: { data: '518756', result: '22.151' },
    kind: 'number',
    size: 2
    },
    {
    id: 82,
    name: 'gyroscope',
    unit: { factor: 0.001, label: '°/s' },
    example: { data: '528756', result: '22.151' },
    kind: 'number',
    size: 2
    },
    {
    id: 83,
    name: 'text',
    example: { data: '530C48656C6C6F20576F726C6421', result: 'Hello World!' },
    size: -1,
    kind: 'utf8'
    },
    {
    id: 84,
    name: 'raw',
    example: {
    data: '540C48656C6C6F20576F726C6421',
    result: '48656c6c6f20576f726c6421'
    },
    size: -1,
    kind: 'hex'
    },
    {
    id: 85,
    name: 'volume storage',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '5587562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    {
    id: 86,
    name: 'conductivity',
    unit: { factor: 1, label: 'µS/cm' },
    example: { data: '56E803', result: '1000' },
    kind: 'number',
    size: 2
    },
    "id": 95,
    "name": "precipitation",
    "unit": {
    "factor": 0.1,
    "label": "mm"
    },
    "example": {
    "data": "5FA00F",
    "result": "400.0"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 87,
    name: 'temperature',
    unit: { factor: 1, label: '°C' },
    example: { data: '57EA', result: '-22' },
    kind: 'number',
    size: 1,
    signed: true
    "id": 96,
    "name": "channel",
    "unit": {
    "factor": 1,
    "label": ""
    },
    "example": {
    "data": "6001",
    "result": "01"
    },
    "kind": "number",
    "size": 1
    },
    {
    id: 88,
    name: 'temperature',
    unit: { factor: 0.35, label: '°C' },
    example: { data: '58EA', result: '-7.7' },
    kind: 'number',
    size: 1,
    signed: true
    "id": 240,
    "name": "device type id",
    "example": {
    "data": "F00100",
    "result": "1"
    },
    "kind": "number",
    "size": 2
    },
    {
    id: 89,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '59EA', result: '-22' },
    kind: 'number',
    size: 1,
    signed: true
    "id": 241,
    "name": "firmware version",
    "example": {
    "data": "F100010204",
    "result": "4.2.1.0"
    },
    "kind": "version",
    "size": 4
    },
    {
    id: 90,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '5AEAEA', result: '-5398' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 91,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '5BEA0234EA', result: '-365690134' },
    kind: 'number',
    size: 4,
    signed: true
    },
    {
    id: 92,
    name: 'power',
    unit: { factor: 0.01, label: 'W' },
    example: { data: '5C02FBFFFF', result: '-12.78' },
    kind: 'number',
    size: 4,
    signed: true
    },
    {
    id: 93,
    name: 'current',
    unit: { factor: 0.001, label: 'A' },
    example: { data: '5D02EA', result: '-5.63' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 94,
    name: 'direction',
    unit: { factor: 0.01, label: '°' },
    example: { data: '5E9F8C', result: '359.99' },
    kind: 'number',
    size: 2
    },
    {
    id: 95,
    name: 'precipitation',
    unit: { factor: 0.1, label: 'mm' },
    example: { data: '5FA00F', result: '400.0' },
    kind: 'number',
    size: 2
    },
    {
    id: 96,
    name: 'channel',
    unit: { factor: 1, label: '' },
    example: { data: '6001', result: '01' },
    kind: 'number',
    size: 1
    },
    {
    id: 240,
    name: 'device type id',
    example: { data: 'F00100', result: '1' },
    kind: 'number',
    size: 2
    },
    {
    id: 241,
    name: 'firmware version',
    example: { data: 'F100010204', result: '4.2.1.0' },
    kind: 'version',
    size: 4
    },
    {
    id: 242,
    name: 'firmware version',
    example: { data: 'F2000106', result: '6.1.0' },
    kind: 'version',
    size: 3
    {
    "id": 242,
    "name": "firmware version",
    "example": {
    "data": "F2000106",
    "result": "6.1.0"
    },
    "kind": "version",
    "size": 3
    }
    ]
  4. ddlsmurf revised this gist Aug 27, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion bthome_sensors.json
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    ]1337;ClearScrollback[
    [
    {
    id: 0,
    name: 'packet id',
  5. ddlsmurf created this gist Aug 27, 2025.
    697 changes: 697 additions & 0 deletions bthome_sensors.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,697 @@
    ]1337;ClearScrollback[
    {
    id: 0,
    name: 'packet id',
    example: { data: '0009', result: '9' },
    kind: 'number',
    size: 1
    },
    {
    id: 1,
    name: 'battery',
    unit: { factor: 1, label: '%' },
    example: { data: '0161', result: '97' },
    kind: 'number',
    size: 1
    },
    {
    id: 2,
    name: 'temperature',
    unit: { factor: 0.01, label: '°C' },
    example: { data: '02CA09', result: '25.06' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 3,
    name: 'humidity',
    unit: { factor: 0.01, label: '%' },
    example: { data: '03BF13', result: '50.55' },
    kind: 'number',
    size: 2
    },
    {
    id: 4,
    name: 'pressure',
    unit: { factor: 0.01, label: 'hPa' },
    example: { data: '04138A01', result: '1008.83' },
    kind: 'number',
    size: 3
    },
    {
    id: 5,
    name: 'illuminance',
    unit: { factor: 0.01, label: 'lux' },
    example: { data: '05138A14', result: '13460.67' },
    kind: 'number',
    size: 3
    },
    {
    id: 6,
    name: 'mass (kg)',
    unit: { factor: 0.01, label: 'kg' },
    example: { data: '065E1F', result: '80.3' },
    kind: 'number',
    size: 2
    },
    {
    id: 7,
    name: 'mass (lb)',
    unit: { factor: 0.01, label: 'lb' },
    example: { data: '073E1D', result: '74.86' },
    kind: 'number',
    size: 2
    },
    {
    id: 8,
    name: 'dewpoint',
    unit: { factor: 0.01, label: '°C' },
    example: { data: '08CA06', result: '17.38' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 9,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '0960', result: '96' },
    kind: 'number',
    size: 1
    },
    {
    id: 10,
    name: 'energy',
    unit: { factor: 0.001, label: 'kWh' },
    example: { data: '0A138A14', result: '1346.067' },
    kind: 'number',
    size: 3
    },
    {
    id: 11,
    name: 'power',
    unit: { factor: 0.01, label: 'W' },
    example: { data: '0B021B00', result: '69.14' },
    kind: 'number',
    size: 3
    },
    {
    id: 12,
    name: 'voltage',
    unit: { factor: 0.001, label: 'V' },
    example: { data: '0C020C', result: '3.074' },
    kind: 'number',
    size: 2
    },
    {
    id: 13,
    name: 'pm2.5',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '0D120C', result: '3090' },
    kind: 'number',
    size: 2
    },
    {
    id: 14,
    name: 'pm10',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '0E021C', result: '7170' },
    kind: 'number',
    size: 2
    },
    {
    id: 15,
    name: 'generic boolean',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    },
    {
    id: 16,
    name: 'power',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    },
    {
    id: 17,
    name: 'opening',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    },
    {
    id: 18,
    name: 'co2',
    unit: { factor: 1, label: 'ppm' },
    example: { data: '12E204', result: '1250' },
    kind: 'number',
    size: 2
    },
    {
    id: 19,
    name: 'tvoc',
    unit: { factor: 1, label: 'ug/m3' },
    example: { data: '133301', result: '307' },
    kind: 'number',
    size: 2
    },
    {
    id: 20,
    name: 'moisture',
    unit: { factor: 0.01, label: '%' },
    example: { data: '14020C', result: '30.74' },
    kind: 'number',
    size: 2
    },
    {
    id: 21,
    name: 'battery',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Low' ]
    },
    {
    id: 22,
    name: 'battery charging',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not Charging', 'Charging' ]
    },
    {
    id: 23,
    name: 'carbon monoxide',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not detected', 'Detected' ]
    },
    {
    id: 24,
    name: 'cold',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Cold' ]
    },
    {
    id: 25,
    name: 'connectivity',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Disconnected', 'Connected' ]
    },
    {
    id: 26,
    name: 'door',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    },
    {
    id: 27,
    name: 'garage door',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    },
    {
    id: 28,
    name: 'gas',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 29,
    name: 'heat',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Normal', 'Hot' ]
    },
    {
    id: 30,
    name: 'light',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'No light', 'Light detected' ]
    },
    {
    id: 31,
    name: 'lock',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Locked', 'Unlocked' ]
    },
    {
    id: 32,
    name: 'moisture',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Dry', 'Wet' ]
    },
    {
    id: 33,
    name: 'motion',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 34,
    name: 'moving',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not moving', 'Moving' ]
    },
    {
    id: 35,
    name: 'occupancy',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 36,
    name: 'plug',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Unplugged', 'Plugged in' ]
    },
    {
    id: 37,
    name: 'presence',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Away', 'Home' ]
    },
    {
    id: 38,
    name: 'problem',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'OK', 'Problem' ]
    },
    {
    id: 39,
    name: 'running',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Not Running', 'Running' ]
    },
    {
    id: 40,
    name: 'safety',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Unsafe', 'Safe' ]
    },
    {
    id: 41,
    name: 'smoke',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 42,
    name: 'sound',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 43,
    name: 'tamper',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Off', 'On' ]
    },
    {
    id: 44,
    name: 'vibration',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Clear', 'Detected' ]
    },
    {
    id: 45,
    name: 'window',
    kind: 'boolean',
    size: 1,
    boolLabels: [ 'Closed', 'Open' ]
    },
    {
    id: 46,
    name: 'humidity',
    unit: { factor: 1, label: '%' },
    example: { data: '2E23', result: '35' },
    kind: 'number',
    size: 1
    },
    {
    id: 47,
    name: 'moisture',
    unit: { factor: 1, label: '%' },
    example: { data: '2F23', result: '35' },
    kind: 'number',
    size: 1
    },
    {
    id: 58,
    name: 'button',
    size: 1,
    kind: 'trigger',
    enum: [
    [Array], [Array],
    [Array], [Array],
    [Array], [Array],
    [Array], [Array]
    ]
    },
    {
    id: 60,
    name: 'dimmer',
    size: 2,
    kind: 'trigger_with_count',
    enum: [ [Array], [Array], [Array] ]
    },
    {
    id: 61,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '3D0960', result: '24585' },
    kind: 'number',
    size: 2
    },
    {
    id: 62,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '3E2A2C0960', result: '1611213866' },
    kind: 'number',
    size: 4
    },
    {
    id: 63,
    name: 'rotation',
    unit: { factor: 0.1, label: '°' },
    example: { data: '3F020C', result: '307.4' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 64,
    name: 'distance (mm)',
    unit: { factor: 1, label: 'mm' },
    example: { data: '400C00', result: '12' },
    kind: 'number',
    size: 2
    },
    {
    id: 65,
    name: 'distance (m)',
    unit: { factor: 0.1, label: 'm' },
    example: { data: '414E00', result: '7.8' },
    kind: 'number',
    size: 2
    },
    {
    id: 66,
    name: 'duration',
    unit: { factor: 0.001, label: 's' },
    example: { data: '424E3400', result: '13.390' },
    kind: 'number',
    size: 3
    },
    {
    id: 67,
    name: 'current',
    unit: { factor: 0.001, label: 'A' },
    example: { data: '434E34', result: '13.39' },
    kind: 'number',
    size: 2
    },
    {
    id: 68,
    name: 'speed',
    unit: { factor: 0.01, label: 'm/s' },
    example: { data: '444E34', result: '133.90' },
    kind: 'number',
    size: 2
    },
    {
    id: 69,
    name: 'temperature',
    unit: { factor: 0.1, label: '°C' },
    example: { data: '451101', result: '27.3' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 70,
    name: 'UV index',
    unit: { factor: 0.1, label: '' },
    example: { data: '4632', result: '5.0' },
    kind: 'number',
    size: 1
    },
    {
    id: 71,
    name: 'volume',
    unit: { factor: 0.1, label: 'L' },
    example: { data: '478756', result: '2215.1' },
    kind: 'number',
    size: 2
    },
    {
    id: 72,
    name: 'volume',
    unit: { factor: 1, label: 'mL' },
    example: { data: '48DC87', result: '34780' },
    kind: 'number',
    size: 2
    },
    {
    id: 73,
    name: 'volume flow rate',
    unit: { factor: 0.001, label: 'm3/hr' },
    example: { data: '49DC87', result: '34.780' },
    kind: 'number',
    size: 2
    },
    {
    id: 74,
    name: 'voltage',
    unit: { factor: 0.1, label: 'V' },
    example: { data: '4A020C', result: '307.4' },
    kind: 'number',
    size: 2
    },
    {
    id: 75,
    name: 'gas',
    unit: { factor: 0.001, label: 'm3' },
    example: { data: '4B138A14', result: '1346.067' },
    kind: 'number',
    size: 3
    },
    {
    id: 76,
    name: 'gas',
    unit: { factor: 0.001, label: 'm3' },
    example: { data: '4C41018A01', result: '25821.505' },
    kind: 'number',
    size: 4
    },
    {
    id: 77,
    name: 'energy',
    unit: { factor: 0.001, label: 'kWh' },
    example: { data: '4d12138a14', result: '344593.170' },
    kind: 'number',
    size: 4
    },
    {
    id: 78,
    name: 'volume',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '4E87562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    {
    id: 79,
    name: 'water',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '4F87562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    {
    id: 80,
    name: 'timestamp',
    unit: { factor: 1, label: 's since epoch' },
    example: { data: '505d396164', result: '2023-05-14T19:41:17.000Z' },
    kind: 'epochS',
    size: 4
    },
    {
    id: 81,
    name: 'acceleration',
    unit: { factor: 0.001, label: 'm/s²' },
    example: { data: '518756', result: '22.151' },
    kind: 'number',
    size: 2
    },
    {
    id: 82,
    name: 'gyroscope',
    unit: { factor: 0.001, label: '°/s' },
    example: { data: '528756', result: '22.151' },
    kind: 'number',
    size: 2
    },
    {
    id: 83,
    name: 'text',
    example: { data: '530C48656C6C6F20576F726C6421', result: 'Hello World!' },
    size: -1,
    kind: 'utf8'
    },
    {
    id: 84,
    name: 'raw',
    example: {
    data: '540C48656C6C6F20576F726C6421',
    result: '48656c6c6f20576f726c6421'
    },
    size: -1,
    kind: 'hex'
    },
    {
    id: 85,
    name: 'volume storage',
    unit: { factor: 0.001, label: 'L' },
    example: { data: '5587562A01', result: '19551.879' },
    kind: 'number',
    size: 4
    },
    {
    id: 86,
    name: 'conductivity',
    unit: { factor: 1, label: 'µS/cm' },
    example: { data: '56E803', result: '1000' },
    kind: 'number',
    size: 2
    },
    {
    id: 87,
    name: 'temperature',
    unit: { factor: 1, label: '°C' },
    example: { data: '57EA', result: '-22' },
    kind: 'number',
    size: 1,
    signed: true
    },
    {
    id: 88,
    name: 'temperature',
    unit: { factor: 0.35, label: '°C' },
    example: { data: '58EA', result: '-7.7' },
    kind: 'number',
    size: 1,
    signed: true
    },
    {
    id: 89,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '59EA', result: '-22' },
    kind: 'number',
    size: 1,
    signed: true
    },
    {
    id: 90,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '5AEAEA', result: '-5398' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 91,
    name: 'count',
    unit: { factor: 1, label: '' },
    example: { data: '5BEA0234EA', result: '-365690134' },
    kind: 'number',
    size: 4,
    signed: true
    },
    {
    id: 92,
    name: 'power',
    unit: { factor: 0.01, label: 'W' },
    example: { data: '5C02FBFFFF', result: '-12.78' },
    kind: 'number',
    size: 4,
    signed: true
    },
    {
    id: 93,
    name: 'current',
    unit: { factor: 0.001, label: 'A' },
    example: { data: '5D02EA', result: '-5.63' },
    kind: 'number',
    size: 2,
    signed: true
    },
    {
    id: 94,
    name: 'direction',
    unit: { factor: 0.01, label: '°' },
    example: { data: '5E9F8C', result: '359.99' },
    kind: 'number',
    size: 2
    },
    {
    id: 95,
    name: 'precipitation',
    unit: { factor: 0.1, label: 'mm' },
    example: { data: '5FA00F', result: '400.0' },
    kind: 'number',
    size: 2
    },
    {
    id: 96,
    name: 'channel',
    unit: { factor: 1, label: '' },
    example: { data: '6001', result: '01' },
    kind: 'number',
    size: 1
    },
    {
    id: 240,
    name: 'device type id',
    example: { data: 'F00100', result: '1' },
    kind: 'number',
    size: 2
    },
    {
    id: 241,
    name: 'firmware version',
    example: { data: 'F100010204', result: '4.2.1.0' },
    kind: 'version',
    size: 4
    },
    {
    id: 242,
    name: 'firmware version',
    example: { data: 'F2000106', result: '6.1.0' },
    kind: 'version',
    size: 3
    }
    ]
    240 changes: 240 additions & 0 deletions generate_sensors_json.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,240 @@
    #!/usr/bin/env node

    function run_this_in_the_browser_and_pass_downloaded_json_path_as_first_argument() {
    // Visit https://bthome.io/format/

    (function tablesToJSON() {
    function downloadBlob(blob, mime) {
    if (typeof blob === "string") blob = new Blob([blob], { type: mime || "application/octet-stream" });
    const url = URL.createObjectURL(blob);
    window.location.assign(url);
    URL.revokeObjectURL(url);
    }
    const result = [...document.querySelectorAll("table")].map(function tableToJSON(table) {
    let headers;
    const rows = [];
    table.querySelectorAll("tr").forEach(row => {
    const dataRow = [...row.querySelectorAll("td")].map(x => x.innerText);
    if (dataRow.length)
    rows.push(dataRow);
    else {
    const isHeaders = [...row.querySelectorAll("th")].map(x => x.innerText);
    if (!isHeaders.length) console.error("Skipping unknown row", row);
    else if (headers) console.error("Ignoring after first header row", row, isHeaders)
    headers = isHeaders;
    }
    });
    return { headers, rows };
    });
    downloadBlob(JSON.stringify(result, null, 2));
    })();

    }

    if (!process.argv[2]) throw new Error(`Missing argument: the downloaded JSON file (see this source code)`);
    const tables = JSON.parse(require('fs').readFileSync(process.argv[2], "utf8"));

    const expectedHeaders = [
    'Object id,Property,Data type,Factor,Example,Result,Unit',
    'Object id,Property,Data type,Example,Result',
    'Object id,Device type,Event id,Event type,Event property,Example,Result',
    'Object id,Property,Data Type,Example,Result',
    'Object id,Property,Data Type,Example,Result'
    ];
    tables.forEach((t, i) => { if (t.headers.join(",") != expectedHeaders[i]) throw new Error(`Expected data type column ${JSON.stringify(t.headers)}`)});

    const numericSensors = tables[0];
    const binarySensors = tables[1];
    const eventSensors = tables[2];
    const deviceInfo = tables[3];
    const packetInfo = tables[4];

    const tablesWithDataTypes = [ numericSensors, binarySensors, deviceInfo, packetInfo ];

    const dataTypes = {
    'sint8 (1 byte)': { kind: 'number', size: 1, signed: true, },
    'sint16 (2 bytes)': { kind: 'number', size: 2, signed: true, },
    // sint24 is not supported, hoping it doesn't appear
    'sint32 (4 bytes)': { kind: 'number', size: 4, signed: true, },
    'uint8 (1 byte)': { kind: 'number', size: 1 }, // "uint8 (1 byte)" (the space after the number isn't normal)
    'uint16 (2 bytes)': { kind: 'number', size: 2 },
    'uint24 (3 bytes)': { kind: 'number', size: 3 },
    'uint32 (4 bytes)': { kind: 'number', size: 4 },
    "see below": { size: -1 },

    get(label) { return this[label.replace(/\s/g, ' ')] },
    }

    // Check all types are known
    tablesWithDataTypes.forEach(({rows}) => rows.forEach(row => {
    if (!dataTypes.get(row[2])) throw new Error(`Unknown data type ${JSON.stringify(row[2])}`)
    }));

    const parseHex = (str) => parseInt(/^0x([0-9a-f]{2})$/i.exec(str)[1], 16);

    const parseNumericSensors = (table) =>
    table.rows.map(row => {
    const dataType = dataTypes.get(row[2]);
    const common = {
    id: parseHex(row[0]),
    name: row[1],
    ...(
    row.length === 7
    ? {
    unit: { factor: parseFloat(row[3]), label: row[6], },
    example: { data: row[4].replace(/\s+/g, ''), result: row[5] },
    }
    : {
    example: { data: row[3].replace(/\s+/g, ''), result: row[4] },
    }
    ),
    ...dataType,
    };
    // numericSensors
    if (common.id === 80 && common.name === "timestamp") {
    common.unit = { factor: 1, label: "s since epoch" };
    common.example.result = "2023-05-14T19:41:17.000Z";
    common.kind = "epochS";
    } else if (common.id === 83 && common.name === "text") {
    delete common.unit;
    common.kind = "utf8";
    } else if (common.id === 84 && common.name === "raw") {
    delete common.unit;
    common.kind = "hex";
    common.example.result = common.example.result.replace(/\s+/g, '');
    }
    // deviceInfo
    else if ((common.id === 241 || common.id === 242) && common.name === "firmware version")
    common.kind = "version";
    return common;
    });

    const parseBinarySensors = (table) =>
    table.rows.map(row => {
    const dataType = dataTypes.get(row[2]);
    if (dataType.size !== 1 || dataType.signed) throw new Error(`Invalid for bool sensor`);
    return {
    id: parseHex(row[0]),
    name: row[1],
    ...dataType,
    boolLabels: [
    /False = ([^)]*)/i.exec(row[4])[1],
    /True = ([^)]*)/i.exec(row[4])[1],
    ],
    kind: 'boolean',
    };
    });

    const parseEventSensors = (table = eventSensors) => {
    const result = [];
    table.rows.forEach(row => {
    const enumValue = [ parseHex(row[2]), row[3] ];
    if (row[0]) {
    const size = (row[5].length / 2) - 1;
    result.push({
    id: parseHex(row[0]),
    name: row[1],
    size,
    kind: size === 1 ? 'trigger' : (size === 2 ? 'trigger_with_count' : `Unknown size in event sensor ${size}`),
    enum: [],
    })
    }
    result[result.length - 1].enum.push(enumValue);
    });
    return result;
    }

    const sensors = [].concat(
    parseNumericSensors(numericSensors),
    parseNumericSensors(deviceInfo),
    parseEventSensors(),
    parseNumericSensors(packetInfo),
    parseBinarySensors(binarySensors),
    );

    function byteLengthPrefixedStringFromBuffer(buffer, offset, encoding = 'utf8') {
    if (buffer.length < offset + 1) throw new Error(`Not enough data`);
    const size = buffer[offset];
    if (buffer.length < offset + size + 1) throw new Error(`Not enough data`);
    return [size + 1, buffer.subarray(offset + 1, offset + 1 + size).toString(encoding)];
    }
    function numberFromBuffer(buffer, offset, size, signed) {
    if (buffer.length < offset + size) throw new Error(`Not enough data`);
    const functionName = `read${signed ? 'I' : 'Ui'}nt${size * 8}${size > 1 ? 'LE' : ''}`;
    if (functionName === 'readInt24LE') throw new Error(`Signed 3 byte is not supported`);
    else if (functionName === 'readUint24LE') {
    let num = 0;
    for (let i = 2; i >= 0; i--)
    num = (num << 8) | buffer[offset + i];
    return num;
    } else
    return buffer[functionName](offset);
    }

    /** @returns [bytesRead, value, sensor] */
    function readSensor(buffer, offset) {
    const sensorID = buffer[offset++];
    const sensor = sensors.find(sensor => sensor.id === sensorID);
    if (!sensor) throw new Error(`Unknown sensor ID ${sensorID}`);
    switch (sensor.kind) {
    case "number":
    case "version":
    case "epochS":
    const number = numberFromBuffer(buffer, offset, sensor.size, sensor.signed);
    return [ sensor.size + 1, number, sensor ];
    case "hex":
    case "utf8":
    const [ bytesRead, value ] = byteLengthPrefixedStringFromBuffer(buffer, offset, sensor.kind);
    return [ bytesRead + 1, value, sensor ];
    case "trigger":
    case "trigger_with_count":
    const hasCount = sensor.kind === "trigger_with_count";
    const eventId = numberFromBuffer(buffer, offset, 1);
    const enumEntry = sensor.enum.find(e => e[0] === eventId);
    const result = { trigger: enumEntry ? enumEntry[1] : `0x${value.toString(16)}` };
    if (hasCount) result.arg = numberFromBuffer(buffer, offset + 1, 1);
    return [ hasCount ? 3 : 2, result, sensor ];
    case "boolean":
    return [ 2, numberFromBuffer(buffer, offset, 1) != 0, sensor ];
    default:
    throw new Error(`Unknown sensor value kind: ${JSON.stringify(sensor)}`);
    }
    }

    function getEnums() {
    const triggers = {};
    sensors.forEach(sensor => {
    if (sensor.enum) triggers[sensor.name] = sensor.enum;
    });
    return triggers;
    }

    /** @returns [bytesRead, formattedValue, value, sensor] */
    function formatReadSensor(buffer, offset) {
    const [ size, value, sensor ] = readSensor(buffer, offset);
    let formatted = value;
    const formatters = {
    number: () => sensor.unit ? value * sensor.unit.factor : value,
    epochS: () => new Date(value * 1000).toISOString(),
    version: () => new Array(sensor.size).fill(0).map((_, i) => (value >> (sensor.size - i - 1) * 8) & 0xff).join("."),
    boolean: () => sensor.boolLabels[value ? 1 : 0],
    };
    if (sensor.kind in formatters) formatted = formatters[sensor.kind]();
    return [ size, formatted, value, sensor ];
    }

    const areFloatsEqual = (a, b) => Math.abs(a - b) < 1e-6;
    // Check example.result against example.data when available
    sensors.forEach(sensorDUT => {
    if (!sensorDUT.example?.data) return;
    if (!sensorDUT.example?.result) return;
    const [size, formatted, value, sensor] = formatReadSensor(Buffer.from(sensorDUT.example.data, "hex"), 0);
    if (typeof formatted == "number") {
    if (!areFloatsEqual(formatted, parseFloat(sensorDUT.example?.result)))
    throw new Error(`Sensor example error, ${JSON.stringify(formatted)} != ${JSON.stringify(sensorDUT.example?.result)}`);
    } else if (formatted !== sensorDUT.example?.result)
    throw new Error(`Sensor example error, ${JSON.stringify(formatted)} != ${JSON.stringify(sensorDUT.example?.result)}`);
    });
    sensors.sort((a, b) => a.id - b.id);
    // sensors.sort((a, b) => a.name.localeCompare(b.name));
    console.error(sensors)