Milesight GS301: LoRaWAN Odor Detector (NH3 & H2S)

Milesight GS301 LoRaWAN odor detector: own ChirpStack/ThingsBoard decoder for NH3, H2S, temperature and humidity, with decoded example.

Milesight GS301
GS301Sensor
LoRaWAN
Class A, OTAA
Band / port
EU868 / port 85
NH3 range
0 to 10 ppm, accuracy +/- 5%
H2S range
0 to 5 ppm, accuracy +/- 5%
Sensing principle
Solid polymer electrochemical
Local alarm
LED light and buzzer on threshold
Configuration
NFC (Milesight ToolBox)
Measurements

What the GS301 measures

Ammonia (NH3)

Electrochemical cell, 0 to 10 ppm, reported as ppm (raw / 100).

Hydrogen sulfide (H2S)

Electrochemical cell, 0 to 5 ppm, reported as ppm (channel 05, raw / 100 up to firmware v1.1; channel 06, raw / 1000 from v1.2).

Temperature

Ambient temperature in degrees Celsius (INT16, /10).

Humidity

Relative humidity 0 to 100 % RH (raw / 2).

Battery level

Reported periodically with local storage and retransmission.

Data into your dashboard

Integration

Sensor / controller

Measures or controls in the field and sends LoRaWAN uplinks.

LoRaWAN gateway

Receives the radio packets and forwards them to the server.

ChirpStack

Network server: manages sessions and decodes the payload.

ThingsBoard / Grafana

Dashboards, alarms, rules and reports.

ChirpStack v4 · decodeUplink
function decodeUplink(input) {
  var bytes = input.bytes;
  var data = {};

  for (var i = 0; i < bytes.length; ) {
    var channel = bytes[i++];
    var type = bytes[i++];

    if (channel === 0x01 && type === 0x75) {          // battery (%)
      data.battery = bytes[i]; i += 1;
    } else if (channel === 0x02 && type === 0x67) {   // temperature (degC)
      data.temperature = readInt16LE(bytes, i) / 10; i += 2;
    } else if (channel === 0x03 && type === 0x68) {   // humidity (%RH)
      data.humidity = bytes[i] / 2; i += 1;
    } else if (channel === 0x04 && type === 0x7d) {   // NH3 (ppm)
      var nh3 = readUInt16LE(bytes, i); i += 2;
      if (nh3 === 0xfffe || nh3 === 0xffff) {
        data.nh3_status = nh3 === 0xffff ? "device_error" : "polarizing";
      } else {
        data.nh3 = nh3 / 100;
      }
    } else if (channel === 0x05 && type === 0x7d) {   // H2S (ppm), firmware <= v1.1
      var h2s = readUInt16LE(bytes, i); i += 2;
      if (h2s === 0xfffe || h2s === 0xffff) {
        data.h2s_status = h2s === 0xffff ? "device_error" : "polarizing";
      } else {
        data.h2s = h2s / 100;
      }
    } else if (channel === 0x06 && type === 0x7d) {   // H2S (ppm), firmware >= v1.2
      var h2s12 = readUInt16LE(bytes, i); i += 2;
      if (h2s12 === 0xfffe || h2s12 === 0xffff) {
        data.h2s_status = h2s12 === 0xffff ? "device_error" : "polarizing";
      } else {
        data.h2s = h2s12 / 1000;
      }
    } else {
      break;
    }
  }
  return { data: data };
}

function readUInt16LE(b, i) {
  return (b[i + 1] << 8) | b[i];
}

function readInt16LE(b, i) {
  var v = readUInt16LE(b, i);
  return v > 0x7fff ? v - 0x10000 : v;
}

Implemented from the published Milesight byte specification (Communication Protocol / User Guide).

Channel format: 01 75 battery (%), 02 67 temperature (INT16 little-endian, /10), 03 68 humidity (/2), 04 7d NH3 (UINT16 little-endian, /100, ppm). H2S moved channel between firmware revisions: 05 7d (/100, ppm) up to v1.1 and 06 7d (/1000, ppm) from v1.2, so the decoder handles both. The raw values 0xFFFE and 0xFFFF on a gas channel are not real readings: 0xFFFE means the electrochemical cell is still polarizing and 0xFFFF flags a device error, so the decoder maps them to a status field. Implemented from the published Milesight byte specification. For ThingsBoard the same logic goes into an uplink converter.

Uplink (hex)

01756402670A01036864047D0000057D0100

Decoded JSON

{ "battery": 100, "temperature": 26.6, "humidity": 50, "nh3": 0, "h2s": 0.01 }
From the field

Configuration & pitfalls

NFC setup

Keys, reporting interval and NH3/H2S thresholds are set over NFC with the Milesight ToolBox before rollout.

Warm-up period

Electrochemical cells need a warm-up after power-on. Early uplinks may carry a status flag instead of a reading; suppress alarms until valid values arrive.

Calibration drift

Electrochemical gas cells drift over time. Schedule periodic calibration with the ToolBox and document the offset so dashboard readings stay traceable.

Threshold alarms & D2D

When NH3 or H2S crosses the threshold the device triggers a local LED and buzzer and can fire a D2D command to a ventilation relay without a gateway. Account for D2D events in monitoring.

Your partner

How merkaio supports your GS301

From sourcing to day-to-day operation, all from one partner on our own European infrastructure.

Pre-staging & provisioning

We configure the GS301, set keys, intervals and alarms, and ship it ready to deploy.

Own decoder

Payload codec for ChirpStack v4 and ThingsBoard, implemented from the Milesight specification.

Dashboard integration

Data lands in your ThingsBoard or Grafana, with alarms and reports.

Operations & monitoring

We run the LoRaWAN stack and dashboards on European infrastructure, you just use the data.

Frequently asked questions

Yes. It is a standard LoRaWAN device, no Milesight gateway or cloud required. You add the codec to the device profile and provision it via OTAA.
Yes, for both ChirpStack and ThingsBoard, implemented from the published Milesight byte specification. The same channel logic goes into a ThingsBoard uplink converter.
It measures ammonia (NH3) from 0 to 10 ppm and hydrogen sulfide (H2S) from 0 to 5 ppm with a solid-polymer electrochemical cell, plus ambient temperature and humidity for full context.
It is a Class A device on LoRaWAN port 85. Each uplink can carry battery, temperature, humidity and the two gas channels.
They are not real concentrations. 0xFFFE means the electrochemical cell is still polarizing after power-on and 0xFFFF signals a device error or fault, so the decoder maps them to a status field instead of a ppm value.
Up to firmware v1.1 the GS301 reports H2S on channel 05 with a /100 scaling. From v1.2 it moved H2S to channel 06 with a finer /1000 scaling for better low-range resolution. Our decoder handles both, so the dashboard shows correct ppm regardless of the firmware on the unit.
Yes. When NH3 or H2S reaches the configured threshold the detector triggers an LED light and a buzzer locally, and it can also send an alarm uplink and a D2D command to a ventilation device without a gateway.
From the same series

Related devices

Let's discuss your infrastructure. Digital and on-site.

Whether it's IoT platform development, hardware selection, managed hosting for ChirpStack, ThingsBoard, Grafana or NetBird VPN, or migration from a self-hosted setup - we'll find the right solution for your use case. Book a free 30-minute consultation, no commitment required.

Timo Wevelsiep

Your contact

Timo Wevelsiep

Founder, merkaio

15 minutes, no commitment, directly with Timo.

By submitting, you agree to our Privacy Policy.

Decoder for ChirpStack v4. merkaio is an independent integrator and is not affiliated with Milesight.