The following can be used to decode the data. I don’t own this device, so it has only been tested with the provided examples, and some more examples I created myself:
function Decoder(bytes, port) {
// See http://wiki.nemeus.fr/index.php?title=NIS-UL_UltraSonic_Sensor#Nemeus_uplink_protocol
// Test with:
// 8B 02 0097 0098 1f 19: 151, 152cm; 31, 25°C
// 8B 02 0097 0098 fe 03: 151, 152cm; -2, 3°C
// AF 03 0097 0098 0090 0fb9 0e91 0fff fe 03 00 0a: 151, 152, 144cm; 4025, 3729, 4095mV, -2, 3, 0°C
// 82 00a0: 160cm
// 88 1e: 30°C
var usonic_dist = [], voltage = [], internal_temp = [];
var i = 0, m = 0;
var mask = bytes[i++];
// If bit 0 set: 1 byte indicating the number of measurements, else 1
var nb_meas = mask & 1<<0 ? bytes[i++] : 1;
// If bit 1 set: distances in centimeters, each 2 bytes unsigned MSB, 1 to 300cm
if (mask & 1<<1) {
for (m = 0; m < nb_meas; m++) {
usonic_dist.push(bytes[i++]<<8 | bytes[i++]);
}
}
// If bit 2 set: voltages in millivolts, each 2 bytes unsigned MSB
if (mask & 1<<2) {
for (m = 0; m < nb_meas; m++) {
voltage.push((bytes[i++]<<8 | bytes[i++]) / 1000);
}
}
// If bit 3 set: internal temperatures, each 1 byte signed integer [-128..+127]
if (mask & 1<<3) {
for (m = 0; m < nb_meas; m++) {
// Sign-extend to 32 bits to support negative values, by shifting 24 bits
// (too far) to the left, followed by a sign-propagating right shift:
internal_temp.push(bytes[i++]<<24>>24);
}
}
// Bit 4: reserved
// If bit 5 set: cause for the measurement, else "periodic measurement"
var cause = mask & 1<<5 ? bytes[i++] : 1;
// We should have consumed all bytes, if not then just return the payload as hex
if (i !== bytes.length) {
return {
'error': 'failed to parse payload',
'payload': bytes.map(function(b) {
return ('0' + b.toString(16).toUpperCase()).substr(-2);
}).join(' ')
}
}
// continued below!
…which in the very same Decoder
can then be formatted any way you like:
// Format the results
var result = {};
// Show all 8 bits, ensuring leading zeroes
result.mask = '0b' + ('00000000' + mask.toString(2)).substr(-8);
result.numberOfMeasurements = nb_meas;
result.distances = usonic_dist;
result.voltages = voltage;
result.temperatures = internal_temp;
// Same data, grouped by measurement
result.measurements = [];
for (m = 0; m < nb_meas; m++) {
result.measurements.push({
distance: usonic_dist[m],
voltage: voltage[m],
temperature: internal_temp[m]
});
}
result.cause = cause;
result.causeMask = '0b' + ('00000000' + cause.toString(2)).substr(-8);
result.causes = [];
if (cause & 1<<0) {
result.causes.push('periodic');
}
if (cause & 1<<1) {
result.causes.push('usonic_dist > high threshold');
}
if (cause & 1<<2) {
result.causes.push('usonic_dist < high threshold - high hysteresis');
}
if (cause & 1<<3) {
result.causes.push('usonic_dist < low threshold');
}
if (cause & 1<<4) {
result.causes.push('usonic_dist > low threshold + low hysteresis');
}
if (cause & 1<<5) {
result.causes.push('forced manually');
}
return result;
}
…which for AF 03 0097 0098 0090 0fb9 0e91 0fff fe 03 00 0a
would return something like:
{
"mask": "0b10101111",
"numberOfMeasurements": 3,
"distances": [
151,
152,
144
],
"voltages": [
4.025,
3.729,
4.095
],
"temperatures": [
-2,
3,
0
],
"measurements": [
{
"distance": 151,
"voltage": 4.025,
"temperature": -2
},
{
"distance": 152,
"voltage": 3.729,
"temperature": 3
},
{
"distance": 144,
"voltage": 4.095,
"temperature": 0
}
],
"cause": 10,
"causeMask": "0b00001010",
"causes": [
"usonic_dist > high threshold",
"usonic_dist < low threshold"
]
}
(Above, I changed the order for readability, to match the order of the bytes and the handling in the Decoder; you’ll get the keys sorted by name instead. And of course, much of the data in the result is redundant.)
If you know of a way to add this the Nemeus wiki, then please do! If you have true data, then please let us know as well, for proper testing.