-
Notifications
You must be signed in to change notification settings - Fork 3
Sensor Configuration Reference
Every sensor is described by a JSON object. The default list lives in sensors.json; you can also load your own file through Custom JSON Sensor Configuration in the UI.
- Connect → serial port opened with the
portsettings. -
command(or thecommandssequence) is sent. - Incoming bytes are buffered.
- A frame is detected using
startByte,endByteandlength(after un-stuffing, ifstuffingis set). -
checksum.evalis computed and compared withchecksum.compare. - If they match, every
dataexpression is evaluated, charts are updated, the row is stored for CSV, and the packet is logged with ✅. Otherwise it is logged with ❌.
{
"sensors": [
{ "...": "sensor object" }
]
}| Field | Required | Type | Description |
|---|---|---|---|
name |
yes | string | Unique name shown in the dropdown |
inherits_from |
no | string | Name of another sensor to copy settings from — see Inheritance |
command |
no | string | Hex string sent on connection, e.g. "7E 00 03 00 FC 7E", or "none"
|
send_cmd_period |
no | number |
0 = send command once; > 0 = resend every N seconds |
start_command |
no | string | Hex string sent after connect. Classic mode only — ignored if commands is present |
stop_command |
no | string | Hex string sent on disconnect. Works in both classic and multi-command modes |
commands |
no | array | Command sequence — see Multi-Command Sensors |
port |
yes | object | Serial settings |
frame |
yes | object | Frame detection |
checksum |
yes* | object | Checksum validation |
data |
yes* | object | Signal definitions |
* In multi-command mode, frame, checksum and data can be defined per command instead.
| Field | Type | Example |
|---|---|---|
baudRate |
integer |
9600, 19200, 115200
|
dataBits |
integer | 8 |
stopBits |
integer | 1 |
parity |
string |
"none", "even", "odd"
|
| Field | Required | Description |
|---|---|---|
startByte |
yes | One or more start bytes: [66, 77], ["0x42", "0x4D"], 170, "0xAA", or "none"
|
endByte |
yes | Terminator, same formats as startByte
|
length |
yes | Total frame length including start/end bytes. With byte-stuffing, this is the length after un-stuffing |
stuffing |
no | Pairs of sequence to find → replacement, e.g. [["7D 5E", "0x7E"], ["7D 5D", "0x7D"]]
|
Both fields are JavaScript expressions where data[i] is the i-th byte of the frame (a normal JS array, so slice, reduce etc. work).
| Field | Description | Example |
|---|---|---|
eval |
Computes the checksum | "data.slice(0, 30).reduce((a, b) => (a + b) & 0xFFFF, 0)" |
compare |
The value it must equal | "(data[30] << 8) + data[31]" |
Common patterns:
| Algorithm | eval |
|---|---|
| 16-bit sum | data.slice(0, N).reduce((a, b) => (a + b) & 0xFFFF, 0) |
| 8-bit sum | data.slice(1, N).reduce((a, b) => (a + b) & 0xFF, 0) |
| XOR | data.slice(1, N).reduce((a, b) => a ^ b, 0) |
| Two's complement | (0x100 - (data.slice(1, N).reduce((a, b) => a + b, 0) & 0xFF)) & 0xFF |
| Sensirion SHDLC (SPS30, SEN55) |
0xFF-(data.slice(1, N).reduce((a,b)=>a+b,0)&0xFF) — see SEN5x UART Protocol
|
Each key is a signal name (shown in the UI and used as the CSV column).
"data": {
"PM2.5": {
"value": "(data[6] << 8) + data[7]",
"unit": "μg/m³"
}
}| Field | Required | Description |
|---|---|---|
value |
yes | JavaScript expression evaluated against the frame bytes |
unit |
yes | Unit label, e.g. "μg/m³", "ppm", "%", "°C"
|
Useful expression patterns:
| Need | Expression |
|---|---|
| Unsigned 16-bit big-endian | (data[i] << 8) + data[i+1] |
| Unsigned 16-bit little-endian | data[i] + (data[i+1] << 8) |
| Unsigned 32-bit (force unsigned) | ((data[i] << 24) + (data[i+1] << 16) + (data[i+2] << 8) + data[i+3]) >>> 0 |
| Signed 16-bit | ((data[i] << 8) | data[i+1]) << 16 >> 16 |
| Scaled value | ((data[i] << 8) + data[i+1]) / 10 |
| Calibration factor | ((data[6] << 8) + data[7]) * 0.4 |
| IEEE-754 float (big-endian) | new DataView(new Uint8Array(data.slice(i, i+4)).buffer).getFloat32(0) |
For frames with many 16- or 32-bit values, the Sensirion configs create one DataView in the first signal and reuse it in the others:
"data": {
"PM1.0": { "value": "(dv=new DataView(new Uint8Array(data).buffer),dv.getUint16(5,false)/10)", "unit": "µg/m³" },
"PM2.5": { "value": "dv.getUint16(7,false)/10", "unit": "µg/m³" },
"RH": { "value": "dv.getInt16(13,false)/100", "unit": "%RH" }
}-
getUint16/getInt16/getFloat32(offset, false)read big-endian values; passtruefor little-endian. - The signal that defines
dvmust come first indata, because signals are evaluated in order. - Offsets are positions in the full un-stuffed frame, including the start byte.
- Use decimal (
66) or hex strings ("0x42"). - Raw hex literals like
0x42are not valid JSON and will fail to load.
{
"sensors": [
{
"name": "Honeywell HPMA115S0-XXX",
"command": "none",
"port": { "baudRate": 9600, "dataBits": 8, "stopBits": 1, "parity": "none" },
"frame": { "length": 32, "startByte": [66, 77], "endByte": "none" },
"data": {
"PM2.5": { "value": "(data[6] << 8) + data[7]", "unit": "μg/m³" },
"PM10": { "value": "(data[8] << 8) + data[9]", "unit": "μg/m³" }
},
"checksum": {
"eval": "data.slice(0, 30).reduce((a, b) => (a + b) & 0xFFFF, 0)",
"compare": "(data[30] << 8) + data[31]"
}
}
]
}A second template is in the repo: custom_example.json.
🔒 Security note:
value,evalandcompareare executed with JavaScripteval(). Only load JSON files you trust.
polluSensWeb · MIT License · Repository · Report an issue
Using the app
Sensor definitions
Sensor notes
Deployment
Help