Seeed Studio SenseCAP S210x Payload Decoder

Decode Seeed SenseCAP S210x LoRaWAN payloads online: temperature, humidity, light, CO2, soil moisture and EC from the 7-byte measurement-ID frames shared by the whole S2101–S2110 series. Includes verified examples and a decodeUplink codec for ChirpStack v4 and The Things Stack.

Type: Sensor series: S2101 T&H · S2102 light · S2103 CO2 · S2104/S2105 soil · S2110 LoRaWAN class: Class A FPort: 1 Payload: N × 7 bytes + CRC Endianness: Little-endian

Example payloads

01011098530000010210A87A0000AF51 S2101: 21.4 °C · 31.4 %RH (official TTN repository test vector)
010110B068000001021088F400008CFF S2101: 26.8 °C · 62.6 %RH (example from the official user guide)
00070064000500010610B45F0000010710A41F00003259 S2104: battery 100 % · interval 5 min · soil 24.5 °C · moisture 8.1 % (user guide)

Payload structure

Bytes Field Type / scale Description
frame byte 0 Channel uint8 0x01 = sensor measurements; 0x00 = system frames (battery, versions, device info).
frame bytes 1–2 Measurement ID uint16 LE 0x1001 air temperature (°C) · 0x1002 air humidity (%RH) · 0x1003 light (lux) · 0x1004 CO2 (ppm) · 0x1006 soil temperature (°C) · 0x1007 soil moisture (%) · 0x100C soil EC (dS/m).
frame bytes 3–6 Value int32 LE ÷ 1000 Signed two's complement — negative temperatures are supported. On-wire value is the physical value × 1000.
ID 0x0007 Battery + interval 2 × uint16 LE System frame sent roughly every 20 uplinks: battery % and upload interval in minutes.
last 2 bytes CRC-16 checksum Packet checksum. The official decoders do not verify it; this tool displays it in the trace.

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 names = {
    4097: "air_temperature", 4098: "air_humidity", 4099: "light",
    4100: "co2", 4101: "pressure", 4102: "soil_temperature",
    4103: "soil_moisture", 4108: "soil_ec"
  };
  if (b.length < 9 || (b.length - 2) % 7 !== 0) {
    return { errors: ["invalid S210x frame length"] };
  }
  var data = {};
  for (var o = 0; o + 7 <= b.length - 2; o += 7) {
    var id = b[o + 1] | (b[o + 2] << 8);
    var raw = b[o + 3] | (b[o + 4] << 8) | (b[o + 5] << 16) | (b[o + 6] << 24);
    if (id > 0x1000) {
      data[names[id] || ("measurement_" + id)] = raw / 1000;
    } else if (id === 7) {
      data.battery = b[o + 3] | (b[o + 4] << 8);
      data.interval_min = b[o + 5] | (b[o + 6] << 8);
    } // other system IDs (versions, EUI) are ignored
  }
  return { data: data };
}

About the S210x frame format

All SenseCAP S210x sensors — S2101 (temperature & humidity), S2102 (light), S2103 (CO2), S2104/S2105 (soil) and the S2110 family — share one payload format, which is why a single decoder covers the whole series. Each uplink is a sequence of 7-byte frames plus a 2-byte CRC:

[channel 1B] [measurement ID 2B LE] [value 4B int32 LE] ... [CRC 2B]

Practical notes:

  • Everything is little-endian, including the measurement ID: the on-wire bytes 01 10 mean ID 0x1001 (air temperature).
  • All values are ×1000. Divide the int32 by 1000: 26800 → 26.8 °C. Negative temperatures use standard two’s complement.
  • A battery frame appears periodically (channel 0x00, ID 0x0007) about every 20 uplinks, carrying battery % and the upload interval in minutes — don’t treat it as a broken measurement.
  • Device-info frames after join (IDs 0x0000–0x0009) carry versions and the sensor EUI; decoders can safely skip them.
  • The trailing CRC-16 is not verified by the official decoders; it is shown in this tool’s trace for completeness.

References

  • SenseCAP S210X Sensors User Guide (packet structure and worked examples) — Seeed Studio
  • Official decoder: github.com/Seeed-Solution/SenseCAP-Decoder, S210X/
  • Measurement ID list: sensecap-docs.seeed.cc/measurement_list.html