Skip to content

Instantly share code, notes, and snippets.

@ddlsmurf
Last active August 27, 2025 01:36
Show Gist options
  • Save ddlsmurf/1d1ee3aedd4aee6d3b800490d3e6dde7 to your computer and use it in GitHub Desktop.
Save ddlsmurf/1d1ee3aedd4aee6d3b800490d3e6dde7 to your computer and use it in GitHub Desktop.
BTHome javascript definitions (see https://bthome.io/format/ )
[
{
"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": [
[
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": 60,
"name": "dimmer",
"size": 2,
"kind": "trigger_with_count",
"enum": [
[
0,
"None"
],
[
1,
"rotate left"
],
[
2,
"rotate right"
]
]
},
{
"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
}
]
#!/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.log(JSON.stringify(sensors, null, 2));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment