Milesight AM319: 9-in-1 LoRaWAN Air Quality Sensor with E-Ink

Milesight AM319 9-in-1 LoRaWAN IAQ sensor with E-Ink display: own ChirpStack/ThingsBoard decoder, decoded example, CO2, PM and HCHO monitoring.

Milesight AM319
AM319Sensor
LoRaWAN
Class A, OTAA
Band / port
EU868 / port 85
Display
4.2 inch E-Ink, traffic-light status
CO2 sensor
NDIR, 0 to 5000 ppm
Particulates
PM2.5 and PM10, laser scatter
Power
Battery or USB Type-C
Configuration
NFC (Milesight ToolBox)
Measurements

What the AM319 measures

CO2

NDIR sensor, 0 to 5000 ppm, with ABC or manual calibration.

Temperature & humidity

Comfort readings, temperature INT16 little-endian /10, humidity /2.

TVOC

Reported as an IAQ index (/100) or in micrograms per cubic metre, switchable.

HCHO and PM

Formaldehyde in mg/m³ (/100), PM2.5 and PM10 in micrograms per cubic metre.

Pressure, light, motion

Barometric pressure (/10), light level and a PIR occupancy flag.

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 === 0x03 && type === 0x67) {   // temperature (°C)
      data.temperature = readInt16LE(bytes, i) / 10; i += 2;
    } else if (channel === 0x04 && type === 0x68) {   // humidity (%RH)
      data.humidity = bytes[i] / 2; i += 1;
    } else if (channel === 0x05 && type === 0x00) {   // PIR occupancy
      data.pir = bytes[i] === 1 ? "trigger" : "idle"; i += 1;
    } else if (channel === 0x06 && type === 0xcb) {   // light level (0-10)
      data.light_level = bytes[i]; i += 1;
    } else if (channel === 0x07 && type === 0x7d) {   // CO2 (ppm)
      data.co2 = readUInt16LE(bytes, i); i += 2;
    } else if (channel === 0x08 && type === 0x7d) {   // TVOC (IAQ index, /100)
      data.tvoc = readUInt16LE(bytes, i) / 100; i += 2;
    } else if (channel === 0x08 && type === 0xe6) {   // TVOC (µg/m³)
      data.tvoc = readUInt16LE(bytes, i); i += 2;
    } else if (channel === 0x09 && type === 0x73) {   // pressure (hPa, /10)
      data.pressure = readUInt16LE(bytes, i) / 10; i += 2;
    } else if (channel === 0x0a && type === 0x7d) {   // HCHO (mg/m³, /100)
      data.hcho = readUInt16LE(bytes, i) / 100; i += 2;
    } else if (channel === 0x0b && type === 0x7d) {   // PM2.5 (µg/m³)
      data.pm2_5 = readUInt16LE(bytes, i); i += 2;
    } else if (channel === 0x0c && type === 0x7d) {   // PM10 (µg/m³)
      data.pm10 = readUInt16LE(bytes, i); i += 2;
    } else {
      break;
    }
  }
  return { data: data };
}

function readUInt16LE(b, i) {
  return ((b[i + 1] << 8) | b[i]) & 0xffff;
}
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, 03 67 temperature (INT16 little-endian, /10), 04 68 humidity (/2), 07 7d CO2 (ppm), 08 7d TVOC as IAQ index (/100) or 08 e6 TVOC in micrograms per cubic metre, 09 73 pressure (/10), 0a 7d HCHO (/100), 0b/0c 7d PM2.5/PM10. The TVOC channel switches between IAQ index and micrograms per cubic metre depending on the configured unit. The decoder is implemented from the published Milesight byte specification. Stored history frames (channels 20/21, type ce) and downlink responses use longer records, add those branches if you enable history retransmission. The same channel logic goes into a ThingsBoard uplink converter.

Uplink (hex)

0367E80004685D077D3002087D6400097390270B7D1900

Decoded JSON

{ "temperature": 23.2, "humidity": 46.5, "co2": 560, "tvoc": 1, "pressure": 1012.8, "pm2_5": 25 }
From the field

Configuration & pitfalls

CO2 calibration

Choose ABC for unattended offices or manual/background calibration for sealed rooms. Document which mode you set so dashboard values stay traceable.

TVOC unit

TVOC is sent either as an IAQ index or in micrograms per cubic metre. Decide the unit before rollout, because the byte type changes (7d vs e6) and your decoder branch must match.

Power and warm-up

Battery extends runtime, but the PM laser and CO2 sensor draw more current. For dense reporting or continuous PM sampling, power the AM319 over USB Type-C.

E-Ink and child lock

The E-Ink display and on-device buttons are configurable over NFC. Enable the child lock in public spaces so the screen elements and buzzer cannot be changed at the device.

Your partner

How merkaio supports your AM319

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

Pre-staging & provisioning

We configure the AM319, 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 Class A 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.
Up to nine parameters: CO2, temperature, humidity, TVOC, HCHO, PM2.5, PM10, barometric pressure and light, plus a PIR motion flag. Readings are also shown on the E-Ink display.
The AM319 can report TVOC as an IAQ index (channel type 7d, divided by 100) or in micrograms per cubic metre (channel type e6). The unit is set over NFC, so make sure your decoder branch matches the configured unit.
Both are supported. Battery is convenient for light reporting, but the PM laser and CO2 sensor draw more current, so for dense intervals or continuous particulate sampling use USB Type-C.
Yes. With history and retransmission enabled it buffers frames locally and resends them. Those history records use a longer payload (channels 20/21, type ce), so add the matching decoder branch if you turn the feature on.
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.