It would help a lot to see (a link to) the full code and the libraries that are used, any documentation of the device itself, and a few example payloads with the expected values, preferably also with negative values when applicable. If those cannot be found then a log of the device, matching those example payloads, helps to see what’s in the program’s variables before the encoding happens. And to help future users of the same device find this topic, also please add some type number?
-
Are values transmitted MSB or LSB? In code and on paper, a decimal reading such as 300 could be written in the human readable hexadecimal representation 0x012C, denoting the two bytes 0x01 and 0x2C. But one needs to know which byte is transmitted first. (In fact, these two bytes will be transmitted as binary data, being either the 16 bits 0b00000001 00101100, or 0b00101100 00000001. But the order of the bits within each 8 bits byte never needs to be reversed when decoding.)
Now, lacking any documentation of, e.g., message.addUint16
one cannot tell if the result is MSB or LSB. However, the following snippet gives us a clue:
message.addUint8(lux % 255);
message.addUint16(lux / 255);
Above, the name addUint8
suggests that it handles 8 bits, and the %
modulo operator gives us the remainder after dividing by 255. So, this shows that the LSB of the variable lux
is added first. It’s probably safe to assume that this is transmitted first too, and that addUint16
adds 2 bytes using LSB as well. When comparing to Working with bytes this would roughly be the equivalent of:
data[0] = lux;
data[1] = lux >> 8;
data[2] = lux >> 16;
Unfortunately, this also raises a question: why does this use lux % 255
instead of lux % 256
? As 0x012C % 255
yields 0x2D, not 0x2C, this would transmit 0x2D 0x01 instead of 0x2C 0x01. And 0xFF00 / 255 yields 0x100 rather than 0xFF, which would even be transmitted as 0x00.
That’s a major error in the encoding.
I’d look for a different example.
-
How are decimal values and negative values transmitted? Names such as addUint16
suggest that only unsigned integer values are sent (even though such method typically really just splits the bytes and transmits the values without doing any interpretation or processing at all).
So, to handle decimals a value of 30.0 could be multiplied by 10, and be sent as 300, to be divided in the decoder again. To handle negative values, one would add some offset before transmitting it, and then subtract that during decoding. (Aside: sending and decoding signed integer values is not that hard either; you’ll find many examples on the forum.)
Now, what’s going on with this?
message.addUint16((temperature + 18) * 771);
An unsigned 16 bits integer ranges from 0 to 65,535 and it seems the author of the encoding wanted to use every available value to get the highest possible precision for some specific range in those 16 bits. Doing the reverse math this suggests a precision of 1/771 ≈ 0.0013 degrees, to encode a range of 0 / 771 - 18 = -18.000 through 65,535 / 771 - 18 = 67.000. That’s either a very expensive and accurate temperature sensor there, or this is a very unrealistic accuracy. Also, there is no special handling of values below -18 or above 67, so what if the sensor returns an unexpected value? Again, I’d look for a different example…
Still then, this could probably be decoded using:
function Decoder(bytes, port) {
var decoded = {};
// The index in the bytes array that needs to be handled next;
// use along with "i++" which returns the current value, and
// then increments it for the next usage
var i = 0;
// Temperature with 0.0013 precision, -18.000 through 67.000, LSB
var temperature = (bytes[i++] | bytes[i++]<<8) / 771 - 18;
// Unary plus operator to cast string result of toFixed to number
decoded.temperature = +temperature.toFixed(3);
// More magic to be added here
return decoded;
}
-
Next, what does message.addHumidity(humidity)
do? Lacking documentation, the only clue we have: if the total payload is 16 bytes, then this apparently uses 2 bytes. Assuming LSB, assuming this is an unsigned 16 bits integer again, and assuming relative humidity (so, 0 through 100%), then using 2 bytes might suggest this has two decimals, so ranges from 0 to 10,000 (well below the maximum of 65,535). Again, this implies an unrealistic accuracy of the sensor. Did I say I’d look for another encoding example? Anyway, this might work:
// Relative humidity with 0.01 precision, 0.00 through 100.00, LSB
decoded.humidity = (bytes[i++] | bytes[i++]<<8) / 100;
But beware: maybe the library also tries to use all possible values of a signed integer and multiplies by 655.35 to encode 0 through 100. This might then need:
// Relative humidity with 0.0015 precision, 0.000 through 100.000, LSB
decoded.humidity = (bytes[i++] | bytes[i++]<<8) / 655.35;
-
Another funny one:
message.addUint16((pressure - 300) * 81.9187);
Just like the temperature, this suggests a precision of 1/81.9187 ≈ 0.012, and a range from 0 / 81.9187 + 300 = 300.00 through 65,535 / 81.9187 + 300 = 1100.00. Again, unrealistic:
// Pressure with 0.012 precision, 300.00 through 1100.00, LSB
var pressure = (bytes[i++] | bytes[i++]<<8) / 81.9187 + 300;
// Unary plus operator to cast string result of toFixed to number
decoded.pressure = +pressure.toFixed(2);
-
As the library apparently has no built-in support for 24 bits numbers, we already saw the erroneous:
message.addUint8(lux % 255);
message.addUint16(lux / 255);
...
message.addUint8(uv % 255);
message.addUint16(uv / 255);
This seems wrong, but the decoder would read:
// NOT RECOMMENDED; fix the encoding instead
decoded.lux = bytes[i++] + 255 * (bytes[i++] | bytes[i++]<<8);
decoded.uv = bytes[i++] + 255 * (bytes[i++] | bytes[i++]<<8);
I’d either change all occurrences of 255 to 256, or rewrite this to:
message.addUint8(lux);
message.addUint16(lux >> 8);
...
message.addUint8(uv);
message.addUint16(uv >> 8);
…or even:
message.addUint8(lux);
message.addUint8(lux >> 8);
message.addUint8(lux >> 16);
...
message.addUint8(uv);
message.addUint8(uv >> 8);
message.addUint8(uv >> 16);
Whatever fix you choose, all should then decode just fine with:
// 24 bits unsigned integer, 0 through 16,777,215, LSB
decoded.lux = bytes[i++] | bytes[i++]<<8 | bytes[i++]<<16;
decoded.uv = bytes[i++] | bytes[i++]<<8 | bytes[i++]<<16;
-
And finally something I guess I can agree with, if these are low-concentration sensors:
message.addUint16(pm10 * 10);
...
message.addUint16(pm25 * 10);
…which decodes using:
// Particulate matter with 0.1 precision, 0.0 through 6,553.5, LSB
decoded.pm10 = (bytes[i++] | bytes[i++]<<8) / 10;
decoded.pm25 = (bytes[i++] | bytes[i++]<<8) / 10;
-
Of course, seeing some repeated code, one could define a helper function in the Payload Format in TTN Console:
/**
* Convert the array of bytes to an unsigned integer, LSB.
*
* BEWARE: This is only safe up to 0x1FFFFFFFFFFFFF, so: 6 bytes.
*/
function uint(bytes) {
return bytes.reduceRight(function(acc, b) {
// We expect an unsigned value, so to support more than 3 bytes
// don't use any bitwise operators, which would always yield a
// signed 32 bits integer instead.
return acc * 0x100 + b;
}, 0);
}
…along with:
function Decoder(bytes, port) {
var decoded = {};
// The index in the bytes array that needs to be handled next;
// use along with "i++" which returns the current value, and
// then increments it for the next usage
var i = 0;
// Temperature with 0.0013 precision, -18.000 through 67.000, LSB
var temperature = uint(bytes.slice(i, i+=2)) / 771 - 18;
// Unary plus operator to cast string result of toFixed to number
decoded.temperature = +temperature.toFixed(3);
// Relative humidity with 0.01 precision, 0.00 through 100.00, LSB
decoded.humidity = uint(bytes.slice(i, i+=2)) / 100;
// Pressure with 0.012 precision, 300.00 through 1100.00, LSB
var pressure = uint(bytes.slice(i, i+=2)) / 81.9187 + 300;
decoded.pressure = +pressure.toFixed(2);
decoded.lux = uint(bytes.slice(i, i+=3));
decoded.uv = uint(bytes.slice(i, i+=3));
decoded.pm10 = uint(bytes.slice(i, i+=2)) / 10;
decoded.pm25 = uint(bytes.slice(i, i+=2)) / 10;
return decoded;
}
Lacking example payloads: all untested. Even if the above works for you, I’d still look for another example. Also, please report the error about using 255 instead of 256 to the original author.