Last active
August 27, 2025 01:36
-
-
Save ddlsmurf/1d1ee3aedd4aee6d3b800490d3e6dde7 to your computer and use it in GitHub Desktop.
Revisions
-
ddlsmurf revised this gist
Aug 27, 2025 . No changes.There are no files selected for viewing
-
ddlsmurf revised this gist
Aug 27, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.log(JSON.stringify(sensors, null, 2)); -
ddlsmurf revised this gist
Aug 27, 2025 . 1 changed file with 1015 additions and 557 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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": 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 } ] -
ddlsmurf revised this gist
Aug 27, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ [ { id: 0, name: 'packet id', -
ddlsmurf created this gist
Aug 27, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 } ] This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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)