Milesight EM300-TH Payload Decoder
Decode Milesight EM300-TH LoRaWAN payloads online: battery, temperature and humidity from the channel/type TLV format. Includes payload structure, verified examples and a decodeUplink codec for ChirpStack v4 and The Things Stack.
Example payloads
01756403671001046865
Battery 100 % · 27.2 °C · 50.5 %
0367 9BFF 0468 5C
Freezer reading: −10.1 °C · 46 % (no battery field in this uplink)
Payload structure
| Bytes | Field | Type / scale | Description |
|---|---|---|---|
| x, x+1 | Channel + type header | uint8 + uint8 | Every measurement starts with a channel byte and a type byte, followed by its value. Fields can appear in any order. |
| 01 75 + 1 byte | Battery | uint8 → % | Battery level in percent. Example: 0x64 = 100 %. |
| 03 67 + 2 bytes | Temperature | int16 LE ÷ 10 → °C | Little-endian and signed. Example: bytes 10 01 = 0x0110 = 272 → 27.2 °C; bytes 9B FF = −101 → −10.1 °C. |
| 04 68 + 1 byte | Humidity | uint8 ÷ 2 → %RH | Half-percent resolution. Example: 0x65 = 101 → 50.5 %. |
Ready-to-use codec
ChirpStack v4 and The Things Stack share the same decodeUplink(input) signature:
paste this into the device profile codec (ChirpStack) or the
payload formatter (TTN).
function decodeUplink(input) {
var b = input.bytes;
var data = {};
var i = 0;
while (i < b.length - 1) {
var ch = b[i], type = b[i + 1];
i += 2;
if (ch === 0x01 && type === 0x75) { // battery, %
data.battery_pct = b[i]; i += 1;
} else if (ch === 0x03 && type === 0x67) { // temperature, int16 LE, 0.1 C
var t = b[i] | (b[i + 1] << 8);
if (t >= 0x8000) t -= 0x10000;
data.temperature_c = t / 10; i += 2;
} else if (ch === 0x04 && type === 0x68) { // humidity, 0.5 %
data.humidity_pct = b[i] / 2; i += 1;
} else {
break; // unknown channel/type: stop to avoid misreading offsets
}
}
return { data: data };
}
About the EM300-TH uplink
The Milesight EM300-TH reports on FPort 85 using Milesight’s channel/type TLV format: each measurement is announced by a channel byte and a type byte, followed by its value. Because of that, uplinks have variable length and fields can appear in any order — a robust decoder walks the buffer instead of using fixed offsets.
Points that commonly break third-party decoders:
- Values are little-endian, unlike most LoRaWAN sensors. The temperature
bytes
10 01mean0x0110(272), not0x1001(4097). - Temperature is signed. Cold-chain deployments will produce negative values; two’s complement handling is required.
- Unknown channels should stop the parser. Milesight devices can emit additional TLV fields (device info, history). Skipping bytes blindly after an unknown header corrupts every following field, so the codec on this page stops instead.
References
- Milesight EM300 series payload documentation — milesight-iot.com
- Milesight publishes official decoders on GitHub; this page adds the byte-level explanation and a minimal, readable implementation.
More device decoders
Building your own LoRaWAN network? Get Yubox Gateway OS free, or learn hands-on in the LoRaWAN Master Training.