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.
- 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)
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.
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.
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)
0175640367000104cc0000000005cc02000000Decoded JSON
{ "battery": 100, "temperature": 25.6, "total_in": 0, "total_out": 0, "period_in": 2, "period_out": 0 }Use cases
Smart retail footfall
Count visitors per entrance and turn traffic into conversion and staffing data.
MoreBuilding & space analytics
Measure how corridors, zones and rooms are actually used, without cameras.
MoreSmart city & public spaces
Anonymous footfall in libraries, transit and public buildings for capacity planning.
MoreConfiguration & 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.
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
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.
Your contact
Timo Wevelsiep
Founder, merkaio
15 minutes, no commitment, directly with Timo.
Decoder for ChirpStack v4. merkaio is an independent integrator and is not affiliated with Milesight.