Milesight VS351: LoRaWAN Mini Thermopile People Counter

Milesight VS351 LoRaWAN thermopile people counter: own ChirpStack/ThingsBoard decoder, decoded in/out example and smart-retail integration.

Milesight VS351
VS351Sensor
LoRaWAN
Class A, OTAA
Band / port
EU868 / port 85
Sensing
Thermopile IR array + radar
Counting
Bi-directional in / out
Privacy
Anonymous, no image, GDPR friendly
Power
Battery or USB Type-C version
Configuration
NFC (Milesight ToolBox)
Measurements

What the VS351 measures

Total in / out

Cumulative people count in both directions since the last reset.

Period in / out

People counted in and out during the current reporting interval.

Temperature

Onboard ambient temperature from the thermopile array, in degrees Celsius.

Battery / power status

Battery percentage on the wireless version, or external/charging status on the Type-C version.

Count alarms

Threshold alarms on cumulative or period counts, sent outside the regular interval.

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 (INT16 LE, /10 °C)
      data.temperature = readInt16LE(bytes, i) / 10; i += 2;
    } else if (channel === 0x04 && type === 0xcc) {   // total in / out (UINT16 LE each)
      data.total_in = readUInt16LE(bytes, i);
      data.total_out = readUInt16LE(bytes, i + 2);
      i += 4;
    } else if (channel === 0x05 && type === 0xcc) {   // period in / out (UINT16 LE each)
      data.period_in = readUInt16LE(bytes, i);
      data.period_out = readUInt16LE(bytes, i + 2);
      i += 4;
    } else if (channel === 0xff) {                    // device info (join / power-on)
      i += deviceInfoLen(type);
    } else {
      break;                                          // alarm / history / downlink frames
    }
  }
  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;
}
// 0xFF segment lengths from the published byte spec: version 1B, hw/fw/tsl 2B, SN 8B, class/power 1B.
function deviceInfoLen(type) {
  if (type === 0x16) return 8;                        // serial number
  if (type === 0x09 || type === 0x0a || type === 0xff) return 2; // hw / fw / tsl version
  return 1;                                           // ipso version, class, power, reset, status
}

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

Channel format: 01 75 battery (%), 03 67 temperature (INT16 little-endian, /10), 04 cc total in/out (two UINT16 LE), 05 cc period in/out (two UINT16 LE). The 0xFF channel carries device info on join and power-on. Temperature alarms (83 67) and count alarms (84/85 cc add a trailing alarm byte), historical batches (20 ce) and downlink-response frames are configuration-dependent and are skipped by the loop; we add them per deployment. For ThingsBoard the same channel logic goes into an uplink converter. Implemented from the published Milesight byte specification.

Uplink (hex)

0175640367000104cc0000000005cc02000000

Decoded JSON

{ "battery": 100, "temperature": 25.6, "total_in": 0, "total_out": 0, "period_in": 2, "period_out": 0 }
From the field

Configuration & pitfalls

Mounting height & angle

The thermopile array has a fixed field of view, so mount it overhead at the height and tilt in the user guide. A wrong angle clips the detection zone and undercounts at the edges.

Counting line direction

Set the in/out direction over NFC to match the real flow at the door. If it is mirrored, every total swaps in against out and the analytics read backwards.

Period vs total reset

Period in/out resets each reporting interval, total accumulates until cleared. Decide in the dashboard which one feeds the footfall graph so you do not double count.

Anonymous detection

Thermopile and radar sensing capture no image and no identity, which keeps footfall counting inside GDPR. Document this for the works council or store data-protection record.

Your partner

How merkaio supports your VS351

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

Pre-staging & provisioning

We configure the VS351, 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.
Bi-directional people counts: a total in/out that accumulates and a period in/out per reporting interval, plus an ambient temperature and the battery or power status. It captures no image.
Its detection is anonymous. Thermopile and radar sensing measure presence and movement without capturing any image or personal data, which makes it well suited to GDPR-sensitive retail and public-space deployments.
Period in/out is the number of people counted during the current reporting interval and resets each cycle. Total in/out keeps accumulating until you clear it, so use period for live footfall and total for cumulative reporting.
There is a battery version for fully wireless installation and a USB Type-C version for continuous operation. The decoded payload reports either a battery percentage or an external/charging power status accordingly.
Milesight rates bi-directional counting up to about 95 percent accuracy, with an AI algorithm that filters side-by-side and U-turn walkers. Mounting height, angle and the counting line direction set during configuration determine real-world accuracy.
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.