# Abeeway Microtracker Decoder

**URL:** <https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473>\
**Category:** End Devices (Nodes)\
**Created:** [February 9, 2019, 12:16pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473 "2019-02-09T12:16:35Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![thomassieczkowski](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/thomassieczkowski/32/19635_2.png) [@thomassieczkowski](https://www.thethingsnetwork.org/forum/u/thomassieczkowski)\
**Post date:** [February 9, 2019, 12:16pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/1 "2019-02-09T12:16:36Z")

</div>

Hi there. I’ve been struggling on getting the Abeeway Microtracker decoded. Unfortunately, the manufacturer does not share its decoder to buyers. Here is what I have in terms of material:

1. A work in progress by @flodenh in [this post](https://www.thethingsnetwork.org/forum/t/help-with-byte-joggling-payload-funtion/18885)

2. This file: [Abeeway Micro Tracker\_Reference\_Guide\_FW1.7\_V1.1.pdf](https://www.thethingsnetwork.org/forum/uploads/default/original/3X/1/6/16b27ecd19b1a54bcd0926500a98d0f525b679ae.pdf) (1.1 MB) which contains explanation on how to build a decoder;

3. An example payload: **032CD1890900C46E1FF44B9EC5C83A355A3898A6** which should bring lat/lng close to: -29.9606817, -50.127897399999995 and it is bringing me -99.9416064,-19.6370944 (at this point I’m using @flodenh 's decoder)

It brings me the following JSON:

```json
{
  "ack": 0,
  "age": 0,
  "battery": 3.95,
  "bytes": "AyzRiQkAxG4f9Euexcg6NVo4mKY=",
  "lattitude": -99.9416064,
  "length": 20,
  "longitude": -19.6370944,
  "mode": 1,
  "position_type": 9,
  "temperature": 25.31,
  "type": "POSITION"
}

```

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [February 9, 2019, 1:02pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/2 "2019-02-09T13:02:47Z")

</div>

Looking at the documentation, the payload you’re showing for “position type” 9 does not provide GPS coordinates. Instead, it gives you up to 4 MAC addresses of WiFi access points. I guess you should also get other payloads, that do provide the GPS data. Or maybe you need to move outside to get a GPS fix.

The following has only been tested with `032CD1890900C46E1FF44B9EC5C83A355A3898A6` and `0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC`, the latter yielding:

```json
{
  "ack": 0,
  "age": 112,
  "battery": 3.99,
  "data": 9,
  "position_type": "WIFI BSSIDs",
  "stations": [
    {
      "mac_address": "c4:6e:1f:f4:4b:9e",
      "rssi": -73
    },
    {
      "mac_address": "64:66:b3:b8:74:54",
      "rssi": -83
    },
    {
      "mac_address": "50:09:59:ca:1e:d4",
      "rssi": -83
    },
    {
      "mac_address": "52:5e:67:da:14:a1",
      "rssi": -84
    }
  ],
  "status": {
    "mode": {
      "code": 2,
      "message": "Permanent tracking"
    },
    "moving": 0,
    "on_demand": 0,
    "periodic": 0,
    "sos": 1,
    "tracking": 1
  },
  "temperature": 31.46,
  "type": "POSITION"
}

```

So, most of the following is really untested, so you’ve got a lot of testing to do now (and I’d suggest you thoroughly compare the code to the documentation as well…).

For the GPS coordinates, in `function degree(bytes)`, you might actually need:

```javascript
var d = bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100;

```

But we need some test payload to confirm that. I guess some payload types (and the conditions for which you get them) would be nice for future readers as well.

> **Click for old version of the decoder, or see posts below for updates**
>
> ```javascript
> //
> // SEE UPDATED VERSION(S) IN FORUM POSTS BELOW
> //
> function Decoder(bytes, port) {
> 
> function step_size(lo, hi, nbits, nresv) {
> return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
> }
> 
> function mt_value_decode(value, lo, hi, nbits, nresv) {
> return ((value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo);
> }
> 
> // Gets the zero-based unsigned numeric value of the given bit(s)
> function bits(value, lsb, msb) {
> var len = (msb ? msb : lsb) - lsb + 1;
> var mask = (1<<len) - 1;
> return value>>lsb & mask;
> }
> 
> // Gets a hexadecimal representation with leading zeroes for each byte
> function hex(bytes, separator) {
> return bytes.map(function (b) {
> return ("0" + b.toString(16)).substr(-2);
> }).join(separator || "");
> }
> 
> // Decodes 4 bytes into a signed integer, MSB
> function int32(bytes) {
> // JavaScript bitwise operators always work with 32 bits signed integers
> return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
> }
> 
> // Decodes 4 bytes into an unsigned integer, MSB
> function uint32(bytes) {
> // Force an unsigned 32 bits integer using zero-fill right shift:
> return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<< 8 | bytes[3])>>>0;
> // Alternatively, don't use bitwise operators at all:
> // return bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
> }
> 
> // Decodes the decimal degree for either latitude or longitude
> function degree(bytes) {
> // TODO This is probably an unsigned number, hence needs:
> // var d = (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8)>>>0
> // or:
> // var d = bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100;
> var d = bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8;
> if (d > 0x7FFFFFFF) {
> d = d - 0x100000000;
> }
> return d / 1e7;
> }
> 
> // Decodes 1 to 4 MAC addresses and their RSSI
> function mac_rssi(bytes) {
> var items = [];
> for (var offset = 0; offset < bytes.length; offset += 7) {
> items.push({
> // Get hexadecimal bytes with a leading zero
> mac_address: hex(bytes.slice(offset, offset + 6), ":"),
> // Sign-extend to 32 bits to support negative values
> rssi: bytes[offset + 6]<<24>>24,
> });
> }
> return items;
> }
> 
> function message(value, messages) {
> return {
> code: value,
> message: value > messages.length ? "UNKNOWN" : messages[value]
> };
> }
> 
> var decoded = {};
> var i;
> var type = bytes[0];
> 
> if (type !== 0x00) {
> // All message types, except for Frame pending messages, share the same header
> decoded.status = {
> mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
> "Motion start/end tracking", "Activity tracking", "OFF"]),
> sos: bits(bytes[1], 4),
> tracking: bits(bytes[1], 3),
> moving: bits(bytes[1], 2),
> periodic: bits(bytes[1], 1),
> on_demand: bits(bytes[1], 0)
> };
> 
> decoded.battery = Math.round(100 * mt_value_decode(bytes[2], 2.8, 4.2, 8, 2)) / 100;
> decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 2)) / 100;
> decoded.ack = bits(bytes[4], 4, 7);
> decoded.data = bits(bytes[4], 0, 3);
> }
> 
> switch (type) {
> case 0x00:
> decoded.type = "FRAME PENDING";
> decoded.token = bytes[1];
> break;
> 
> case 0x03:
> decoded.type = "POSITION";
> switch (decoded.data) {
> case 0:
> decoded.position_type = "GPS fix";
> decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
> decoded.latitude = degree(bytes.slice[6]);
> decoded.longitude = degree(bytes.slice[9]);
> // Estimated Horizontal Position Error
> decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
> break;
> 
> case 1:
> decoded.position_type = "GPS timeout";
> decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
> for (i = 0; i < 4; i++) {
> // Carrier over noise (dBm) for the i-th satellite seen
> decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
> }
> break;
> 
> case 2:
> // Documented as obsolete
> decoded.error = "UNSUPPORTED";
> break;
> 
> case 3:
> decoded.position_type = "WIFI timeout";
> for (i = 0; i < 6; i++) {
> decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
> }
> break;
> 
> case 4:
> decoded.position_type = "WIFI failure";
> for (i = 0; i < 6; i++) {
> // Most of time a WIFI timeout occurs due to a low battery condition
> decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
> }
> decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
> "Antenna unavailable", "WIFI not supported on this device"]);
> break;
> 
> case 5:
> case 6:
> decoded.position_type = "LP-GPS data";
> // Encrypted, not described in the documentation
> decoded.error = "UNSUPPORTED";
> break;
> 
> case 7:
> decoded.position_type = "BLE beacon scan";
> decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
> // Remaining data: up to 4 beacons
> decoded.beacons = mac_rssi(bytes.slice(6));
> break;
> 
> case 8:
> decoded.position_type = "BLE beacon failure";
> decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
> "Scan already on going", "No beacon detected", "Hardware incompatibility"]);
> break;
> 
> // Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
> // or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
> case 9:
> decoded.position_type = "WIFI BSSIDs";
> decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
> // Remaining data: up to 4 WiFi BSSIDs
> decoded.stations = mac_rssi(bytes.slice(6));
> break;
> 
> default:
> decoded.position_type = "UNSUPPORTED";
> }
> break;
> 
> case 0x04:
> decoded.type = "ENERGY STATUS";
> break;
> 
> case 0x05:
> decoded.type = "HEARTBEAT";
> break;
> 
> case 0x07:
> // Activity status message and configuration message share the same identifier
> var tag = bytes[5];
> switch (tag) {
> case 1:
> decoded.type = "Activity Status";
> decoded.activity_counter = uint32(bytes.slice(6, 10));
> break;
> 
> case 2:
> decoded.type = "Configuration";
> for (i = 0; i < 5; i++) {
> var offset = 6 + 5 * i;
> decoded["param" + i] = {
> type: bytes[offset],
> value: uint32(bytes.slice(offset + 1, offset + 5))
> };
> }
> break;
> 
> default:
> decoded.error = "UNSUPPORTED";
> }
> break;
> 
> case 0x09:
> decoded.type = "Shutdown";
> break;
> 
> case 0xFF:
> decoded.type = "Debug";
> break;
> 
> default:
> decoded.type = "UNSUPPORTED";
> }
> 
> // Just some redundant debug info
> decoded.debug = {
> payload: hex(bytes),
> length: bytes.length,
> port: port
> };
> 
> return decoded;
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![thomassieczkowski](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/thomassieczkowski/32/19635_2.png) [@thomassieczkowski](https://www.thethingsnetwork.org/forum/u/thomassieczkowski)\
**Post date:** [February 9, 2019, 7:42pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/3 "2019-02-09T19:42:08Z")

</div>

@arjanvanb - Thank you so much. This totally works. I’m gonna check how to get proper downlink working in order to set GPS only mode! Thanks again!

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [February 9, 2019, 7:51pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/4 "2019-02-09T19:51:32Z")

</div>

> [@thomassieczkowski](#):
>
> This totally works. I’m gonna check how to get proper downlink working in order to set GPS only mode!

So did you already test GPS, or are you only getting the WiFi details? Any chance that, to test GPS, you really just need to take the device outside? Or did you already try that? Unless it’s now in some “WiFi-only” mode (which I doubt) I’d expect another uplink with the GPS details.

---

<div class="post-metadata">

**Author:** ![thomassieczkowski](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/thomassieczkowski/32/19635_2.png) [@thomassieczkowski](https://www.thethingsnetwork.org/forum/u/thomassieczkowski)\
**Post date:** [February 9, 2019, 9:44pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/5 "2019-02-09T21:44:12Z")

</div>

Hi @arjanvanb - Thank you so much for your help so far. I managed to get it working by using the following code:

```auto
function Decoder(bytes, port) {

  function step_size(lo, hi, nbits, nresv) {
    return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
  }

  function mt_value_decode(value, lo, hi, nbits, nresv) {
    return ((value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo);
  }

  // Gets the zero-based unsigned numeric value of the given bit(s)
  function bits(value, lsb, msb) {
    var len = (msb ? msb : lsb) - lsb + 1;
    var mask = (1<<len) - 1;
    return value>>lsb & mask;
  }

  // Gets a hexadecimal representation with leading zeroes for each byte
  function hex(bytes, separator) {
    return bytes.map(function (b) {
      return ("0" + b.toString(16)).substr(-2);
    }).join(separator || "");
  }

  // Decodes 4 bytes into a signed integer, MSB
  function int32(bytes) {
    // JavaScript bitwise operators always work with 32 bits signed integers
    return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
  }

  // Decodes 4 bytes into an unsigned integer, MSB
  function uint32(bytes) {
    return bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
  }

  // Decodes the decimal degree for either latitude or longitude
  function degree(bytes) {
    // TODO This is probably an unsigned number, hence needs:
    //var d = bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100;
    var d = bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8;
    //var d = bytes//[0] << 8 | bytes[1] << 8 | bytes[2] << 8;
    if (d > 0x7FFFFFFF) {
      d = d - 0x100000000;
    }
    return d / 1e7;
  }

  // Decodes 1 to 4 MAC addresses and their RSSI
  function mac_rssi(bytes) {
    var items = [];
    for (var offset = 0; offset < bytes.length; offset += 7) {
      items.push({
        // Get hexadecimal bytes with a leading zero
        mac_address: hex(bytes.slice(offset, offset + 6), ":"),
        // Sign-extend to 32 bits to support negative values
        rssi: bytes[offset + 6]<<24>>24,
      });
    }
    return items;
  }

  function message(value, messages) {
    return {
      code: value,
      message: value > messages.length ? "UNKNOWN" : messages[value]
    };
  }

  var decoded = {};
  var i;
  var type = bytes[0];

  if (type !== 0x00) {
    // All message types, except for Frame pending messages, share the same header
    decoded.status = {
      mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
        "Motion start/end tracking", "Activity tracking", "OFF"]),
      sos: bits(bytes[1], 4),
      tracking: bits(bytes[1], 3),
      moving: bits(bytes[1], 2),
      periodic: bits(bytes[1], 1),
      on_demand: bits(bytes[1], 0)
    };

    decoded.battery = Math.round(100 * mt_value_decode(bytes[2], 2.8, 4.2, 8, 2)) / 100;
    decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 2)) / 100;
    decoded.ack = bits(bytes[4], 4, 7);
    decoded.data = bits(bytes[4], 0, 3);
  }

  switch (type) {
    case 0x00:
      decoded.type = "FRAME PENDING";
      decoded.token = bytes[1];
      break;

    case 0x03:
      decoded.type = "POSITION";
      switch (decoded.data) {
        case 0:
          decoded.position_type = "GPS fix";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          decoded.latitude = degree(bytes.slice(6,8));
          decoded.longitude = degree(bytes.slice(9,11));
          // Estimated Horizontal Position Error
          decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
          break;

        case 1:
          decoded.position_type = "GPS timeout";
          decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
          for (i = 0; i < 4; i++) {
            // Carrier over noise (dBm) for the i-th satellite seen
            decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
          }
          break;

        case 2:
          // Documented as obsolete
          decoded.error = "UNSUPPORTED";
          break;

        case 3:
          decoded.position_type = "WIFI timeout";
          for (i = 0; i < 6; i++) {
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          break;

        case 4:
          decoded.position_type = "WIFI failure";
          for (i = 0; i < 6; i++) {
            // Most of time a WIFI timeout occurs due to a low battery condition
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
            "Antenna unavailable", "WIFI not supported on this device"]);
          break;

        case 5:
        case 6:
          decoded.position_type = "LP-GPS data";
          // Encrypted, not described in the documentation
          decoded.error = "UNSUPPORTED";
          break;

        case 7:
          decoded.position_type = "BLE beacon scan";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 beacons
          decoded.beacons = mac_rssi(bytes.slice(6));
          break;

        case 8:
          decoded.position_type = "BLE beacon failure";
          decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
            "Scan already on going", "No beacon detected", "Hardware incompatibility"]);
          break;

        // Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
        // or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
        case 9:
          decoded.position_type = "WIFI BSSIDs";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 WiFi BSSIDs
          decoded.stations = mac_rssi(bytes.slice(6));
          break;

        default:
          decoded.position_type = "UNSUPPORTED";
      }
      break;

    case 0x04:
      decoded.type = "ENERGY STATUS";
      break;

    case 0x05:
      decoded.type = "HEARTBEAT";
      break;

    case 0x07:
      // Activity status message and configuration message share the same identifier
      var tag = bytes[5];
      switch (tag) {
        case 1:
          decoded.type = "Activity Status";
          decoded.activity_counter = uint32(bytes.slice(6, 10));
          break;

        case 2:
          decoded.type = "Configuration";
          for (i = 0; i < 5; i++) {
            var offset = 6 + 5 * i;
            decoded["param" + i] = {
              type: bytes[offset],
              value: uint32(bytes.slice(offset + 1, offset + 5))
            };
          }
          break;

        default:
          decoded.error = "UNSUPPORTED";
      }
      break;

    case 0x09:
      decoded.type = "Shutdown";
      break;

    case 0xFF:
      decoded.type = "Debug";
      break;

    default:
      decoded.type = "UNSUPPORTED";
  }

  // Just some redundant debug info
  decoded.debug = {
    payload: hex(bytes),
    length: bytes.length,
    port: port
  };

  return decoded;
}

```

I just changed the `slice` function.

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [February 9, 2019, 10:17pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/6 "2019-02-09T22:17:18Z")

</div>

Ah, nice, so you changed:

```javascript
decoded.latitude = degree(bytes.slice[6]);
decoded.longitude = degree(bytes.slice[9]);

```

…into:

```javascript
decoded.latitude = degree(bytes.slice(6,8));
decoded.longitude = degree(bytes.slice(9,11));

```

The second parameter is not really needed (but surely cleaner as it’s more explicit) as the `degree` function only cares for the first 3 bytes it gets passed, and ignores the rest. However, to pass 3 bytes it would then need to read `(6, 9)` and `(9, 12)` as the end position [is not included](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Syntax).

But using `(..)` instead of `[..]` surely helps a lot. 🙂

Can you please share a GPS payload for future readers? (And any other type of payload you might get in the future? @flodenh, maybe you have some other payloads too?) I assume that the following would suffice, as JavaScript’s bitwise operators always yield signed 32 bits numbers:

```javascript
function degree(bytes) {
  return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8) / 1e7;
}

```

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [February 13, 2019, 11:45am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/7 "2019-02-13T11:45:55Z")

</div>

Seeing temperature values like 25.31, I wonder how the values are truly encoded. It is documented as:

> Temperature measured in the device, expressed in degree Celsius. Encoded form using lo= - 44, hi= 85, nbits= 8, nresv= 0. It is given with a step of 0.5°C

But while decoding the step is calculated as 1 / (255 / 129) = 0.5058823529411764, yielding values such as 25.30588235294117. Here, JavaScript’s floating point numbers [might be to blame partially](https://medium.com/@maximus.koretskyi/javascripts-number-type-8d59199db1b6#.9whwe88tz). But even in exact math 1 / (255 / 129) will have an infinite number of digits, so I guess I would have used different low and high values, or use a different formula for the step.

One might be tempted to round the calculated step, but for the battery reading the calculated step yields 0.005533596837944666 and is documented as 5.5 mV. Or one might want to use the documented step, but even that will run into JavaScript rounding errors ~~for `(value - nresv / 2)`, if `nresv` is not zero~~:

```javascript
function mt_value_decode2(value, lo, nresv, step_size) {
  return (value - nresv / 2) * step_size + lo;
}

```

Also, with the given documentation there is just no way to tell if the above should really be interpreted as 25.3 or 25.5 (not even taking the sensor’s accuracy into account)…

Or: **the above could also indicate a documentation error, if different values for the high and low values are used while encoding?** If the device has any option to see the actual values, then I’d compare that to the output.

All said, **while looking at that code, I happened to run into a wrong value for `nresv` in the temperature decoding** , also present in @flodenh’s version. (And there might be more: validate against the documentation!) I fixed that in the version below, and also simplified the GPS handling, though I’ve no payloads to validate that. Just to be able to refer to this example in the future, I also changed the output of single-bit values into booleans, and made the error reporting more consistent:

```javascript
/**
 * Decoder for Abeeway Microtracker.
 *
 * 2019-02-09: initial release
 * 2019-02-13: fixed "nresv" for temperature reading;
 * using booleans for single-bit values;
 * improved error reporting
 * 2019-02-13: fixed message()
 * 2020-06-16: simplified uint32()
 * added warning about Data Storage Integration
 */
function Decoder(bytes, port) {

  // nbits: number of bits used to encode
  // lo: min value that can be encoded
  // hi: max value that can be encoded
  // nresv: number of reserved values, not used for the encoding
  function step_size(lo, hi, nbits, nresv) {
    return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
  }

  function mt_value_decode(value, lo, hi, nbits, nresv) {
    return (value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo;
  }

  // Gets the zero-based unsigned numeric value of the given bit(s)
  function bits(value, lsb, msb) {
    var len = msb - lsb + 1;
    var mask = (1<<len) - 1;
    return value>>lsb & mask;
  }

  // Gets the boolean value of the given bit
  function bit(value, bit) {
    return (value & (1<<bit)) > 0;
  }

  // Gets a hexadecimal representation ensuring a leading zero for each byte
  function hex(bytes, separator) {
    return bytes.map(function (b) {
      return ("0" + b.toString(16)).substr(-2);
    }).join(separator || "");
  }

  // Decodes 4 bytes into a signed integer, MSB
  function int32(bytes) {
    // JavaScript bitwise operators always work with 32 bits signed integers
    return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
  }

  // Decodes 4 bytes into an unsigned integer, MSB
  function uint32(bytes) {
    // Or, same result:
    // return bytes[0] * 0x1000000 + bytes[1] * 0x10000 + bytes[2] * 0x100 + bytes[3];
    // return bytes[0] * 0x1000000 + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
    // return bytes[0] * (1<<24) + (bytes[1]<<16 | bytes[2]<<8 | bytes[3]);
    // return int32(bytes)>>>0;

    // JavaScript bitwise operators always work with 32 bits signed integers;
    // force conversion to unsigned 32 bits value using zero-fill right shift
    return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3])>>>0;    
  }

  // Decodes 1 to 4 MAC addresses and their RSSI
  function mac_rssi(bytes) {
    var items = [];
    for (var offset = 0; offset < bytes.length; offset += 7) {
      items.push({
        mac_address: hex(bytes.slice(offset, offset + 6), ":"),
        // Sign-extend to 32 bits to support negative values; dBm
        rssi: bytes[offset + 6]<<24>>24,
      });
    }
    return items;
  }

  function message(code, descriptions) {
    return {
      code: code,
      description: code < 0 || code >= descriptions.length ? "UNKNOWN" : descriptions[code]
    };
  }

  var decoded = {};
  var i;

  var type = bytes[0];

  // All message types, except for Frame pending messages, share the same header
  if (type !== 0x00) {
    // Note: the Data Storage Integration stores nested objects as text
    decoded.status = {
      mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
        "Motion start/end tracking", "Activity tracking", "OFF"]),
      sos: bit(bytes[1], 4),
      tracking: bit(bytes[1], 3),
      moving: bit(bytes[1], 2),
      periodic: bit(bytes[1], 1),
      on_demand: bit(bytes[1], 0)
    };

    // Or, same result:
    // // Unary plus-operator to cast string results of toFixed to a number:
    // decoded.battery = +mt_value_decode(bytes[2], 2.8, 4.2, 8, 2).toFixed(2);
    // decoded.temperature = +mt_value_decode(bytes[3], -44, 85, 8, 0).toFixed(2);
    decoded.battery = Math.round(100 * mt_value_decode(bytes[2], 2.8, 4.2, 8, 2)) / 100;
    decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 0)) / 100;
    decoded.ack = bits(bytes[4], 4, 7);
    decoded.data = bits(bytes[4], 0, 3);
  }

  switch (type) {
    case 0x00:
      decoded.type = "FRAME PENDING";
      decoded.token = bytes[1];
      break;

    case 0x03:
      decoded.type = "POSITION";
      switch (decoded.data) {
        case 0:
          decoded.position_type = "GPS fix";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Signed 32 bits integers; LSB is always zero
          decoded.latitude = (bytes[6]<<24 | bytes[7]<<16 | bytes[8]<<8) / 1e7;
          decoded.longitude = (bytes[9]<<24 | bytes[10]<<16 | bytes[11]<<8) / 1e7;
          // Estimated Horizontal Position Error
          decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
          break;

        case 1:
          decoded.position_type = "GPS timeout";
          decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
          for (i = 0; i < 4; i++) {
            // Carrier over noise (dBm) for the i-th satellite seen
            decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
          }
          break;

        case 2:
          // Documented as obsolete
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
          break;

        case 3:
          decoded.position_type = "WIFI timeout";
          for (i = 0; i < 6; i++) {
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          break;

        case 4:
          decoded.position_type = "WIFI failure";
          for (i = 0; i < 6; i++) {
            // Most of time a WIFI timeout occurs due to a low battery condition
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
            "Antenna unavailable", "WIFI not supported on this device"]);
          break;

        case 5:
        case 6:
          decoded.position_type = "LP-GPS data";
          // Encrypted; not described in the documentation
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
          break;

        case 7:
          decoded.position_type = "BLE beacon scan";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 beacons
          decoded.beacons = mac_rssi(bytes.slice(6));
          break;

        case 8:
          decoded.position_type = "BLE beacon failure";
          decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
            "Scan already on going", "No beacon detected", "Hardware incompatibility"]);
          break;

        // Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
        // or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
        case 9:
          decoded.position_type = "WIFI BSSIDs";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 WiFi BSSIDs
          decoded.stations = mac_rssi(bytes.slice(6));
          break;

        default:
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
      }
      break;

    case 0x04:
      decoded.type = "ENERGY STATUS";
      break;

    case 0x05:
      decoded.type = "HEARTBEAT";
      break;

    case 0x07:
      // Activity status message and configuration message share the same identifier
      var tag = bytes[5];
      switch (tag) {
        case 1:
          decoded.type = "ACTIVITY STATUS";
          decoded.activity_counter = uint32(bytes.slice(6, 10));
          break;

        case 2:
          decoded.type = "CONFIGURATION";
          for (i = 0; i < 5; i++) {
            var offset = 6 + 5 * i;
            decoded["param" + i] = {
              type: bytes[offset],
              value: uint32(bytes.slice(offset + 1, offset + 5))
            };
          }
          break;

        default:
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data + "/" + tag]);
      }
      break;

    case 0x09:
      decoded.type = "SHUTDOWN";
      break;

    case 0xFF:
      decoded.type = "DEBUG";
      break;

    default:
      decoded.error = message(0, ["UNSUPPORTED MESSAGE TYPE " + type]);
  }

  // Just some redundant debug info
  decoded.debug = {
    payload: hex(bytes),
    length: bytes.length,
    port: port,
    server_time: new Date().toISOString()
  };

  return decoded;
}

```

> [@arjanvanb](#):
>
> Can you please share a GPS payload for future readers? (And any other type of payload you might get in the future? @flodenh, maybe you have some other payloads too?)

Still interested!

---

<div class="post-metadata">

**Author:** ![flodenh](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/flodenh/32/25844_2.png) [@flodenh](https://www.thethingsnetwork.org/forum/u/flodenh)\
**Post date:** [February 15, 2019, 8:02am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/8 "2019-02-15T08:02:52Z")

</div>

Sorry - I have been busy working with something else than the Abeeway trackers. What a pleasant surprise to see this amazing job you guys have done!

Here is an example of a payload with a GPS-position: 0328D87E601523537B0AC8160B

The microtracker is configurable by sending it downlinks and I have had some success configuring it for GPS-only. Please let me know if you need some input on that @thomassieczkowski

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [February 15, 2019, 8:29am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/9 "2019-02-15T08:29:41Z")

</div>

> [@flodenh](#):
>
> a GPS-position: 0328D87E601523537B0AC8160B

Does that give you the expected output? Using the simplified version, that would get you:

```json
"latitude": 59.2673536,
"longitude": 18.0884992

```

And to extend on the temperature even more: a bare byte value of 137 is currently decoded as 25.31, using the calculated step of 0.5058823529411764. If the step was really 0.5 while encoding, then **the decoding introduces an error of 137 \* 0.0058823529411764 = 0.8 degrees** , increasing for larger values. Using the documented step would indeed yield 24.5 rather than 25.31, quite a bit lower, and that still doesn’t even take the accuracy of the sensor into account…

So, depending on the use case, one might want to inquire the manufacturer about the documentation.

---

<div class="post-metadata">

**Author:** ![flodenh](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/flodenh/32/25844_2.png) [@flodenh](https://www.thethingsnetwork.org/forum/u/flodenh)\
**Post date:** [February 15, 2019, 10:21am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/10 "2019-02-15T10:21:04Z")

</div>

Yes that is the expected output - you all know where I live by now 🙂

The temperature is of no interest in my use case but I see your point.

---

<div class="post-metadata">

**Author:** ![system](https://www.thethingsnetwork.org/forum/uploads/default/original/3X/6/2/6250ea44fd2b0d6259489e1a92748df681e1f970.png) [@system](https://www.thethingsnetwork.org/forum/u/system)\
**Post date:** [April 16, 2019, 10:21am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/11 "2019-04-16T10:21:10Z")

</div>

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [July 29, 2020, 1:43pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/12 "2020-07-29T13:43:23Z")

</div>



---

<div class="post-metadata">

**Author:** ![JoergHu](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/joerghu/32/35697_2.png) [@JoergHu](https://www.thethingsnetwork.org/forum/u/JoergHu)\
**Post date:** [July 29, 2020, 9:11am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/13 "2020-07-29T09:11:03Z")

</div>

I used the above decoder, but it didn’t give a satisfying result. So I contacted Abeeway again. The statement, that they don’t provide a decoder is wrong. They sent me the file ands I’ll post it here. My problem is, that I can’t see location data on Cayenne. According to Abeeway the problem is not on TTN but on Cayenne. So i post it there too.  
Now, here is the current offical decoder file from Abeeway. i think some other people will need that in future too:

```javascript
function Decoder(bytes, port) {
/*
 * Example decoder for some Netvox sensors with The Things Network
 * FOR TESTING PURPOSES ONLY
 * Paul Hayes - paul@alliot.co.uk
 */

  var decoded = {};
  
  // decode common header
  decoded.battery_voltage = bytes[2]*0.0055+2.8;
  decoded.battery_percentage = parseInt((bytes[2]/255)*100);
  decoded.temperature = (bytes[3]*0.5)-44;
  decoded.ack_token = bytes[4] >> 4;
  // decode status
  if (bytes[1] & 0x10) decoded.sos_mode = true;
  if (bytes[1] & 0x08) decoded.tracking_state = true;
  if (bytes[1] & 0x04) decoded.moving = true;
  if (bytes[1] & 0x02) decoded.periodic_pos = true;
  if (bytes[1] & 0x01) decoded.pos_on_demand = true;
  decoded.operating_mode = bytes[1] >> 5;
  
  // decode rest of message
  if ((bytes[0] === 0x03) && ((bytes[4] & 0x0F) === 0x00)) { // position message & GPS type
    var lat_raw = ((bytes[6] << 16) | (bytes[7] << 8) | bytes[8]);
    lat_raw = lat_raw << 8;
    if (lat_raw > 0x7FFFFFFF) {
      lat_raw = lat_raw - 0x100000000;
    }
    decoded.latitude = lat_raw/10000000;
    var lng_raw = ((bytes[9] << 16) | (bytes[10] << 8) | bytes[11]);
    lng_raw = lng_raw << 8;
    if (lng_raw > 0x7FFFFFFF) {
      lng_raw = lng_raw - 0x100000000;
    }
    decoded.longitude = lng_raw/10000000;
    decoded.accuracy = bytes[12]*3.9;
    decoded.age = bytes[5]*8;
  } else if ((bytes[0] === 0x03) && ((bytes[4] & 0x0F) === 0x09)) { // position message & wifi bssid type
    decoded.bssid0 = bytes.slice(6, 12).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.bssid1 = bytes.slice(13, 19).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.bssid2 = bytes.slice(20, 26).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.bssid3 = bytes.slice(27, 33).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    
    decoded.rssi0 = (bytes[12] > 127 ? bytes[12] -256 : bytes[12]);
    decoded.rssi1 = (bytes[19] > 127 ? bytes[19] -256 : bytes[19]);
    decoded.rssi2 = (bytes[26] > 127 ? bytes[26] -256 : bytes[26]);
    decoded.rssi3 = (bytes[33] > 127 ? bytes[33] -256 : bytes[33]);
  } else if ((bytes[0] === 0x03) && ((bytes[4] & 0x0F) === 0x07)) { // position message & BLE macaddr type
    decoded.macadr0 = bytes.slice(6, 12).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.macadr1 = bytes.slice(13, 19).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.macadr2 = bytes.slice(20, 26).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.macadr3 = bytes.slice(27, 33).map(function(b) { return ("0" + b.toString(16)).substr(-2); }).join(":");
    decoded.rssi0 = (bytes[12] > 127 ? bytes[12] -256 : bytes[12]);
    decoded.rssi1 = (bytes[19] > 127 ? bytes[19] -256 : bytes[19]);
    decoded.rssi2 = (bytes[26] > 127 ? bytes[26] -256 : bytes[26]);
    decoded.rssi3 = (bytes[33] > 127 ? bytes[33] -256 : bytes[33]);
  } else if ((bytes[0] === 0x03) && ((bytes[4] & 0x0F) === 0x01)) { // position message & GPS timeout (failure)
    decoded.gpstimeout = true;
  } else if (bytes[0] === 0x09) { // shutdown message
    decoded.shutdown = true;
  } else if (bytes[0] === 0x0A) {
    decoded.geoloc_start = true; 
  } else if (bytes[0] === 0x05) {
    decoded.heartbeat = true;
    decoded.reset_cause = bytes[5];
    decoded.firmware_ver = bytes.slice(6, 9);
  }
  return decoded;
}

```

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [July 29, 2020, 1:46pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/14 "2020-07-29T13:46:07Z")

</div>

> [@JoergHu](#):
>
> but it didn’t give a satisfying result

For the people using that decoder: can you tell us what details were off? And for which payload?

> [@arjanvanb](#):
>
> And to extend on the temperature even more: a bare byte value of 137 is currently decoded as 25.31, using the calculated step of 0.5058823529411764. If the step was really 0.5 while encoding, then **the decoding introduces an error of 137 \* 0.0058823529411764 = 0.8 degrees** , increasing for larger values. Using the documented step would indeed yield 24.5 rather than 25.31, quite a bit lower, and that still doesn’t even take the accuracy of the sensor into account…

@thomassieczkowski and @flodenh, the new decoder shows a different value for temperature, for `0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC`. The new one shows 30.5 using:

```javascript
decoded.temperature = (bytes[3]*0.5)-44;

```

…where the earlier one showed 31.38 using:

```javascript
decoded.temperature 
  = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 0)) / 100;

```

For `032CD1890900C46E1FF44B9EC5C83A355A3898A6` the new one shows 24.5 while earlier we got 25.31.

For the coordinates both show the same results, for `0328D87E601523537B0AC8160B`, again with a different temperature.

> [@JoergHu](#):
>
> ```auto
> /*
> * Example decoder for some Netvox sensors with The Things Network
> * FOR TESTING PURPOSES ONLY
> * Paul Hayes - paul@alliot.co.uk
> */
> 
> ```

It seems [Alliot](https://www.alliot.co.uk) is a reseller, not [the manufacturer](https://www.abeeway.com)?

Aside, [they provide](https://www.alliot.co.uk/products/asset-tracking/abeeway-micro-tracker/) a more recent version of the documentation. I’ve not looked at it.

---

<div class="post-metadata">

**Author:** ![arjanvanb](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/arjanvanb/32/46626_2.png) [@arjanvanb](https://www.thethingsnetwork.org/forum/u/arjanvanb)\
**Post date:** [July 30, 2020, 9:19am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/15 "2020-07-30T09:19:39Z")

</div>

> [@arjanvanb](#):
>
> It seems [Alliot](https://www.alliot.co.uk) is a reseller, not [the manufacturer](https://www.abeeway.com)?

Indeed, [Alliot claims](https://www.alliot.co.uk/products/asset-tracking/abeeway-micro-tracker/):

> Alliot Technologies are official Hardware Distributors and Resellers of [Abeeway](https://www.abeeway.com/partners/) products.

So, @JoergHu, did you get the decoder from Abeeway (in which case one might say it’s official), or from Alliot (in which case it’s simply not clear if their simplified approach is valid)?

To summarize the issues already mentioned above: when relying on the notes about encoding in the official documentation, then decoding uses non-exact conversion factors, such as 0.5058823529411764 instead of 0.5. Using those non-exact numbers is actually required if the encoding uses the same non-exact numbers. Assuming the firmware is closed source, and if the device provides no logging of its own (to compare sensor readings to the LoRaWAN payload) then one may want to ask Abeeway.

---

<div class="post-metadata">

**Author:** ![Johan\_Scheepers](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/johan_scheepers/32/46550_2.png) [@Johan\_Scheepers](https://www.thethingsnetwork.org/forum/u/Johan_Scheepers)\
**Post date:** [May 18, 2021, 10:54am UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/16 "2021-05-18T10:54:11Z")

</div>

Changes in the battery value - it’s now expressed for all rechargeable Abeeway devices as a %, also added MCU and BLE fwVersion

```
/**
 * Decoder for Abeeway Microtracker.
 *From Abeeway Tracker Reference Guide FW 2.1 V1.3
 */
function Decoder(bytes, port) {

  // nbits: number of bits used to encode
  // lo: min value that can be encoded
  // hi: max value that can be encoded
  // nresv: number of reserved values, not used for the encoding
  function step_size(lo, hi, nbits, nresv) {
    return 1.0 / ((((1<<nbits) - 1) - nresv) / (hi - lo));
  }

  function mt_value_decode(value, lo, hi, nbits, nresv) {
    return (value - nresv / 2) * step_size(lo, hi, nbits, nresv) + lo;
  }

  // Gets the zero-based unsigned numeric value of the given bit(s)
  function bits(value, lsb, msb) {
    var len = msb - lsb + 1;
    var mask = (1<<len) - 1;
    return value>>lsb & mask;
  }

  // Gets the boolean value of the given bit
  function bit(value, bit) {
    return (value & (1<<bit)) > 0;
  }

  // Gets a hexadecimal representation ensuring a leading zero for each byte
  function hex(bytes, separator) {
    return bytes.map(function (b) {
      return ("0" + b.toString(16)).substr(-2);
    }).join(separator || "");
  }

  // Decodes 4 bytes into a signed integer, MSB
  function int32(bytes) {
    // JavaScript bitwise operators always work with 32 bits signed integers
    return bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3];
  }

  // Decodes 4 bytes into an unsigned integer, MSB
  function uint32(bytes) {
   
    return (bytes[0]<<24 | bytes[1]<<16 | bytes[2]<<8 | bytes[3])>>>0;    
  }

  // Decodes 1 to 4 MAC addresses and their RSSI
  function mac_rssi(bytes) {
    var items = [];
    for (var offset = 0; offset < bytes.length; offset += 7) {
      items.push({
        mac_address: hex(bytes.slice(offset, offset + 6), ":"),
        // Sign-extend to 32 bits to support negative values; dBm
        rssi: bytes[offset + 6]<<24>>24,
      });
    }
    return items;
  }

  function message(code, descriptions) {
    return {
      code: code,
      description: code < 0 || code >= descriptions.length ? "UNKNOWN" : descriptions[code]
    };
  }

  var decoded = {};
  var i;

  var type = bytes[0];

  // All message types, except for Frame pending messages, share the same header
  if (type !== 0x00) {
    // Note: the Data Storage Integration stores nested objects as text
    decoded.status = {
      mode: message(bits(bytes[1], 5, 7), ["Standby", "Motion tracking", "Permanent tracking",
        "Motion start/end tracking", "Activity tracking", "OFF"]),
      sos: bit(bytes[1], 4),
      tracking: bit(bytes[1], 3),
      moving: bit(bytes[1], 2),
      periodic: bit(bytes[1], 1),
      on_demand: bit(bytes[1], 0)
    };

    // Trackers with a rechargeable battery:the percentage reflects the actual value
    decoded.batteryPersentage = bytes[2];
    //Temperature
    decoded.temperature = Math.round(100 * mt_value_decode(bytes[3], -44, 85, 8, 0)) / 100;
    
    decoded.ack = bits(bytes[4], 4, 7);
    
    decoded.data = bits(bytes[4], 0, 3);
    //MCU and BLE fwVersion
    decoded.lastResetCause = "lastResetCause: " + bytes[5];
    decoded.mcuFirmware = "fwVersion: " + bytes[6] + "." + bytes[7] + "." + bytes[8];
    decoded.bleFirmware = "bleFwVersion" + bytes[9] + "." + bytes[10] + "." + bytes[11];

  }

  switch (type) {
    case 0x00:
      decoded.type = "FRAME PENDING";
      decoded.token = bytes[1];
      break;

    case 0x03:
      decoded.type = "POSITION";
      switch (decoded.data) {
        case 0:
          decoded.position_type = "GPS fix";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Signed 32 bits integers; LSB is always zero
          decoded.latitude = (bytes[6]<<24 | bytes[7]<<16 | bytes[8]<<8) / 1e7;
          decoded.longitude = (bytes[9]<<24 | bytes[10]<<16 | bytes[11]<<8) / 1e7;
          // Estimated Horizontal Position Error
          decoded.ehpe = mt_value_decode(bytes[12], 0, 1000, 8, 0);
          break;

        case 1:
          decoded.position_type = "GPS timeout";
          decoded.timeout_cause = message(bytes[5], ["User timeout cause"]);
          for (i = 0; i < 4; i++) {
            // Carrier over noise (dBm) for the i-th satellite seen
            decoded["cn" + i] = mt_value_decode(bytes[6 + i], 0, 2040, 8, 0);
          }
          break;

        case 2:
          // Documented as obsolete
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
          break;

        case 3:
          decoded.position_type = "WIFI timeout";
          for (i = 0; i < 6; i++) {
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          break;

        case 4:
          decoded.position_type = "WIFI failure";
          for (i = 0; i < 6; i++) {
            // Most of time a WIFI timeout occurs due to a low battery condition
            decoded["v_bat" + (i + 1)] = mt_value_decode(bytes[5 + i], 2.8, 4.2, 8, 2);
          }
          decoded.error = message(bytes[11], ["WIFI connection failure", "Scan failure",
            "Antenna unavailable", "WIFI not supported on this device"]);
          break;

        case 5:
        case 6:
          decoded.position_type = "LP-GPS data";
          // Encrypted; not described in the documentation
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
          break;

        case 7:
          decoded.position_type = "BLE beacon scan";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 beacons
          decoded.beacons = mac_rssi(bytes.slice(6));
          break;

        case 8:
          decoded.position_type = "BLE beacon failure";
          decoded.error = message(bytes[5], ["BLE is not responding", "Internal error", "Shared antenna not available",
            "Scan already on going", "No beacon detected", "Hardware incompatibility"]);
          break;

        // Test with: 0358D895090EC46E1FF44B9EB76466B3B87454AD500959CA1ED4AD525E67DA14A1AC
        // or 032CD1890900C46E1FF44B9EC5C83A355A3898A6
        case 9:
          decoded.position_type = "WIFI BSSIDs";
          decoded.age = mt_value_decode(bytes[5], 0, 2040, 8, 0);
          // Remaining data: up to 4 WiFi BSSIDs
          decoded.stations = mac_rssi(bytes.slice(6));
          break;

        default:
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data]);
      }
      break;

    case 0x04:
      decoded.type = "ENERGY STATUS";
      break;

    case 0x05:
      decoded.type = "HEARTBEAT";
      break;

    case 0x07:
      // Activity status message and configuration message share the same identifier
      var tag = bytes[5];
      switch (tag) {
        case 1:
          decoded.type = "ACTIVITY STATUS";
          decoded.activity_counter = uint32(bytes.slice(6, 10));
          break;

        case 2:
          decoded.type = "CONFIGURATION";
          for (i = 0; i < 5; i++) {
            var offset = 6 + 5 * i;
            decoded["param" + i] = {
              type: bytes[offset],
              value: uint32(bytes.slice(offset + 1, offset + 5))
            };
          }
          break;

        default:
          decoded.error = message(0, ["UNSUPPORTED POSITION TYPE " + decoded.data + "/" + tag]);
      }
      break;

    case 0x09:
      decoded.type = "SHUTDOWN";
      break;

    case 0xFF:
      decoded.type = "DEBUG";
      break;

    default:
      decoded.error = message(0, ["UNSUPPORTED MESSAGE TYPE " + type]);
  }

  // Just some redundant debug info
  decoded.debug = {
    payload: hex(bytes),
    length: bytes.length,
    port: port,
    server_time: new Date().toISOString()
  };

  return decoded;
}
```

---

<div class="post-metadata">

**Author:** ![defjam](https://www.thethingsnetwork.org/forum/letter_avatar_proxy/v4/letter/d/9e8a1a/32.png) [@defjam](https://www.thethingsnetwork.org/forum/u/defjam)\
**Post date:** [January 6, 2023, 7:20pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/17 "2023-01-06T19:20:25Z")

</div>

Getting an error description": “UNSUPPORTED MESSAGE TYPE 10”  
Sample packet below –

> 0A2849770002FFFDFFFB040D

From a terminal “system info” gives the following:

```auto
Versions
 Application: AT2 2.3-1 - built Jun 14 2022 10:22:04
 MTOS: MTOSv4/CT0/2022.03.21 built on Mar 21 2022 at 17:33:14
 FW Specific: 00FF0144 10001F40 00002660

```

---

<div class="post-metadata">

**Author:** ![defjam](https://www.thethingsnetwork.org/forum/letter_avatar_proxy/v4/letter/d/9e8a1a/32.png) [@defjam](https://www.thethingsnetwork.org/forum/u/defjam)\
**Post date:** [January 6, 2023, 7:33pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/18 "2023-01-06T19:33:03Z")

</div>

Information at this link  
[https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/](https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/)

---

<div class="post-metadata">

**Author:** ![Johan\_Scheepers](https://www.thethingsnetwork.org/forum/user_avatar/www.thethingsnetwork.org/johan_scheepers/32/46550_2.png) [@Johan\_Scheepers](https://www.thethingsnetwork.org/forum/u/Johan_Scheepers)\
**Post date:** [January 6, 2023, 8:29pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/19 "2023-01-06T20:29:35Z")

</div>

> [@defjam](#):
>
> 0A2849770002FFFDFFFB040D

Your type is bytes[0]

Is this the only uplink message you get?

---

<div class="post-metadata">

**Author:** ![defjam](https://www.thethingsnetwork.org/forum/letter_avatar_proxy/v4/letter/d/9e8a1a/32.png) [@defjam](https://www.thethingsnetwork.org/forum/u/defjam)\
**Post date:** [January 7, 2023, 12:13pm UTC](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473/20 "2023-01-07T12:13:04Z")

</div>

NO – I get all the other values – The device operates as you would expect. Intermittently I get this value (0xA) as per the documentation here:

> [https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/](https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/)

Which looks to be decoded here:

> [https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/event/#event-value](https://docs.abeeway.com/thingpark-location/AbeewayRefGuide/uplink-messages/event/#event-value)

If no one else does it, I will adjust the code and post a link to a github repo. There seems to be some other message types that are missing too - I will try to add these.

[Next page](https://www.thethingsnetwork.org/forum/t/abeeway-microtracker-decoder/22473.md?page=2)
