Hmmm, that doesnât seem right, if I read https://support.digitalmatter.com/helpdesk/attachments/16024374732
3.2. Uplink
LoRaWAN uplink payloads can be as small as 11 bytes in some regions (for the longest range transmissions). The packet headers already include the device serial number, and a âport numberâ from 1 to 223, which we will use as a message type.
3.2.1. Uplink Port 1: Status Update
Offset |
Description |
0.0 |
0: Out of trip, 1: In-trip |
0.1 - 0.7 |
Battery voltage, LSb = 14 mV, 2 V offset |
1 (BYTE) |
Temperature, LSb = 0.5 °C, -40 °C offset |
2.0 |
Man down (no movement for configured period), optional |
2.1 - 2.7 |
Inclination (see configuration and usage guide), 0-120: 0-180°, optional |
3 (BYTE) |
Azimuth (see configuration and guide), 0-239: 0-358.5°, optional |
This message includes two optional bytes, which are only sent if either the Man Down or Tilt
feature is enabled. They are not enabled by default.
Above, â2.1 - 2.7â implies that you need to use bits 1 to 7 of byte 2. So: youâll need to ignore bit 0, which is actually used to indicate âMan downâ. Also, the number of the byte is different? So, Iâd assume:
// Inclination: bits 1..7; ignore bit 0 by right-shifting it out of memory
var inclinationEncoded = bytes[2] >> 1;
var inclinationDecoded = inclinationEncoded * 1.5;
obj.inclination = +inclinationDecoded.toFixed(2);
Also, Iâd say that toFixed
is not needed here, as youâre multiplying an integer by 1.5, which will never give you more than 1 decimal. But if youâre using it, then beware that toFixed
returns a string value. Prefix that string result with the unary plus operator to get a true number rather than some string, like I wrote earlier. So, you should expect to see no quotes in the value, like:
"inclination": 3.5
âŚrather than:
"inclination": "3.50"
All said, this one line suffices:
// Inclination: bits 1..7; ignore bit 0 by right-shifting it out of memory
obj.inclination = (bytes[2] >> 1) * 1.5;
Finally, it doesnât seem complete, as how did you get the battery value that you posted then?
Even better, the documentation includes the following full working example for TTN:
3.2.5. Example JavaScript Decode
// Decode an uplink message from an array of bytes to an object of fields
function Decoder(bytes, port)
{
var decoded = {};
if (port === 1)
{
decoded.type = "status";
decoded.inTrip = ((bytes[0] & 0x1) !== 0) ? true : false;
decoded.batV = 2.0 + 0.014 * (bytes[0] >> 1);
decoded.temp = -40.0 + 0.5 * bytes[1];
if (bytes.length >= 4)
{
decoded.manDown = ((bytes[2] & 0x1) !== 0) ? true : false;
decoded.inclinationDeg = (bytes[2] >> 1) * 1.5;
decoded.azimuthDeg = bytes[3] * 1.5;
// Extra derived angles
decoded.xyz = {};
{
// The direction of 'down' in rectangular coordinates,
// unit vector.
d = decoded.xyz.downUnit =
[
Math.sin(decoded.inclinationDeg * Math.PI / 180) *
Math.sin(decoded.azimuthDeg * Math.PI / 180),
Math.cos(decoded.inclinationDeg * Math.PI / 180),
Math.sin(decoded.inclinationDeg * Math.PI / 180) *
Math.cos(decoded.azimuthDeg * Math.PI / 180),
];
// The azimuthal angles about each axis, right-handed, in degrees.
// You can set up triggers on these angles. These trigger angles
// are not well defined if the inclination is within 7 degrees of
// vertical, and will not trigger within that range.
hypX = Math.sqrt(d[1]*d[1] + d[2]*d[2]);
hypY = Math.sqrt(d[2]*d[2] + d[0]*d[0]);
hypZ = Math.sqrt(d[0]*d[0] + d[1]*d[1]);
decoded.xyz.azimuthDeg =
[
(hypX < 0.125) ? null : (Math.atan2(d[2], d[1]) * 180 / Math.PI),
(hypY < 0.125) ? null : decoded.azimuthDeg,
(hypZ < 0.125) ? null : (Math.atan2(d[1], d[0]) * 180 / Math.PI),
];
if (decoded.xyz.azimuthDeg[0] < 0)
decoded.xyz.azimuthDeg[0] += 360;
if (decoded.xyz.azimuthDeg[2] < 0)
decoded.xyz.azimuthDeg[2] += 360;
// The angle between each axis and 'down', in degrees.
// You can set up triggers on these angles.
// They are always well defined.
iX = 1 - ((d[0]-1)*(d[0]-1) + d[1]*d[1] + d[2]*d[2]) / 2;
iZ = 1 - (d[0]*d[0] + d[1]*d[1] + (d[2]-1)*(d[2]-1)) / 2;
iX = Math.max(iX, -1);
iX = Math.min(iX, +1);
iZ = Math.max(iZ, -1);
iZ = Math.min(iZ, +1);
decoded.xyz.inclinationDeg =
[
Math.acos(iX) * 180 / Math.PI,
decoded.inclinationDeg,
Math.acos(iZ) * 180 / Math.PI,
];
}
}
else
{
decoded.manDown = null;
decoded.inclinationDeg = null;
decoded.azimuthDeg = null;
decoded.xyz = null;
}
}
else if (port === 2)
{
decoded.type = "downlink ack";
decoded.sequence = (bytes[0] & 0x7F);
decoded.accepted = ((bytes[0] & 0x80) !== 0) ? true : false;
decoded.fwMaj = bytes[1];
decoded.fwMin = bytes[2];
}
else if (port === 3)
{
decoded.type = "stats";
decoded.initialBatV = 2.0 + 0.014 * (bytes[0] & 0x7F);
decoded.uptimeWeeks = (bytes[0] >> 7) + bytes[1] * 2;
decoded.txCount = 32 * (bytes[2] + bytes[3] * 256);
decoded.tripCount = 32 * (bytes[4] + bytes[5] * 256);
decoded.wakeupsPerTrip = bytes[6];
}
else if (port === 4)
{
decoded.type = "rtc request";
decoded.wasSet = ((bytes[0] & 0x01) !== 0) ? true : false;
decoded.cookie = (bytes[0] + bytes[1] * 256 + bytes[2] * 65536 +
bytes[3] * 16777216) >>> 1;
// seconds since 2013-01-01
decoded.timestamp = bytes[4] + bytes[5] * 256 + bytes[6] * 65536 +
bytes[7] * 16777216;
// Date() takes milliseconds since 1970-01-01
decoded.time = (new Date((decoded.timestamp + 1356998400) * 1000))
.toUTCString();
}
return decoded;
}
Finally, they offer a nice online decoder, which shows the following for your payload. This shows your decoding is wrong:
{
"type": "status",
"inTrip": false,
"batV": 2.43,
"temp": 26,
"manDown": false,
"inclinationDeg": 90,
"azimuthDeg": 3,
"xyz": {
"downUnit": [0.05234, 6.123e-17, 0.9986],
"azimuthDeg": [90, 3, null],
"inclinationDeg": [87, 90, 3]
}
}
Great documentation!