Milesight VS135: Ultra ToF LoRaWAN People Counter
Milesight VS135 Ultra ToF people counter: own ChirpStack decoder framework for line, region and dwell counts plus retail integration.
- LoRaWAN
- Class C, OTAA / ABP
- Band
- EU868 / US915 / AS923 and more
- Sensing
- 2nd-gen ToF depth + AI, no camera images
- Counting accuracy
- up to 99.8 %
- Installation height
- 2 to 6.5 m (high-ceiling mount)
- Power
- DC 12 V / PoE (line-powered, no battery)
- Operating temperature
- -20 to +50 °C
What the VS135 measures
Line crossing (in / out)
Up to four counting lines, cumulative people in and out per line.
Regional counting
Up to four regions report the current head count per zone.
Dwell time
Average and maximum dwell time per region for queue analysis.
Period counts
People in and out per reporting period, separate from the running total.
Multi-device stitching
Master and child sensors report a combined count for wide entrances.
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 = { lines: {}, regions: {}, dwell: {} };
// Channel groups (IPSO): each counting line uses a fixed triplet of channels.
var inChns = [0x03, 0x06, 0x09, 0x0c];
var outChns = [0x04, 0x07, 0x0a, 0x0d];
var periodChns = [0x05, 0x08, 0x0b, 0x0e];
for (var i = 0; i < bytes.length; ) {
var channel = bytes[i++];
var type = bytes[i++];
// Device info on join / power-on: skip with known lengths.
if (channel === 0xff) {
if (type === 0x01) { i += 1; } // protocol version
else if (type === 0x09) { i += 2; } // hardware version
else if (type === 0x16) { i += 8; } // serial number
else if (type === 0x1f) { i += 4; } // firmware version
else { break; } // downlink-response payloads vary
continue;
}
// Line total IN: UINT32 little-endian.
if (idx(inChns, channel) >= 0 && type === 0xd2) {
data.lines["line_" + (idx(inChns, channel) + 1) + "_in"] = readUInt32LE(bytes, i);
i += 4; continue;
}
// Line total OUT: UINT32 little-endian.
if (idx(outChns, channel) >= 0 && type === 0xd2) {
data.lines["line_" + (idx(outChns, channel) + 1) + "_out"] = readUInt32LE(bytes, i);
i += 4; continue;
}
// Line period IN/OUT: two UINT16 little-endian values.
if (idx(periodChns, channel) >= 0 && type === 0xcc) {
var n = idx(periodChns, channel) + 1;
data.lines["line_" + n + "_period_in"] = readUInt16LE(bytes, i);
data.lines["line_" + n + "_period_out"] = readUInt16LE(bytes, i + 2);
i += 4; continue;
}
// Region count: four UINT8 head counts.
if (channel === 0x0f && type === 0xe3) {
for (var r = 0; r < 4; r++) { data.regions["region_" + (r + 1)] = bytes[i + r]; }
i += 4; continue;
}
// Region dwell time: region id + avg + max (UINT16 little-endian, seconds).
if (channel === 0x10 && type === 0xe4) {
var region = bytes[i];
data.dwell["region_" + region + "_avg"] = readUInt16LE(bytes, i + 1);
data.dwell["region_" + region + "_max"] = readUInt16LE(bytes, i + 3);
i += 5; continue;
}
// Occlusion / sensor alarm: node id + alarm type.
if (channel === 0x50 && type === 0xfc) {
data.alarm = { node: bytes[i + 1], type: bytes[i + 2] };
i += 3; continue;
}
break; // unknown channel: stop, the mapping is deployment-specific
}
return { data: data };
}
function idx(arr, v) {
for (var k = 0; k < arr.length; k++) { if (arr[k] === v) { return k; } }
return -1;
}
function readUInt16LE(b, i) {
return ((b[i + 1] << 8) | b[i]) & 0xffff;
}
function readUInt32LE(b, i) {
return ((b[i + 3] << 24) | (b[i + 2] << 16) | (b[i + 1] << 8) | b[i]) >>> 0;
}
Implemented from the published Milesight byte specification (Communication Protocol / User Guide).
The VS135 payload is configuration-dependent: which counting lines, regions and dwell-time channels appear depends on how you set up the detection lines and zones in the web GUI. Line totals are UINT32 little-endian, region counts are single bytes per zone and dwell times are UINT16 seconds. Master and child sensors use separate channel groups for multi-device stitching. This is a framework, not a drop-in: we confirm the final field mapping against a real uplink from your deployment. The LoRaWAN version is Class C and line-powered. For ThingsBoard the same channel logic goes into an uplink converter.
Use cases
Smart retail footfall
Count visitors at entrances and zones to size staffing and measure conversion.
MoreQueue and dwell management
Regional dwell time flags growing queues at checkouts and service desks.
MoreWorkplace occupancy
Anonymous room and zone occupancy for desk and meeting-space planning.
MoreConfiguration & pitfalls
Draw lines and regions first
Counting lines and regions are configured over the local Wi-Fi web GUI before rollout. The channels in the uplink only exist for the lines and zones you actually define.
Auto height detection
The sensor detects its own installation height. Mount it level and within 2 to 6.5 m, since a tilted or out-of-range mount degrades depth accuracy.
Master and child stitching
For wide entrances, link multiple units. Child sensors report under their own channel group, so the decoder and dashboard must sum master and child counts.
Class C power planning
The LoRaWAN version runs on DC 12 V or PoE and keeps its receive window open. Plan for permanent power, this is not a battery sensor.
How merkaio supports your VS135
From sourcing to day-to-day operation, all from one partner on our own European infrastructure.
Pre-staging & provisioning
We configure the VS135, 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.