Milesight VS360: LoRaWAN IR Breakbeam People Counter

Milesight VS360 LoRaWAN IR breakbeam people counter: own ChirpStack/ThingsBoard decoder, decoded example, in/out counts and entrance footfall integration.

Milesight VS360
VS360Sensor
LoRaWAN
Class A, OTAA
Design
Paired master + node (transmitter/receiver)
Counting
IR breakbeam, bi-directional in/out
Accuracy
Up to 80% (single-file passage)
Detection width
1.2 to 3 m, mount height 0.7 to 1.2 m
Battery
2 x ER14505 Li-SOCl2 per unit, several years
Ingress / temperature
IP30, -20 to +50 °C
Measurements

What the VS360 measures

Total in / out

Cumulative inbound and outbound counts since the last reset (channel 04, two UINT16LE).

Period in / out

In and out counts for the current reporting period (channel 05, two UINT16LE).

Count alarms

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

Battery (master + node)

Each paired unit reports its own battery percentage (channels 01 and 02).

Events

Tamper, low battery and pairing events as discrete status flags.

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++];

    // Device info on join / power-on (protocol, hw/fw, serial, class)
    if (channel === 0xff) {
      if (type === 0x01) { i += 1; }        // IPSO version
      else if (type === 0x09) { i += 2; }   // hardware version
      else if (type === 0x0a) { i += 2; }   // firmware version
      else if (type === 0x16) { i += 8; }   // serial number
      else if (type === 0x0f) { i += 1; }   // LoRaWAN class
      else if (type === 0xff) { i += 2; }   // TSL version
      else { break; }
    } else if (channel === 0x01 && type === 0x75) {        // battery master (%)
      data.battery_main = bytes[i]; i += 1;
    } else if (channel === 0x02 && type === 0x75) {        // battery node (%)
      data.battery_node = bytes[i]; i += 1;
    } else if (channel === 0x03 && type === 0xf4) {        // event: type + status
      data.event = { type: bytes[i], status: bytes[i + 1] }; i += 2;
    } else if (channel === 0x04 && type === 0xcc) {        // total in / out
      data.total_in = readUInt16LE(bytes, i);
      data.total_out = readUInt16LE(bytes, i + 2); i += 4;
    } else if (channel === 0x84 && type === 0xcc) {        // total in / out + alarm
      data.total_in = readUInt16LE(bytes, i);
      data.total_out = readUInt16LE(bytes, i + 2);
      data.total_count_alarm = bytes[i + 4]; i += 5;
    } else if (channel === 0x05 && type === 0xcc) {        // period in / out
      data.period_in = readUInt16LE(bytes, i);
      data.period_out = readUInt16LE(bytes, i + 2); i += 4;
    } else if (channel === 0x85 && type === 0xcc) {        // period in / out + alarm
      data.period_in = readUInt16LE(bytes, i);
      data.period_out = readUInt16LE(bytes, i + 2);
      data.period_count_alarm = bytes[i + 4]; i += 5;
    } else if (channel === 0x0a && type === 0xef) {        // timestamp (UINT32LE)
      data.timestamp = readUInt32LE(bytes, i); i += 4;
    } else {
      break;
    }
  }
  return { data: data };
}

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;
}

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

Channel format: 01 75 battery master (%), 02 75 battery node (%), 04 cc total in/out (two UINT16LE), 05 cc period in/out, 84/85 cc the same plus a count-alarm byte, 03 f4 event (type + status), 0a ef timestamp (UINT32LE). The decoder is implemented from the published Milesight byte specification. Historical (catch-up) uplinks on channel 20 ce and downlink-response frames are device-specific; we add them per deployment. For ThingsBoard the same channel logic goes into an uplink converter.

Uplink (hex)

01756402756304CC0C00000005CC05000200

Decoded JSON

{ "battery_main": 100, "battery_node": 99, "total_in": 12, "total_out": 0, "period_in": 5, "period_out": 2 }
From the field

Configuration & pitfalls

Pairing the two units

The VS360 works as a master plus a node facing each other across the doorway. Pair and align them so the IR beam is uninterrupted, then provision only the master over LoRaWAN.

Mount height and width

Keep the passage between 1.2 and 3 m wide and mount at 0.7 to 1.2 m. People walking side by side count as one, which is why accuracy is around 80% in single-file flow.

Total vs period counts

Total counts are cumulative since reset, period counts cover one interval. Build dashboard deltas from period values and use total only as a running reference.

NFC setup

Keys, reporting interval and count-alarm thresholds are set over NFC with the Milesight ToolBox app on the master before rollout.

Your partner

How merkaio supports your VS360

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

Pre-staging & provisioning

We configure the VS360, 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 Class A LoRaWAN device, no Milesight gateway or cloud required. You add the codec to the device profile and provision the master 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.
It uses two paired IR breakbeam units facing each other across an entrance. The order in which the beams break tells the device whether a person walked in or out, so it reports separate in and out counts.
Up to about 80% in single-file passage. People walking side by side through the beam are counted as one, so it suits narrow entrances better than wide open spaces.
Total in/out are cumulative since the last reset, while period in/out cover the current reporting interval. For trend dashboards use the period values and keep total as a running reference.
Each unit uses two replaceable ER14505 Li-SOCl2 cells and lasts several years depending on traffic and reporting interval. Both the master and the node report their own battery level.
Yes. Count-alarm thresholds on period or cumulative counts are set over NFC and sent immediately on channel 84/85, outside the regular interval, so dashboard rules should treat them as priority events.
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.