Milesight VS133: AI ToF People Counting Sensor (LoRaWAN)

Milesight VS133 AI ToF people counting sensor: own ChirpStack decoder framework for line in/out and region counts, plus smart-retail and footfall integration.

VS133Sensor
LoRaWAN
Class C, OTAA
Technology
2nd-gen AI ToF depth sensor
Accuracy
Up to 99.8 %, anonymous (no images)
Counting
Line in/out, region presence, dwell time
Mounting
Overhead (ceiling), top-down view
Power
PoE 802.3af or DC (Class C)
Configuration
Web GUI, draw lines and regions
Measurements

What the VS133 measures

Line crossings (in/out)

Bidirectional counts per drawn line, up to four lines.

Region presence

Live headcount inside up to four drawn regions.

Dwell time

Average and maximum dwell per region for queue and zone analysis.

Demographics

Optional adult vs child distinction and staff filtering, fully anonymous.

Occlusion alarm

Reports when the lens view is blocked, so gaps in data are explained.

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 = { lines: {}, regions: {} };

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

    // Device info on join / power-on (0xFF segments)
    if (channel === 0xff) { i += deviceInfoLen(type); continue; }

    // Line counters: channels 03/06/09/0C = total IN, 04/07/0A/0D = total OUT.
    // Type 0xD2, value is UINT32 little-endian.
    if (type === 0xd2 && isLineIn(channel)) {
      line(data, lineIndex(channel, IN_BASE)).in = readUInt32LE(bytes, i);
      i += 4; continue;
    }
    if (type === 0xd2 && isLineOut(channel)) {
      line(data, lineIndex(channel, OUT_BASE)).out = readUInt32LE(bytes, i);
      i += 4; continue;
    }

    // Per-line period (channels 05/08/0B/0E, type 0xCC): in + out UINT16 LE.
    if (type === 0xcc && isLinePeriod(channel)) {
      var ln = line(data, lineIndex(channel, PERIOD_BASE));
      ln.period_in = readUInt16LE(bytes, i);
      ln.period_out = readUInt16LE(bytes, i + 2);
      i += 4; continue;
    }

    // Region headcount (channel 0F, type 0xE3): four UINT8 region counts.
    if (channel === 0x0f && type === 0xe3) {
      for (var r = 0; r < 4; r++) region(data, r + 1).count = bytes[i + r];
      i += 4; continue;
    }

    // Region dwell (channel 10, type 0xE4): region id + avg + max (UINT16 LE).
    if (channel === 0x10 && type === 0xe4) {
      var rg = region(data, bytes[i]);
      rg.avg_dwell = readUInt16LE(bytes, i + 1);
      rg.max_dwell = readUInt16LE(bytes, i + 3);
      i += 5; continue;
    }

    // Occlusion / lens-blocked alarm (channel 50, type 0xFC).
    if (channel === 0x50 && type === 0xfc) {
      data.occlusion_alarm = bytes[i + 2];
      i += 3; continue;
    }

    // Unknown channel: stop. Region/child layout is deployment-specific.
    break;
  }
  return { data: data };
}

var IN_BASE = 0x03, OUT_BASE = 0x04, PERIOD_BASE = 0x05;
function isLineIn(c)     { return c === 0x03 || c === 0x06 || c === 0x09 || c === 0x0c; }
function isLineOut(c)    { return c === 0x04 || c === 0x07 || c === 0x0a || c === 0x0d; }
function isLinePeriod(c) { return c === 0x05 || c === 0x08 || c === 0x0b || c === 0x0e; }
function lineIndex(c, base) { return (c - base) / 3 + 1; }

function line(data, n) {
  var key = "line_" + n;
  if (!data.lines[key]) data.lines[key] = {};
  return data.lines[key];
}
function region(data, n) {
  var key = "region_" + n;
  if (!data.regions[key]) data.regions[key] = {};
  return data.regions[key];
}
function readUInt16LE(b, i) { return (b[i + 1] << 8) | b[i]; }
function readUInt32LE(b, i) {
  return ((b[i + 3] << 24) | (b[i + 2] << 16) | (b[i + 1] << 8) | b[i]) >>> 0;
}
function deviceInfoLen(type) {
  // 0xFF segment lengths are firmware-specific (version, SN, downlink ACKs).
  void type; return 1;
}

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

The VS133 payload is configuration-dependent: how many counting lines and regions you draw in the web GUI decides which channels appear, and multi-sensor (master plus child) setups add a second block of channels. This is a framework in the IPSO channel format, not a fixed drop-in: line counters arrive as UINT32 little-endian totals on channels 03/06/09/0C (in) and 04/07/0A/0D (out), region headcounts as four bytes on channel 0F, and region dwell time on channel 10. We implement and adapt it from the published Milesight byte specification against a real uplink from the deployment. As a Class C device the VS133 also accepts downlinks to set the report interval or clear counters.

From the field

Configuration & pitfalls

Draw lines and regions first

Counting lines and zones are configured in the web GUI before rollout. The payload only carries the channels you enable, so finalise the layout, then map the decoder.

Overhead mounting height

The ToF field of view is fixed, so mounting height sets the covered width. Confirm ceiling height against the datasheet coverage table before ordering brackets.

Class C power, not battery

The VS133 runs on PoE or DC and keeps its receive window open continuously. Plan cabling and a PoE switch port, not a battery budget.

Master and child layout

Linked sensors covering a wide entrance report a second channel block (child counts). Document which node is master so totals are not double counted.

Your partner

How merkaio supports your VS133

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

Pre-staging & provisioning

We configure the VS133, 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 C device, so no Milesight gateway or cloud is required. You add the codec to the device profile and provision it via OTAA, the counting lines and regions are configured in the device web GUI.
Yes, as a ChirpStack v4 decoder framework, implemented from the published Milesight byte specification. Because the channels depend on how many lines and regions you draw, we adapt and validate it against a real uplink from your deployment, the same logic goes into a ThingsBoard uplink converter.
The payload only contains the counting lines, regions and dwell-time channels you actually enable in the web GUI, and multi-sensor setups add a second master-plus-child block. So the decoder is a framework keyed to the IPSO channels, not a single fixed layout.
Yes. It uses a time-of-flight depth sensor, not a camera, so it reports anonymous counts and depth data rather than identifiable images, which suits GDPR-sensitive retail and office deployments.
It is a Class C device powered over PoE (802.3af) or DC. It keeps its receive window open continuously for downlinks, so it is mains powered rather than battery operated.
Beyond bidirectional line crossings it reports live region headcount, average and maximum dwell time per region, optional adult-versus-child distinction with staff filtering, and an occlusion alarm when the lens view is blocked.
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.