LoRa/TTN Payload¶
Overview¶
Since firmware X.X the MultiGeiger sends Geiger + environmental data in one combined uplink on Port 1 with a fixed length of 18 bytes.
Payload format¶
Port |
Byte(s) |
Hex |
Description |
Example |
|---|---|---|---|---|
1 |
0/1/2/3 |
00000107 |
Number of counts |
263 |
1 |
4/5/6 |
0249F0 |
Measurement time in ms ( |
150000 |
1 |
7/8 |
10C0 |
Software version ( |
1.12.0 |
1 |
9 |
16 |
Geiger tube ID ( |
Si22G |
1 |
10/11 |
0107 |
BMP/BME temperature x10 ( |
0x0107 = 263 -> 26.3 C |
1 |
12 |
9A |
BME humidity x2 ( |
0x9A = 154 -> 77.0% (0 for BMP280) |
1 |
13/14 |
2794 |
BMP/BME pressure x10 ( |
0x2794 = 10132 -> 1013.2 hPa |
1 |
15 |
01 |
THP sensor type |
0=no sensor, 1=BMP280, 2=BME280, 3=BME680 |
1 |
16/17 |
0096 |
Gas resistance in kOhm (BME680 only) |
0x0096 = 150 kOhm (0 for BMP280/BME280) |
Notes¶
If no THP sensor (BMP280/BME280/BME680) is present, bytes 10-17 are all
0x00and byte 15 is0x00.Software version encoding: top 4 bits are the major version (max 15), next 8 bits are minor, lowest 4 bits are patch.
Implemented tube identifiers: SBM-19 (0x13), SBM-20 (0x14), Si22G (0x16).
Supported THP sensors:
BMP280 (type 1): temperature + pressure only (humidity = 0, gas = 0)
BME280 (type 2): temperature + humidity + pressure (gas = 0)
BME680 (type 3): temperature + humidity + pressure + gas resistance
TTN decoder¶
To decode the payload in the TTN Console, add this JavaScript under Payload formatters -> Uplink:
function decodeUplink(input) {
const bytes = input.bytes;
const fPort = parseInt(input.fPort) || 0;
if (fPort !== 1 || bytes.length !== 18) {
return {
data: {},
warnings: [`fPort: ${fPort}, bytes: ${bytes.length} (expected: fPort 1, 18 bytes)`],
errors: []
};
}
// Geiger data (bytes 0-9)
const COUNTS = bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
const DT_MS = bytes[4] * 0x10000 + bytes[5] * 0x100 + bytes[6];
const SW_VERSION = bytes[7] * 0x100 + bytes[8];
const TUBE_NBR = bytes[9];
// THP data (bytes 10-14)
let TEMP_RAW = bytes[10] * 0x100 + bytes[11];
if (TEMP_RAW & 0x8000) TEMP_RAW = TEMP_RAW - 0x10000; // signed
const TEMP_C = TEMP_RAW !== 0 ? (TEMP_RAW / 10.0) : null;
const HUMIDITY_RAW = bytes[12];
const HUMIDITY_PERCENT = HUMIDITY_RAW > 0 ? (HUMIDITY_RAW / 2.0) : null;
const PRESSURE_RAW = bytes[13] * 0x100 + bytes[14];
const PRESSURE_HPA = PRESSURE_RAW > 0 ? (PRESSURE_RAW / 10.0) : null;
// Sensor type (byte 15)
const SENSOR_TYPE_RAW = bytes[15];
const SENSOR_TYPES = ['none', 'BMP280', 'BME280', 'BME680'];
const SENSOR_TYPE = SENSOR_TYPES[SENSOR_TYPE_RAW] || 'unknown';
// Gas resistance (bytes 16-17, BME680 only)
const GAS_RAW = bytes[16] * 0x100 + bytes[17];
const GAS_KOHM = GAS_RAW > 0 ? GAS_RAW : null;
// Calculate radiation values
const CPS = DT_MS > 0 ? COUNTS / (DT_MS / 1000) : 0;
const CPM = DT_MS > 0 ? Math.round(COUNTS * 60000 / DT_MS * 10) / 10 : 0;
const USVH = CPS / 12.2792; // Si22G factor
const result = {
counts: COUNTS,
cpm: CPM,
cps: Math.round(CPS * 100) / 100,
uSvph: Number(USVH.toFixed(3)),
sample_time_ms: DT_MS,
tube_number: TUBE_NBR,
sw_version: `V${(SW_VERSION >> 12) & 0x0F}.${(SW_VERSION >> 4) & 0xFF}.${SW_VERSION & 0x0F}`,
sensor_type: SENSOR_TYPE
};
// Add THP data if sensor available (sensor_type > 0)
if (SENSOR_TYPE_RAW > 0) {
if (TEMP_C !== null) result.temperature_C = TEMP_C;
if (HUMIDITY_PERCENT !== null) result.humidity_percent = HUMIDITY_PERCENT;
if (PRESSURE_HPA !== null) result.pressure_hPa = PRESSURE_HPA;
if (SENSOR_TYPE_RAW === 3 && GAS_KOHM !== null) {
result.gas_resistance_kOhm = GAS_KOHM;
}
}
return { data: result, warnings: [], errors: [] };
}
Decoded fields¶
counts: GM tube counts in the interval
cpm: counts per minute (from counts and
sample_time_ms)cps: counts per second
uSvph: dose rate in uSv/h (Si22G conversion factor)
sample_time_ms: measurement interval in ms
tube_number: tube ID (0x13=SBM-19, 0x14=SBM-20, 0x16=Si22G)
sw_version: firmware version string
sensor_type:
none,BMP280,BME280, orBME680temperature_C: temperature (C, if sensor)
humidity_percent: humidity (%, if sensor)
pressure_hPa: pressure (hPa, if sensor)
gas_resistance_kOhm: gas resistance (kOhm, BME680 only)
HTTP integration¶
HTTP integration to sensor.community is provided via the webhook service https://ttn2luft.citysensor.de. The LoRa payload is transformed server side into the sensor.community format.